mirror of
https://github.com/id-Software/DOOM-3-BFG.git
synced 2024-12-02 17:02:17 +00:00
3856 lines
90 KiB
C++
3856 lines
90 KiB
C++
/*
|
|
===========================================================================
|
|
|
|
Doom 3 BFG Edition GPL Source Code
|
|
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
|
|
|
|
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
|
|
|
|
Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
In addition, the Doom 3 BFG Edition 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 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
|
|
|
|
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.
|
|
|
|
===========================================================================
|
|
*/
|
|
|
|
#include "precompiled.h"
|
|
#pragma hdrstop
|
|
|
|
#include "Game_local.h"
|
|
|
|
// could be a problem if players manage to go down sudden deaths till this .. oh well
|
|
#define LASTMAN_NOLIVES -20
|
|
|
|
extern idCVar ui_skinIndex;
|
|
|
|
const char* idMultiplayerGame::teamNames[] = { "#str_02499", "#str_02500" };
|
|
const char* idMultiplayerGame::skinNames[] =
|
|
{
|
|
"skins/characters/player/marine_mp",
|
|
"skins/characters/player/marine_mp_red",
|
|
"skins/characters/player/marine_mp_blue",
|
|
"skins/characters/player/marine_mp_green",
|
|
"skins/characters/player/marine_mp_yellow",
|
|
"skins/characters/player/marine_mp_purple",
|
|
"skins/characters/player/marine_mp_grey",
|
|
"skins/characters/player/marine_mp_orange"
|
|
};
|
|
const idVec3 idMultiplayerGame::skinColors[] =
|
|
{
|
|
idVec3( 0.25f, 0.25f, 0.25f ), // light grey
|
|
idVec3( 1.00f, 0.00f, 0.00f ), // red
|
|
idVec3( 0.20f, 0.50f, 0.80f ), // blue
|
|
idVec3( 0.00f, 0.80f, 0.10f ), // green
|
|
idVec3( 1.00f, 0.80f, 0.10f ), // yellow
|
|
idVec3( 0.39f, 0.199f, 0.3f ), // purple
|
|
idVec3( 0.425f, 0.484f, 0.445f ), // dark grey
|
|
idVec3( 0.484f, 0.312f, 0.074f ) // orange
|
|
};
|
|
const int idMultiplayerGame::numSkins = sizeof( idMultiplayerGame::skinNames ) / sizeof( idMultiplayerGame::skinNames[0] );
|
|
|
|
const char* idMultiplayerGame::GetTeamName( int team ) const
|
|
{
|
|
return teamNames[idMath::ClampInt( 0, 1, team )];
|
|
}
|
|
const char* idMultiplayerGame::GetSkinName( int skin ) const
|
|
{
|
|
return skinNames[idMath::ClampInt( 0, numSkins - 1, skin )];
|
|
}
|
|
const idVec3& idMultiplayerGame::GetSkinColor( int skin ) const
|
|
{
|
|
return skinColors[idMath::ClampInt( 0, numSkins - 1, skin )];
|
|
}
|
|
|
|
// make CTF not included in game modes for consoles
|
|
|
|
const char* gameTypeNames_WithCTF[] = { "Deathmatch", "Tourney", "Team DM", "Last Man", "CTF", "" };
|
|
const char* gameTypeDisplayNames_WithCTF[] = { "#str_04260", "#str_04261", "#str_04262", "#str_04263", "#str_swf_mode_ctf", "" };
|
|
|
|
const char* gameTypeNames_WithoutCTF[] = { "Deathmatch", "Tourney", "Team DM", "Last Man", "" };
|
|
const char* gameTypeDisplayNames_WithoutCTF[] = { "#str_04260", "#str_04261", "#str_04262", "#str_04263", "" };
|
|
|
|
compile_time_assert( GAME_DM == 0 );
|
|
compile_time_assert( GAME_TOURNEY == 1 );
|
|
compile_time_assert( GAME_TDM == 2 );
|
|
compile_time_assert( GAME_LASTMAN == 3 );
|
|
compile_time_assert( GAME_CTF == 4 );
|
|
|
|
idCVar g_spectatorChat( "g_spectatorChat", "0", CVAR_GAME | CVAR_ARCHIVE | CVAR_BOOL, "let spectators talk to everyone during game" );
|
|
|
|
// global sounds transmitted by index - 0 .. SND_COUNT
|
|
// sounds in this list get precached on MP start
|
|
const char* idMultiplayerGame::GlobalSoundStrings[] =
|
|
{
|
|
"sound/vo/feedback/voc_youwin.wav",
|
|
"sound/vo/feedback/voc_youlose.wav",
|
|
"sound/vo/feedback/fight.wav",
|
|
"sound/vo/feedback/three.wav",
|
|
"sound/vo/feedback/two.wav",
|
|
"sound/vo/feedback/one.wav",
|
|
"sound/vo/feedback/sudden_death.wav",
|
|
"sound/vo/ctf/flag_capped_yours.wav",
|
|
"sound/vo/ctf/flag_capped_theirs.wav",
|
|
"sound/vo/ctf/flag_return.wav",
|
|
"sound/vo/ctf/flag_taken_yours.wav",
|
|
"sound/vo/ctf/flag_taken_theirs.wav",
|
|
"sound/vo/ctf/flag_dropped_yours.wav",
|
|
"sound/vo/ctf/flag_dropped_theirs.wav"
|
|
};
|
|
|
|
// handy verbose
|
|
const char* idMultiplayerGame::GameStateStrings[] =
|
|
{
|
|
"INACTIVE",
|
|
"WARMUP",
|
|
"COUNTDOWN",
|
|
"GAMEON",
|
|
"SUDDENDEATH",
|
|
"GAMEREVIEW",
|
|
"NEXTGAME"
|
|
};
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::idMultiplayerGame
|
|
================
|
|
*/
|
|
idMultiplayerGame::idMultiplayerGame()
|
|
{
|
|
|
|
teamFlags[0] = NULL;
|
|
teamFlags[1] = NULL;
|
|
|
|
teamPoints[0] = 0;
|
|
teamPoints[1] = 0;
|
|
|
|
flagMsgOn = true;
|
|
|
|
player_blue_flag = -1;
|
|
player_red_flag = -1;
|
|
|
|
Clear();
|
|
|
|
scoreboardManager = NULL;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::Shutdown
|
|
================
|
|
*/
|
|
void idMultiplayerGame::Shutdown()
|
|
{
|
|
Clear();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::Reset
|
|
================
|
|
*/
|
|
void idMultiplayerGame::Reset()
|
|
{
|
|
Clear();
|
|
ClearChatData();
|
|
|
|
if( common->IsMultiplayer() )
|
|
{
|
|
scoreboardManager = new idMenuHandler_Scoreboard();
|
|
if( scoreboardManager != NULL )
|
|
{
|
|
scoreboardManager->Initialize( "scoreboard", common->SW() );
|
|
}
|
|
}
|
|
|
|
ClearChatData();
|
|
warmupEndTime = 0;
|
|
lastChatLineTime = 0;
|
|
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::ServerClientConnect
|
|
================
|
|
*/
|
|
void idMultiplayerGame::ServerClientConnect( int clientNum )
|
|
{
|
|
memset( &playerState[ clientNum ], 0, sizeof( playerState[ clientNum ] ) );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::SpawnPlayer
|
|
================
|
|
*/
|
|
void idMultiplayerGame::SpawnPlayer( int clientNum )
|
|
{
|
|
idLobbyBase& lobby = session->GetActingGameStateLobbyBase();
|
|
lobbyUserID_t& lobbyUserID = gameLocal.lobbyUserIDs[clientNum];
|
|
|
|
AddChatLine( idLocalization::GetString( "#str_07177" ), lobby.GetLobbyUserName( lobbyUserID ) );
|
|
|
|
memset( &playerState[ clientNum ], 0, sizeof( playerState[ clientNum ] ) );
|
|
if( !common->IsClient() )
|
|
{
|
|
if( gameLocal.gameType == GAME_LASTMAN )
|
|
{
|
|
// Players spawn with no lives to prevent them from getting the clean slate achievement if
|
|
// they didn't earn it.
|
|
playerState[ clientNum ].fragCount = LASTMAN_NOLIVES;
|
|
}
|
|
|
|
idPlayer* p = static_cast< idPlayer* >( gameLocal.entities[ clientNum ] );
|
|
p->spawnedTime = gameLocal.serverTime;
|
|
p->team = 0;
|
|
p->tourneyRank = 0;
|
|
if( gameLocal.gameType == GAME_TOURNEY && gameState == GAMEON )
|
|
{
|
|
p->tourneyRank++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::Clear
|
|
================
|
|
*/
|
|
void idMultiplayerGame::Clear()
|
|
{
|
|
int i;
|
|
|
|
gameState = INACTIVE;
|
|
nextState = INACTIVE;
|
|
nextStateSwitch = 0;
|
|
matchStartedTime = 0;
|
|
currentTourneyPlayer[ 0 ] = -1;
|
|
currentTourneyPlayer[ 1 ] = -1;
|
|
one = two = three = false;
|
|
memset( &playerState, 0 , sizeof( playerState ) );
|
|
lastWinner = -1;
|
|
pureReady = false;
|
|
|
|
CleanupScoreboard();
|
|
|
|
fragLimitTimeout = 0;
|
|
voiceChatThrottle = 0;
|
|
for( i = 0; i < NUM_CHAT_NOTIFY; i++ )
|
|
{
|
|
chatHistory[ i ].line.Clear();
|
|
}
|
|
startFragLimit = -1;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::Clear
|
|
================
|
|
*/
|
|
void idMultiplayerGame::CleanupScoreboard()
|
|
{
|
|
if( scoreboardManager != NULL )
|
|
{
|
|
delete scoreboardManager;
|
|
scoreboardManager = NULL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::GetFlagPoints
|
|
|
|
Gets number of captures in CTF game.
|
|
|
|
0 = red team
|
|
1 = blue team
|
|
================
|
|
*/
|
|
int idMultiplayerGame::GetFlagPoints( int team )
|
|
{
|
|
assert( team <= 1 );
|
|
|
|
return teamPoints[ team ];
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::UpdatePlayerRanks
|
|
================
|
|
*/
|
|
void idMultiplayerGame::UpdatePlayerRanks()
|
|
{
|
|
int i, j, k;
|
|
idPlayer* players[MAX_CLIENTS];
|
|
idEntity* ent;
|
|
idPlayer* player;
|
|
|
|
memset( players, 0, sizeof( players ) );
|
|
numRankedPlayers = 0;
|
|
|
|
for( i = 0; i < gameLocal.numClients; i++ )
|
|
{
|
|
ent = gameLocal.entities[ i ];
|
|
if( !ent || !ent->IsType( idPlayer::Type ) )
|
|
{
|
|
continue;
|
|
}
|
|
player = static_cast< idPlayer* >( ent );
|
|
if( !CanPlay( player ) )
|
|
{
|
|
continue;
|
|
}
|
|
if( gameLocal.gameType == GAME_TOURNEY )
|
|
{
|
|
if( i != currentTourneyPlayer[ 0 ] && i != currentTourneyPlayer[ 1 ] )
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
if( gameLocal.gameType == GAME_LASTMAN && playerState[ i ].fragCount == LASTMAN_NOLIVES )
|
|
{
|
|
continue;
|
|
}
|
|
for( j = 0; j < numRankedPlayers; j++ )
|
|
{
|
|
bool insert = false;
|
|
|
|
if( IsGametypeTeamBased() ) /* CTF */
|
|
{
|
|
if( player->team != players[ j ]->team )
|
|
{
|
|
if( playerState[ i ].teamFragCount > playerState[ players[ j ]->entityNumber ].teamFragCount )
|
|
{
|
|
// team scores
|
|
insert = true;
|
|
}
|
|
else if( playerState[ i ].teamFragCount == playerState[ players[ j ]->entityNumber ].teamFragCount && player->team < players[ j ]->team )
|
|
{
|
|
// at equal scores, sort by team number
|
|
insert = true;
|
|
}
|
|
}
|
|
else if( playerState[ i ].fragCount > playerState[ players[ j ]->entityNumber ].fragCount )
|
|
{
|
|
// in the same team, sort by frag count
|
|
insert = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
insert = ( playerState[ i ].fragCount > playerState[ players[ j ]->entityNumber ].fragCount );
|
|
}
|
|
if( insert )
|
|
{
|
|
for( k = numRankedPlayers; k > j; k-- )
|
|
{
|
|
players[ k ] = players[ k - 1 ];
|
|
}
|
|
players[ j ] = player;
|
|
break;
|
|
}
|
|
}
|
|
if( j == numRankedPlayers )
|
|
{
|
|
players[ numRankedPlayers ] = player;
|
|
}
|
|
numRankedPlayers++;
|
|
}
|
|
|
|
memcpy( rankedPlayers, players, sizeof( players ) );
|
|
}
|
|
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::UpdateRankColor
|
|
================
|
|
*/
|
|
void idMultiplayerGame::UpdateRankColor( idUserInterface* gui, const char* mask, int i, const idVec3& vec )
|
|
{
|
|
for( int j = 1; j < 4; j++ )
|
|
{
|
|
gui->SetStateFloat( va( mask, i, j ), vec[ j - 1 ] );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::IsScoreboardActive
|
|
================
|
|
*/
|
|
bool idMultiplayerGame::IsScoreboardActive()
|
|
{
|
|
|
|
if( scoreboardManager != NULL )
|
|
{
|
|
return scoreboardManager->IsActive();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::HandleGuiEvent
|
|
================
|
|
*/
|
|
bool idMultiplayerGame::HandleGuiEvent( const sysEvent_t* sev )
|
|
{
|
|
|
|
if( scoreboardManager != NULL && scoreboardManager->IsActive() )
|
|
{
|
|
scoreboardManager->HandleGuiEvent( sev );
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::UpdateScoreboard
|
|
================
|
|
*/
|
|
void idMultiplayerGame::UpdateScoreboard( idMenuHandler_Scoreboard* scoreboard, idPlayer* owner )
|
|
{
|
|
|
|
if( owner == NULL )
|
|
{
|
|
return;
|
|
}
|
|
|
|
int redScore = 0;
|
|
int blueScore = 0;
|
|
|
|
idLobbyBase& lobby = session->GetActingGameStateLobbyBase();
|
|
idList< mpScoreboardInfo > scoreboardInfo;
|
|
for( int i = 0; i < MAX_CLIENTS; ++i )
|
|
{
|
|
|
|
if( i < gameLocal.numClients )
|
|
{
|
|
|
|
idEntity* ent = gameLocal.entities[ i ];
|
|
if( !ent || !ent->IsType( idPlayer::Type ) )
|
|
{
|
|
continue;
|
|
}
|
|
idPlayer* player = static_cast<idPlayer*>( ent );
|
|
if( !player )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
idStr spectateData;
|
|
idStr playerName;
|
|
int score = 0;
|
|
int wins = 0;
|
|
int ping = 0;
|
|
int playerNum = player->entityNumber;
|
|
int team = - 1;
|
|
|
|
lobbyUserID_t& lobbyUserID = gameLocal.lobbyUserIDs[ playerNum ];
|
|
|
|
if( IsGametypeTeamBased() )
|
|
{
|
|
team = player->team;
|
|
if( team == 0 )
|
|
{
|
|
redScore = playerState[ playerNum ].teamFragCount;
|
|
}
|
|
else if( team == 1 )
|
|
{
|
|
blueScore = playerState[ playerNum ].teamFragCount;
|
|
}
|
|
}
|
|
|
|
score = idMath::ClampInt( MP_PLAYER_MINFRAGS, MP_PLAYER_MAXFRAGS, playerState[ playerNum ].fragCount );
|
|
|
|
// HACK -
|
|
if( gameLocal.gameType == GAME_LASTMAN && score == LASTMAN_NOLIVES )
|
|
{
|
|
score = 0;
|
|
}
|
|
|
|
wins = idMath::ClampInt( 0, MP_PLAYER_MAXWINS, playerState[ playerNum ].wins );
|
|
ping = playerState[ playerNum ].ping;
|
|
|
|
if( gameState == WARMUP )
|
|
{
|
|
if( player->spectating )
|
|
{
|
|
spectateData = idLocalization::GetString( "#str_04246" );
|
|
}
|
|
else
|
|
{
|
|
spectateData = idLocalization::GetString( "#str_04247" );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if( gameLocal.gameType == GAME_LASTMAN && playerState[ playerNum ].fragCount == LASTMAN_NOLIVES )
|
|
{
|
|
spectateData = idLocalization::GetString( "#str_06736" );
|
|
}
|
|
else if( player->spectating )
|
|
{
|
|
spectateData = idLocalization::GetString( "#str_04246" );
|
|
}
|
|
}
|
|
|
|
if( playerNum == owner->entityNumber )
|
|
{
|
|
playerName = "^3";
|
|
playerName.Append( lobby.GetLobbyUserName( lobbyUserID ) );
|
|
playerName.Append( "^0" );
|
|
}
|
|
else
|
|
{
|
|
playerName = lobby.GetLobbyUserName( lobbyUserID );
|
|
}
|
|
|
|
mpScoreboardInfo info;
|
|
info.voiceState = session->GetDisplayStateFromVoiceState( session->GetLobbyUserVoiceState( lobbyUserID ) );
|
|
info.name = playerName;
|
|
info.team = team;
|
|
info.score = score;
|
|
info.wins = wins;
|
|
info.ping = ping;
|
|
info.playerNum = playerNum;
|
|
info.spectateData = spectateData;
|
|
|
|
bool added = false;
|
|
for( int i = 0; i < scoreboardInfo.Num(); ++i )
|
|
{
|
|
if( info.team == scoreboardInfo[i].team )
|
|
{
|
|
if( info.score > scoreboardInfo[i].score )
|
|
{
|
|
scoreboardInfo.Insert( info, i );
|
|
added = true;
|
|
break;
|
|
}
|
|
}
|
|
else if( info.team < scoreboardInfo[i].team )
|
|
{
|
|
scoreboardInfo.Insert( info, i );
|
|
added = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( !added )
|
|
{
|
|
scoreboardInfo.Append( info );
|
|
}
|
|
}
|
|
}
|
|
|
|
idStr gameInfo;
|
|
if( gameState == GAMEREVIEW )
|
|
{
|
|
int timeRemaining = nextStateSwitch - gameLocal.serverTime;
|
|
int ms = ( int ) ceilf( timeRemaining / 1000.0f );
|
|
if( ms == 1 )
|
|
{
|
|
gameInfo = idLocalization::GetString( "#str_online_game_starts_in_second" );
|
|
gameInfo.Replace( "<DNT_VAL>", idStr( ms ) );
|
|
}
|
|
else if( ms > 0 && ms < 30 )
|
|
{
|
|
gameInfo = idLocalization::GetString( "#str_online_game_starts_in_seconds" );
|
|
gameInfo.Replace( "<DNT_VAL>", idStr( ms ) );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if( gameLocal.gameType == GAME_LASTMAN )
|
|
{
|
|
if( gameState == GAMEON || gameState == SUDDENDEATH )
|
|
{
|
|
gameInfo = va( "%s: %i", idLocalization::GetString( "#str_04264" ), startFragLimit );
|
|
}
|
|
else
|
|
{
|
|
gameInfo = va( "%s: %i", idLocalization::GetString( "#str_04264" ), gameLocal.serverInfo.GetInt( "si_fragLimit" ) );
|
|
}
|
|
}
|
|
else if( gameLocal.gameType == GAME_CTF )
|
|
{
|
|
int captureLimit = gameLocal.serverInfo.GetInt( "si_fragLimit" );
|
|
gameInfo = va( idLocalization::GetString( "#str_11108" ), captureLimit );
|
|
}
|
|
else
|
|
{
|
|
gameInfo = va( "%s: %i", idLocalization::GetString( "#str_01982" ), gameLocal.serverInfo.GetInt( "si_fragLimit" ) );
|
|
}
|
|
|
|
if( gameLocal.serverInfo.GetInt( "si_timeLimit" ) > 0 )
|
|
{
|
|
gameInfo.Append( va( " %s: %i", idLocalization::GetString( "#str_01983" ), gameLocal.serverInfo.GetInt( "si_timeLimit" ) ) );
|
|
}
|
|
}
|
|
|
|
if( scoreboardManager )
|
|
{
|
|
|
|
if( IsGametypeFlagBased() )
|
|
{
|
|
scoreboardManager->SetTeamScores( GetFlagPoints( 0 ), GetFlagPoints( 1 ) );
|
|
}
|
|
else if( IsGametypeTeamBased() )
|
|
{
|
|
scoreboardManager->SetTeamScores( redScore, blueScore );
|
|
}
|
|
|
|
scoreboardManager->UpdateScoreboard( scoreboardInfo, gameInfo );
|
|
scoreboardManager->UpdateScoreboardSelection();
|
|
scoreboardManager->Update();
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::GameTime
|
|
================
|
|
*/
|
|
const char* idMultiplayerGame::GameTime()
|
|
{
|
|
static char buff[16];
|
|
int m, s, t, ms;
|
|
|
|
if( gameState == COUNTDOWN )
|
|
{
|
|
ms = warmupEndTime - gameLocal.serverTime;
|
|
|
|
// we never want to show double dashes.
|
|
if( ms <= 0 )
|
|
{
|
|
|
|
// Try to setup time again.
|
|
warmupEndTime = gameLocal.serverTime + 1000 * cvarSystem->GetCVarInteger( "g_countDown" );
|
|
ms = warmupEndTime - gameLocal.serverTime;
|
|
}
|
|
|
|
s = ms / 1000 + 1;
|
|
if( ms <= 0 )
|
|
{
|
|
strcpy( buff, "WMP --" );
|
|
}
|
|
else
|
|
{
|
|
sprintf( buff, "WMP %i", s );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int timeLimit = gameLocal.serverInfo.GetInt( "si_timeLimit" );
|
|
if( timeLimit )
|
|
{
|
|
ms = ( timeLimit * 60000 ) - ( gameLocal.serverTime - matchStartedTime );
|
|
}
|
|
else
|
|
{
|
|
ms = gameLocal.serverTime - matchStartedTime;
|
|
}
|
|
if( ms < 0 )
|
|
{
|
|
ms = 0;
|
|
}
|
|
|
|
s = ms / 1000;
|
|
m = s / 60;
|
|
s -= m * 60;
|
|
t = s / 10;
|
|
s -= t * 10;
|
|
|
|
sprintf( buff, "%i:%i%i", m, t, s );
|
|
}
|
|
return &buff[0];
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::NumActualClients
|
|
================
|
|
*/
|
|
int idMultiplayerGame::NumActualClients( bool countSpectators, int* teamcounts )
|
|
{
|
|
idPlayer* p;
|
|
int c = 0;
|
|
|
|
if( teamcounts )
|
|
{
|
|
teamcounts[ 0 ] = teamcounts[ 1 ] = 0;
|
|
}
|
|
for( int i = 0 ; i < gameLocal.numClients ; i++ )
|
|
{
|
|
idEntity* ent = gameLocal.entities[ i ];
|
|
if( !ent || !ent->IsType( idPlayer::Type ) )
|
|
{
|
|
continue;
|
|
}
|
|
p = static_cast< idPlayer* >( ent );
|
|
if( countSpectators || CanPlay( p ) )
|
|
{
|
|
c++;
|
|
}
|
|
if( teamcounts && CanPlay( p ) )
|
|
{
|
|
teamcounts[ p->team ]++;
|
|
}
|
|
}
|
|
return c;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::EnoughClientsToPlay
|
|
================
|
|
*/
|
|
bool idMultiplayerGame::EnoughClientsToPlay()
|
|
{
|
|
int teamCount[ 2 ];
|
|
int clients = NumActualClients( false, teamCount );
|
|
if( IsGametypeTeamBased() ) /* CTF */
|
|
{
|
|
return clients >= 2 && teamCount[ 0 ] && teamCount[ 1 ];
|
|
}
|
|
else
|
|
{
|
|
return clients >= 2;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::FragLimitHit
|
|
return the winning player (team player)
|
|
if there is no FragLeader(), the game is tied and we return NULL
|
|
================
|
|
*/
|
|
idPlayer* idMultiplayerGame::FragLimitHit()
|
|
{
|
|
int i;
|
|
int fragLimit = gameLocal.serverInfo.GetInt( "si_fragLimit" );
|
|
idPlayer* leader;
|
|
|
|
if( IsGametypeFlagBased() ) /* CTF */
|
|
return NULL;
|
|
|
|
leader = FragLeader();
|
|
if( !leader )
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
if( fragLimit <= 0 )
|
|
{
|
|
fragLimit = MP_PLAYER_MAXFRAGS;
|
|
}
|
|
|
|
if( gameLocal.gameType == GAME_LASTMAN )
|
|
{
|
|
// we have a leader, check if any other players have frags left
|
|
assert( !static_cast< idPlayer* >( leader )->lastManOver );
|
|
for( i = 0 ; i < gameLocal.numClients ; i++ )
|
|
{
|
|
idEntity* ent = gameLocal.entities[ i ];
|
|
if( !ent || !ent->IsType( idPlayer::Type ) )
|
|
{
|
|
continue;
|
|
}
|
|
if( !CanPlay( static_cast< idPlayer* >( ent ) ) )
|
|
{
|
|
continue;
|
|
}
|
|
if( ent == leader )
|
|
{
|
|
continue;
|
|
}
|
|
if( playerState[ ent->entityNumber ].fragCount > 0 )
|
|
{
|
|
return NULL;
|
|
}
|
|
}
|
|
// there is a leader, his score may even be negative, but no one else has frags left or is !lastManOver
|
|
return leader;
|
|
}
|
|
else if( IsGametypeTeamBased() ) /* CTF */
|
|
{
|
|
if( playerState[ leader->entityNumber ].teamFragCount >= fragLimit )
|
|
{
|
|
return leader;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if( playerState[ leader->entityNumber ].fragCount >= fragLimit )
|
|
{
|
|
return leader;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::TimeLimitHit
|
|
================
|
|
*/
|
|
bool idMultiplayerGame::TimeLimitHit()
|
|
{
|
|
int timeLimit = gameLocal.serverInfo.GetInt( "si_timeLimit" );
|
|
if( timeLimit )
|
|
{
|
|
if( gameLocal.serverTime >= matchStartedTime + timeLimit * 60000 )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::WinningTeam
|
|
return winning team
|
|
-1 if tied or no players
|
|
================
|
|
*/
|
|
int idMultiplayerGame::WinningTeam()
|
|
{
|
|
if( teamPoints[0] > teamPoints[1] )
|
|
return 0;
|
|
if( teamPoints[0] < teamPoints[1] )
|
|
return 1;
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::PointLimitHit
|
|
================
|
|
*/
|
|
bool idMultiplayerGame::PointLimitHit()
|
|
{
|
|
int pointLimit = gameLocal.serverInfo.GetInt( "si_fragLimit" );
|
|
|
|
// default to MP_CTF_MAXPOINTS if needed
|
|
if( pointLimit > MP_CTF_MAXPOINTS )
|
|
pointLimit = MP_CTF_MAXPOINTS;
|
|
else if( pointLimit <= 0 )
|
|
pointLimit = MP_CTF_MAXPOINTS;
|
|
|
|
if( teamPoints[0] == teamPoints[1] )
|
|
return false;
|
|
|
|
if( teamPoints[0] >= pointLimit ||
|
|
teamPoints[1] >= pointLimit )
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::FragLeader
|
|
return the current winner ( or a player from the winning team )
|
|
NULL if even
|
|
================
|
|
*/
|
|
idPlayer* idMultiplayerGame::FragLeader()
|
|
{
|
|
int i;
|
|
int frags[ MAX_CLIENTS ];
|
|
idPlayer* leader = NULL;
|
|
idEntity* ent;
|
|
idPlayer* p;
|
|
int high = -9999;
|
|
int count = 0;
|
|
bool teamLead[ 2 ] = { false, false };
|
|
|
|
for( i = 0 ; i < gameLocal.numClients ; i++ )
|
|
{
|
|
ent = gameLocal.entities[ i ];
|
|
if( !ent || !ent->IsType( idPlayer::Type ) )
|
|
{
|
|
continue;
|
|
}
|
|
if( !CanPlay( static_cast< idPlayer* >( ent ) ) )
|
|
{
|
|
continue;
|
|
}
|
|
if( gameLocal.gameType == GAME_TOURNEY && ent->entityNumber != currentTourneyPlayer[ 0 ] && ent->entityNumber != currentTourneyPlayer[ 1 ] )
|
|
{
|
|
continue;
|
|
}
|
|
if( static_cast< idPlayer* >( ent )->lastManOver )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
int fragc = ( IsGametypeTeamBased() ) ? playerState[i].teamFragCount : playerState[i].fragCount; /* CTF */
|
|
if( fragc > high )
|
|
{
|
|
high = fragc;
|
|
}
|
|
|
|
frags[ i ] = fragc;
|
|
}
|
|
|
|
for( i = 0; i < gameLocal.numClients; i++ )
|
|
{
|
|
ent = gameLocal.entities[ i ];
|
|
if( !ent || !ent->IsType( idPlayer::Type ) )
|
|
{
|
|
continue;
|
|
}
|
|
p = static_cast< idPlayer* >( ent );
|
|
p->SetLeader( false );
|
|
|
|
if( !CanPlay( p ) )
|
|
{
|
|
continue;
|
|
}
|
|
if( gameLocal.gameType == GAME_TOURNEY && ent->entityNumber != currentTourneyPlayer[ 0 ] && ent->entityNumber != currentTourneyPlayer[ 1 ] )
|
|
{
|
|
continue;
|
|
}
|
|
if( p->lastManOver )
|
|
{
|
|
continue;
|
|
}
|
|
if( p->spectating )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if( frags[ i ] >= high )
|
|
{
|
|
leader = p;
|
|
count++;
|
|
p->SetLeader( true );
|
|
if( IsGametypeTeamBased() ) /* CTF */
|
|
{
|
|
teamLead[ p->team ] = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( !IsGametypeTeamBased() ) /* CTF */
|
|
{
|
|
// more than one player at the highest frags
|
|
if( count > 1 )
|
|
{
|
|
return NULL;
|
|
}
|
|
else
|
|
{
|
|
return leader;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if( teamLead[ 0 ] && teamLead[ 1 ] )
|
|
{
|
|
// even game in team play
|
|
return NULL;
|
|
}
|
|
return leader;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idGameLocal::UpdateWinsLosses
|
|
================
|
|
*/
|
|
void idMultiplayerGame::UpdateWinsLosses( idPlayer* winner )
|
|
{
|
|
if( winner )
|
|
{
|
|
// run back through and update win/loss count
|
|
for( int i = 0; i < gameLocal.numClients; i++ )
|
|
{
|
|
idEntity* ent = gameLocal.entities[ i ];
|
|
if( !ent || !ent->IsType( idPlayer::Type ) )
|
|
{
|
|
continue;
|
|
}
|
|
idPlayer* player = static_cast<idPlayer*>( ent );
|
|
if( IsGametypeTeamBased() ) /* CTF */
|
|
{
|
|
if( player == winner || ( player != winner && player->team == winner->team ) )
|
|
{
|
|
playerState[ i ].wins++;
|
|
PlayGlobalSound( i, SND_YOUWIN );
|
|
}
|
|
else
|
|
{
|
|
PlayGlobalSound( i, SND_YOULOSE );
|
|
}
|
|
}
|
|
else if( gameLocal.gameType == GAME_LASTMAN )
|
|
{
|
|
if( player == winner )
|
|
{
|
|
playerState[ i ].wins++;
|
|
PlayGlobalSound( i, SND_YOUWIN );
|
|
}
|
|
else if( !player->wantSpectate )
|
|
{
|
|
PlayGlobalSound( i, SND_YOULOSE );
|
|
}
|
|
}
|
|
else if( gameLocal.gameType == GAME_TOURNEY )
|
|
{
|
|
if( player == winner )
|
|
{
|
|
playerState[ i ].wins++;
|
|
PlayGlobalSound( i, SND_YOUWIN );
|
|
}
|
|
else if( i == currentTourneyPlayer[ 0 ] || i == currentTourneyPlayer[ 1 ] )
|
|
{
|
|
PlayGlobalSound( i, SND_YOULOSE );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if( player == winner )
|
|
{
|
|
playerState[i].wins++;
|
|
PlayGlobalSound( i, SND_YOUWIN );
|
|
}
|
|
else if( !player->wantSpectate )
|
|
{
|
|
PlayGlobalSound( i, SND_YOULOSE );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if( IsGametypeFlagBased() ) /* CTF */
|
|
{
|
|
int winteam = WinningTeam();
|
|
|
|
if( winteam != -1 ) // TODO : print a message telling it why the hell the game ended with no winning team?
|
|
for( int i = 0; i < gameLocal.numClients; i++ )
|
|
{
|
|
idEntity* ent = gameLocal.entities[ i ];
|
|
if( !ent || !ent->IsType( idPlayer::Type ) )
|
|
{
|
|
continue;
|
|
}
|
|
idPlayer* player = static_cast<idPlayer*>( ent );
|
|
|
|
if( player->team == winteam )
|
|
{
|
|
PlayGlobalSound( i, SND_YOUWIN );
|
|
playerState[ i ].wins++;
|
|
}
|
|
else
|
|
{
|
|
PlayGlobalSound( i, SND_YOULOSE );
|
|
}
|
|
}
|
|
}
|
|
|
|
if( winner )
|
|
{
|
|
lastWinner = winner->entityNumber;
|
|
}
|
|
else
|
|
{
|
|
lastWinner = -1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::TeamScoreCTF
|
|
================
|
|
*/
|
|
void idMultiplayerGame::TeamScoreCTF( int team, int delta )
|
|
{
|
|
if( team < 0 || team > 1 )
|
|
return;
|
|
|
|
teamPoints[team] += delta;
|
|
|
|
if( gameState == GAMEON || gameState == SUDDENDEATH )
|
|
PrintMessageEvent( MSG_SCOREUPDATE, teamPoints[0], teamPoints[1] );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::PlayerScoreCTF
|
|
================
|
|
*/
|
|
void idMultiplayerGame::PlayerScoreCTF( int playerIdx, int delta )
|
|
{
|
|
if( playerIdx < 0 || playerIdx >= MAX_CLIENTS )
|
|
return;
|
|
|
|
playerState[ playerIdx ].fragCount += delta;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::GetFlagCarrier
|
|
================
|
|
*/
|
|
int idMultiplayerGame::GetFlagCarrier( int team )
|
|
{
|
|
int iFlagCarrier = -1;
|
|
|
|
for( int i = 0; i < gameLocal.numClients; i++ )
|
|
{
|
|
idEntity* ent = gameLocal.entities[ i ];
|
|
if( !ent || !ent->IsType( idPlayer::Type ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
idPlayer* player = static_cast<idPlayer*>( ent );
|
|
if( player->team != team )
|
|
continue;
|
|
|
|
if( player->carryingFlag )
|
|
{
|
|
if( iFlagCarrier != -1 )
|
|
gameLocal.Warning( "BUG: more than one flag carrier on %s team", team == 0 ? "red" : "blue" );
|
|
iFlagCarrier = i;
|
|
}
|
|
}
|
|
|
|
return iFlagCarrier;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::TeamScore
|
|
================
|
|
*/
|
|
void idMultiplayerGame::TeamScore( int entityNumber, int team, int delta )
|
|
{
|
|
playerState[ entityNumber ].fragCount += delta;
|
|
for( int i = 0 ; i < gameLocal.numClients ; i++ )
|
|
{
|
|
idEntity* ent = gameLocal.entities[ i ];
|
|
if( !ent || !ent->IsType( idPlayer::Type ) )
|
|
{
|
|
continue;
|
|
}
|
|
idPlayer* player = static_cast<idPlayer*>( ent );
|
|
if( player->team == team )
|
|
{
|
|
playerState[ player->entityNumber ].teamFragCount += delta;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::PlayerDeath
|
|
================
|
|
*/
|
|
void idMultiplayerGame::PlayerDeath( idPlayer* dead, idPlayer* killer, bool telefrag )
|
|
{
|
|
|
|
// don't do PrintMessageEvent and shit
|
|
assert( !common->IsClient() );
|
|
|
|
if( gameState == COUNTDOWN || gameState == WARMUP )
|
|
{
|
|
// No Kill scores are gained during warmup.
|
|
return;
|
|
}
|
|
|
|
if( dead == NULL )
|
|
{
|
|
idLib::Warning( "idMultiplayerGame::PlayerDeath dead ptr == NULL, kill will not count" );
|
|
return;
|
|
}
|
|
|
|
playerState[ dead->entityNumber ].deaths++;
|
|
|
|
if( killer )
|
|
{
|
|
if( gameLocal.gameType == GAME_LASTMAN )
|
|
{
|
|
playerState[ dead->entityNumber ].fragCount--;
|
|
|
|
}
|
|
else if( IsGametypeTeamBased() ) /* CTF */
|
|
{
|
|
if( killer == dead || killer->team == dead->team )
|
|
{
|
|
// suicide or teamkill
|
|
TeamScore( killer->entityNumber, killer->team, -1 );
|
|
}
|
|
else
|
|
{
|
|
TeamScore( killer->entityNumber, killer->team, +1 );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
playerState[ killer->entityNumber ].fragCount += ( killer == dead ) ? -1 : 1;
|
|
}
|
|
}
|
|
|
|
if( killer && killer == dead )
|
|
{
|
|
PrintMessageEvent( MSG_SUICIDE, dead->entityNumber );
|
|
}
|
|
else if( killer )
|
|
{
|
|
if( telefrag )
|
|
{
|
|
killer->GetAchievementManager().EventCompletesAchievement( ACHIEVEMENT_MP_KILL_PLAYER_VIA_TELEPORT );
|
|
PrintMessageEvent( MSG_TELEFRAGGED, dead->entityNumber, killer->entityNumber );
|
|
}
|
|
else if( IsGametypeTeamBased() && dead->team == killer->team ) /* CTF */
|
|
{
|
|
PrintMessageEvent( MSG_KILLEDTEAM, dead->entityNumber, killer->entityNumber );
|
|
}
|
|
else
|
|
{
|
|
if( killer->PowerUpActive( INVISIBILITY ) )
|
|
{
|
|
killer->GetAchievementManager().EventCompletesAchievement( ACHIEVEMENT_MP_KILL_5_PLAYERS_USING_INVIS );
|
|
}
|
|
if( killer->PowerUpActive( BERSERK ) )
|
|
{
|
|
killer->GetAchievementManager().EventCompletesAchievement( ACHIEVEMENT_MP_USE_BERSERK_TO_KILL_PLAYER );
|
|
}
|
|
PrintMessageEvent( MSG_KILLED, dead->entityNumber, killer->entityNumber );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
PrintMessageEvent( MSG_DIED, dead->entityNumber );
|
|
playerState[ dead->entityNumber ].fragCount--;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::PlayerStats
|
|
================
|
|
*/
|
|
void idMultiplayerGame::PlayerStats( int clientNum, char* data, const int len )
|
|
{
|
|
|
|
idEntity* ent;
|
|
int team;
|
|
|
|
*data = 0;
|
|
|
|
// make sure we don't exceed the client list
|
|
if( clientNum < 0 || clientNum > gameLocal.numClients )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// find which team this player is on
|
|
ent = gameLocal.entities[ clientNum ];
|
|
if( ent && ent->IsType( idPlayer::Type ) )
|
|
{
|
|
team = static_cast< idPlayer* >( ent )->team;
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
|
|
idStr::snPrintf( data, len, "team=%d score=%ld tks=%ld", team, playerState[ clientNum ].fragCount, playerState[ clientNum ].teamFragCount );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::DumpTourneyLine
|
|
================
|
|
*/
|
|
void idMultiplayerGame::DumpTourneyLine()
|
|
{
|
|
int i;
|
|
for( i = 0; i < gameLocal.numClients; i++ )
|
|
{
|
|
if( gameLocal.entities[ i ] && gameLocal.entities[ i ]->IsType( idPlayer::Type ) )
|
|
{
|
|
common->Printf( "client %d: rank %d\n", i, static_cast< idPlayer* >( gameLocal.entities[ i ] )->tourneyRank );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::NewState
|
|
================
|
|
*/
|
|
void idMultiplayerGame::NewState( gameState_t news, idPlayer* player )
|
|
{
|
|
idBitMsg outMsg;
|
|
byte msgBuf[MAX_GAME_MESSAGE_SIZE];
|
|
int i;
|
|
|
|
assert( news != gameState );
|
|
assert( !common->IsClient() );
|
|
assert( news < STATE_COUNT );
|
|
gameLocal.DPrintf( "%s -> %s\n", GameStateStrings[ gameState ], GameStateStrings[ news ] );
|
|
switch( news )
|
|
{
|
|
case GAMEON:
|
|
{
|
|
gameLocal.LocalMapRestart();
|
|
outMsg.InitWrite( msgBuf, sizeof( msgBuf ) );
|
|
outMsg.WriteBits( 0, 1 );
|
|
session->GetActingGameStateLobbyBase().SendReliable( GAME_RELIABLE_MESSAGE_RESTART, outMsg, false );
|
|
|
|
teamPoints[0] = 0;
|
|
teamPoints[1] = 0;
|
|
|
|
PlayGlobalSound( -1, SND_FIGHT );
|
|
matchStartedTime = gameLocal.serverTime;
|
|
|
|
idBitMsg matchStartedTimeMsg;
|
|
byte matchStartedTimeMsgBuf[ sizeof( matchStartedTime ) ];
|
|
matchStartedTimeMsg.InitWrite( matchStartedTimeMsgBuf, sizeof( matchStartedTimeMsgBuf ) );
|
|
matchStartedTimeMsg.WriteLong( matchStartedTime );
|
|
session->GetActingGameStateLobbyBase().SendReliable( GAME_RELIABLE_MESSAGE_MATCH_STARTED_TIME, matchStartedTimeMsg, false );
|
|
|
|
fragLimitTimeout = 0;
|
|
for( i = 0; i < gameLocal.numClients; i++ )
|
|
{
|
|
idEntity* ent = gameLocal.entities[ i ];
|
|
if( !ent || !ent->IsType( idPlayer::Type ) )
|
|
{
|
|
continue;
|
|
}
|
|
idPlayer* p = static_cast<idPlayer*>( ent );
|
|
p->wantSpectate = false; // Make sure everyone is in the game.
|
|
p->SetLeader( false ); // don't carry the flag from previous games
|
|
if( gameLocal.gameType == GAME_TOURNEY && currentTourneyPlayer[ 0 ] != i && currentTourneyPlayer[ 1 ] != i )
|
|
{
|
|
p->ServerSpectate( true );
|
|
idLib::Printf( "TOURNEY NewState GAMEON :> Player %d Benched \n", p->entityNumber );
|
|
p->tourneyRank++;
|
|
}
|
|
else
|
|
{
|
|
int fragLimit = gameLocal.serverInfo.GetInt( "si_fragLimit" );
|
|
int startingCount = ( gameLocal.gameType == GAME_LASTMAN ) ? fragLimit : 0;
|
|
playerState[ i ].fragCount = startingCount;
|
|
playerState[ i ].teamFragCount = startingCount;
|
|
if( !static_cast<idPlayer*>( ent )->wantSpectate )
|
|
{
|
|
static_cast<idPlayer*>( ent )->ServerSpectate( false );
|
|
idLib::Printf( "TOURNEY NewState :> Player %d On Deck \n", ent->entityNumber );
|
|
if( gameLocal.gameType == GAME_TOURNEY )
|
|
{
|
|
p->tourneyRank = 0;
|
|
}
|
|
}
|
|
}
|
|
if( CanPlay( p ) )
|
|
{
|
|
p->lastManPresent = true;
|
|
}
|
|
else
|
|
{
|
|
p->lastManPresent = false;
|
|
}
|
|
}
|
|
NewState_GameOn_ServerAndClient();
|
|
break;
|
|
}
|
|
case GAMEREVIEW:
|
|
{
|
|
SetFlagMsg( false );
|
|
nextState = INACTIVE; // used to abort a game. cancel out any upcoming state change
|
|
// set all players spectating
|
|
for( i = 0; i < gameLocal.numClients; i++ )
|
|
{
|
|
idEntity* ent = gameLocal.entities[ i ];
|
|
if( !ent || !ent->IsType( idPlayer::Type ) )
|
|
{
|
|
continue;
|
|
}
|
|
static_cast<idPlayer*>( ent )->ServerSpectate( true );
|
|
idLib::Printf( "TOURNEY NewState GAMEREVIEW :> Player %d Benched \n", ent->entityNumber );
|
|
}
|
|
UpdateWinsLosses( player );
|
|
SetFlagMsg( true );
|
|
NewState_GameReview_ServerAndClient();
|
|
break;
|
|
}
|
|
case SUDDENDEATH:
|
|
{
|
|
PrintMessageEvent( MSG_SUDDENDEATH );
|
|
PlayGlobalSound( -1, SND_SUDDENDEATH );
|
|
break;
|
|
}
|
|
case COUNTDOWN:
|
|
{
|
|
idBitMsg outMsg;
|
|
byte msgBuf[ 128 ];
|
|
|
|
warmupEndTime = gameLocal.serverTime + 1000 * cvarSystem->GetCVarInteger( "g_countDown" );
|
|
|
|
outMsg.InitWrite( msgBuf, sizeof( msgBuf ) );
|
|
outMsg.WriteLong( warmupEndTime );
|
|
session->GetActingGameStateLobbyBase().SendReliable( GAME_RELIABLE_MESSAGE_WARMUPTIME, outMsg, false );
|
|
|
|
// Reset all the scores.
|
|
for( i = 0; i < gameLocal.numClients; i++ )
|
|
{
|
|
|
|
int fragLimit = gameLocal.serverInfo.GetInt( "si_fragLimit" );
|
|
int startingCount = ( gameLocal.gameType == GAME_LASTMAN ) ? fragLimit : 0;
|
|
playerState[ i ].fragCount = startingCount;
|
|
playerState[ i ].teamFragCount = startingCount;
|
|
playerState[ i ].deaths = 0;
|
|
|
|
}
|
|
|
|
NewState_Countdown_ServerAndClient();
|
|
break;
|
|
}
|
|
case WARMUP:
|
|
{
|
|
teamPoints[0] = 0;
|
|
teamPoints[1] = 0;
|
|
if( IsGametypeFlagBased() )
|
|
{
|
|
// reset player scores to zero, only required for CTF
|
|
for( i = 0; i < gameLocal.numClients; i++ )
|
|
{
|
|
idEntity* ent = gameLocal.entities[ i ];
|
|
if( !ent || !ent->IsType( idPlayer::Type ) )
|
|
{
|
|
continue;
|
|
}
|
|
playerState[ i ].fragCount = 0;
|
|
}
|
|
}
|
|
NewState_Warmup_ServerAndClient();
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
gameState = news;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::NewState_Warmup_ServerAndClient
|
|
Called on both servers and clients once when the game state changes to WARMUP.
|
|
================
|
|
*/
|
|
void idMultiplayerGame::NewState_Warmup_ServerAndClient()
|
|
{
|
|
SetScoreboardActive( false );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::NewState_Countdown_ServerAndClient
|
|
Called on both servers and clients once when the game state changes to COUNTDOWN.
|
|
================
|
|
*/
|
|
void idMultiplayerGame::NewState_Countdown_ServerAndClient()
|
|
{
|
|
SetScoreboardActive( false );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::NewState_GameOn_ServerAndClient
|
|
Called on both servers and clients once when the game state changes to GAMEON.
|
|
================
|
|
*/
|
|
void idMultiplayerGame::NewState_GameOn_ServerAndClient()
|
|
{
|
|
startFragLimit = gameLocal.serverInfo.GetInt( "si_fragLimit" );
|
|
SetScoreboardActive( false );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::NewState_GameReview_ServerAndClient
|
|
Called on both servers and clients once when the game state changes to GAMEREVIEW.
|
|
================
|
|
*/
|
|
void idMultiplayerGame::NewState_GameReview_ServerAndClient()
|
|
{
|
|
SetScoreboardActive( true );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::FillTourneySlots
|
|
NOTE: called each frame during warmup to keep the tourney slots filled
|
|
================
|
|
*/
|
|
void idMultiplayerGame::FillTourneySlots( )
|
|
{
|
|
int i, j, rankmax, rankmaxindex;
|
|
idEntity* ent;
|
|
idPlayer* p;
|
|
|
|
idLib::Printf( "TOURNEY :> Executing FillTourneySlots \n" );
|
|
|
|
// fill up the slots based on tourney ranks
|
|
for( i = 0; i < 2; i++ )
|
|
{
|
|
if( currentTourneyPlayer[ i ] != -1 )
|
|
{
|
|
continue;
|
|
}
|
|
rankmax = -1;
|
|
rankmaxindex = -1;
|
|
for( j = 0; j < gameLocal.numClients; j++ )
|
|
{
|
|
ent = gameLocal.entities[ j ];
|
|
if( !ent || !ent->IsType( idPlayer::Type ) )
|
|
{
|
|
continue;
|
|
}
|
|
if( currentTourneyPlayer[ 0 ] == j || currentTourneyPlayer[ 1 ] == j )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
p = static_cast< idPlayer* >( ent );
|
|
if( p->wantSpectate )
|
|
{
|
|
idLib::Printf( "FillTourneySlots: Skipping Player %d ( Wants Spectate )\n", p->entityNumber );
|
|
continue;
|
|
}
|
|
|
|
|
|
if( p->tourneyRank >= rankmax )
|
|
{
|
|
// when ranks are equal, use time in game
|
|
if( p->tourneyRank == rankmax )
|
|
{
|
|
assert( rankmaxindex >= 0 );
|
|
if( p->spawnedTime > static_cast< idPlayer* >( gameLocal.entities[ rankmaxindex ] )->spawnedTime )
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
rankmax = static_cast< idPlayer* >( ent )->tourneyRank;
|
|
rankmaxindex = j;
|
|
}
|
|
}
|
|
currentTourneyPlayer[ i ] = rankmaxindex; // may be -1 if we found nothing
|
|
}
|
|
|
|
idLib::Printf( "TOURNEY :> Player 1: %d Player 2: %d \n", currentTourneyPlayer[ 0 ], currentTourneyPlayer[ 1 ] );
|
|
|
|
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::UpdateTourneyLine
|
|
we manipulate tourneyRank on player entities for internal ranking. it's easier to deal with.
|
|
but we need a real wait list to be synced down to clients for GUI
|
|
ignore current players, ignore wantSpectate
|
|
================
|
|
*/
|
|
void idMultiplayerGame::UpdateTourneyLine()
|
|
{
|
|
assert( !common->IsClient() );
|
|
if( gameLocal.gameType != GAME_TOURNEY )
|
|
{
|
|
return;
|
|
}
|
|
|
|
int globalmax = -1;
|
|
for( int j = 1; j <= gameLocal.numClients; j++ )
|
|
{
|
|
int max = -1;
|
|
int imax = -1;
|
|
for( int i = 0; i < gameLocal.numClients; i++ )
|
|
{
|
|
if( currentTourneyPlayer[ 0 ] == i || currentTourneyPlayer[ 1 ] == i )
|
|
{
|
|
continue;
|
|
}
|
|
idPlayer* p = static_cast< idPlayer* >( gameLocal.entities[ i ] );
|
|
if( !p || p->wantSpectate )
|
|
{
|
|
continue;
|
|
}
|
|
if( p->tourneyRank > max && ( globalmax == -1 || p->tourneyRank < globalmax ) )
|
|
{
|
|
max = p->tourneyRank;
|
|
imax = i;
|
|
}
|
|
}
|
|
if( imax == -1 )
|
|
{
|
|
break;
|
|
}
|
|
|
|
idBitMsg outMsg;
|
|
byte msgBuf[1024];
|
|
outMsg.InitWrite( msgBuf, sizeof( msgBuf ) );
|
|
outMsg.WriteByte( j );
|
|
session->GetActingGameStateLobbyBase().SendReliableToLobbyUser( gameLocal.lobbyUserIDs[imax], GAME_RELIABLE_MESSAGE_TOURNEYLINE, outMsg );
|
|
|
|
globalmax = max;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::CycleTourneyPlayers
|
|
================
|
|
*/
|
|
void idMultiplayerGame::CycleTourneyPlayers( )
|
|
{
|
|
int i;
|
|
idEntity* ent;
|
|
idPlayer* player;
|
|
|
|
currentTourneyPlayer[ 0 ] = -1;
|
|
currentTourneyPlayer[ 1 ] = -1;
|
|
// if any, winner from last round will play again
|
|
if( lastWinner != -1 )
|
|
{
|
|
idEntity* ent = gameLocal.entities[ lastWinner ];
|
|
if( ent && ent->IsType( idPlayer::Type ) )
|
|
{
|
|
currentTourneyPlayer[ 0 ] = lastWinner;
|
|
}
|
|
}
|
|
FillTourneySlots( );
|
|
// force selected players in/out of the game and update the ranks
|
|
for( i = 0 ; i < gameLocal.numClients ; i++ )
|
|
{
|
|
if( currentTourneyPlayer[ 0 ] == i || currentTourneyPlayer[ 1 ] == i )
|
|
{
|
|
player = static_cast<idPlayer*>( gameLocal.entities[ i ] );
|
|
player->ServerSpectate( false );
|
|
idLib::Printf( "TOURNEY CycleTourneyPlayers:> Player %d On Deck \n", player->entityNumber );
|
|
}
|
|
else
|
|
{
|
|
ent = gameLocal.entities[ i ];
|
|
if( ent && ent->IsType( idPlayer::Type ) )
|
|
{
|
|
player = static_cast<idPlayer*>( gameLocal.entities[ i ] );
|
|
player->ServerSpectate( true );
|
|
idLib::Printf( "TOURNEY CycleTourneyPlayers:> Player %d Benched \n", player->entityNumber );
|
|
}
|
|
}
|
|
}
|
|
UpdateTourneyLine();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::Warmup
|
|
================
|
|
*/
|
|
bool idMultiplayerGame::Warmup()
|
|
{
|
|
return ( gameState == WARMUP );
|
|
}
|
|
|
|
|
|
void idMultiplayerGame::GameHasBeenWon()
|
|
{
|
|
|
|
|
|
// Only allow leaderboard submissions within public games.
|
|
const idMatchParameters& matchParameters = session->GetActingGameStateLobbyBase().GetMatchParms();
|
|
|
|
if( matchParameters.matchFlags & MATCH_RANKED )
|
|
{
|
|
|
|
// Upload all player's scores to the leaderboard.
|
|
for( int playerIdx = 0; playerIdx < gameLocal.numClients; playerIdx++ )
|
|
{
|
|
|
|
leaderboardStats_t stats = { playerState[ playerIdx ].fragCount, playerState[ playerIdx ].wins, playerState[ playerIdx ].teamFragCount, playerState[ playerIdx].deaths };
|
|
|
|
LeaderboardLocal_Upload( gameLocal.lobbyUserIDs[ playerIdx ], gameLocal.gameType, stats );
|
|
}
|
|
|
|
// Flush all the collectively queued leaderboards all at once. ( Otherwise you get a busy signal on the second "flush" )
|
|
session->LeaderboardFlush();
|
|
}
|
|
|
|
// Award Any players that have not died. An Achievement
|
|
for( int i = 0; i < gameLocal.numClients; i++ )
|
|
{
|
|
idPlayer* player = static_cast< idPlayer* >( gameLocal.entities[ i ] );
|
|
if( player == NULL )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Only validate players that were in the tourney.
|
|
if( gameLocal.gameType == GAME_TOURNEY )
|
|
{
|
|
if( i != currentTourneyPlayer[ 0 ] && i != currentTourneyPlayer[ 1 ] )
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// In LMS, players that join mid-game will not have ever died
|
|
if( gameLocal.gameType == GAME_LASTMAN )
|
|
{
|
|
if( playerState[i].fragCount == LASTMAN_NOLIVES )
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if( playerState[ i ].deaths == 0 )
|
|
{
|
|
player->GetAchievementManager().EventCompletesAchievement( ACHIEVEMENT_MP_COMPLETE_MATCH_WITHOUT_DYING );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::Run
|
|
================
|
|
*/
|
|
void idMultiplayerGame::Run()
|
|
{
|
|
int i, timeLeft;
|
|
idPlayer* player;
|
|
int gameReviewPause;
|
|
|
|
assert( common->IsMultiplayer() );
|
|
assert( !common->IsClient() );
|
|
|
|
pureReady = true;
|
|
|
|
if( gameState == INACTIVE )
|
|
{
|
|
NewState( WARMUP );
|
|
}
|
|
|
|
CheckRespawns();
|
|
|
|
if( nextState != INACTIVE && gameLocal.serverTime > nextStateSwitch )
|
|
{
|
|
NewState( nextState );
|
|
nextState = INACTIVE;
|
|
}
|
|
|
|
idLobbyBase& lobby = session->GetActingGameStateLobbyBase();
|
|
for( i = 0; i < gameLocal.numClients; i++ )
|
|
{
|
|
idPlayer* player = static_cast<idPlayer*>( gameLocal.entities[i] );
|
|
if( player != NULL )
|
|
{
|
|
playerState[i].ping = lobby.GetLobbyUserQoS( gameLocal.lobbyUserIDs[i] );
|
|
}
|
|
}
|
|
|
|
switch( gameState )
|
|
{
|
|
case GAMEREVIEW:
|
|
{
|
|
if( nextState == INACTIVE )
|
|
{
|
|
gameReviewPause = cvarSystem->GetCVarInteger( "g_gameReviewPause" );
|
|
nextState = NEXTGAME;
|
|
nextStateSwitch = gameLocal.serverTime + 1000 * gameReviewPause;
|
|
}
|
|
break;
|
|
}
|
|
case NEXTGAME:
|
|
{
|
|
if( nextState == INACTIVE )
|
|
{
|
|
// make sure flags are returned
|
|
if( IsGametypeFlagBased() )
|
|
{
|
|
idItemTeam* flag;
|
|
flag = GetTeamFlag( 0 );
|
|
if( flag )
|
|
{
|
|
flag->Return();
|
|
}
|
|
flag = GetTeamFlag( 1 );
|
|
if( flag )
|
|
{
|
|
flag->Return();
|
|
}
|
|
}
|
|
NewState( WARMUP );
|
|
if( gameLocal.gameType == GAME_TOURNEY )
|
|
{
|
|
CycleTourneyPlayers();
|
|
}
|
|
// put everyone back in from endgame spectate
|
|
for( i = 0; i < gameLocal.numClients; i++ )
|
|
{
|
|
idEntity* ent = gameLocal.entities[ i ];
|
|
if( ent && ent->IsType( idPlayer::Type ) )
|
|
{
|
|
if( !static_cast< idPlayer* >( ent )->wantSpectate )
|
|
{
|
|
CheckRespawns( static_cast<idPlayer*>( ent ) );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case WARMUP:
|
|
{
|
|
if( EnoughClientsToPlay() )
|
|
{
|
|
NewState( COUNTDOWN );
|
|
nextState = GAMEON;
|
|
nextStateSwitch = gameLocal.serverTime + 1000 * cvarSystem->GetCVarInteger( "g_countDown" );
|
|
}
|
|
one = two = three = false;
|
|
break;
|
|
}
|
|
case COUNTDOWN:
|
|
{
|
|
timeLeft = ( nextStateSwitch - gameLocal.serverTime ) / 1000 + 1;
|
|
if( timeLeft == 3 && !three )
|
|
{
|
|
PlayGlobalSound( -1, SND_THREE );
|
|
three = true;
|
|
}
|
|
else if( timeLeft == 2 && !two )
|
|
{
|
|
PlayGlobalSound( -1, SND_TWO );
|
|
two = true;
|
|
}
|
|
else if( timeLeft == 1 && !one )
|
|
{
|
|
PlayGlobalSound( -1, SND_ONE );
|
|
one = true;
|
|
}
|
|
break;
|
|
}
|
|
case GAMEON:
|
|
{
|
|
if( IsGametypeFlagBased() ) /* CTF */
|
|
{
|
|
// totally different logic branch for CTF
|
|
if( PointLimitHit() )
|
|
{
|
|
int team = WinningTeam();
|
|
assert( team != -1 );
|
|
|
|
NewState( GAMEREVIEW, NULL );
|
|
PrintMessageEvent( MSG_POINTLIMIT, team );
|
|
GameHasBeenWon();
|
|
|
|
}
|
|
else if( TimeLimitHit() )
|
|
{
|
|
int team = WinningTeam();
|
|
if( EnoughClientsToPlay() && team == -1 )
|
|
{
|
|
NewState( SUDDENDEATH );
|
|
}
|
|
else
|
|
{
|
|
NewState( GAMEREVIEW, NULL );
|
|
PrintMessageEvent( MSG_TIMELIMIT );
|
|
GameHasBeenWon();
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
player = FragLimitHit();
|
|
if( player )
|
|
{
|
|
// delay between detecting frag limit and ending game. let the death anims play
|
|
if( !fragLimitTimeout )
|
|
{
|
|
common->DPrintf( "enter FragLimit timeout, player %d is leader\n", player->entityNumber );
|
|
fragLimitTimeout = gameLocal.serverTime + FRAGLIMIT_DELAY;
|
|
}
|
|
if( gameLocal.serverTime > fragLimitTimeout )
|
|
{
|
|
NewState( GAMEREVIEW, player );
|
|
PrintMessageEvent( MSG_FRAGLIMIT, IsGametypeTeamBased() ? player->team : player->entityNumber );
|
|
GameHasBeenWon();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if( fragLimitTimeout )
|
|
{
|
|
// frag limit was hit and cancelled. means the two teams got even during FRAGLIMIT_DELAY
|
|
// enter sudden death, the next frag leader will win
|
|
SuddenRespawn();
|
|
PrintMessageEvent( MSG_HOLYSHIT );
|
|
fragLimitTimeout = 0;
|
|
NewState( SUDDENDEATH );
|
|
}
|
|
else if( TimeLimitHit() )
|
|
{
|
|
player = FragLeader();
|
|
if( !player )
|
|
{
|
|
NewState( SUDDENDEATH );
|
|
}
|
|
else
|
|
{
|
|
NewState( GAMEREVIEW, player );
|
|
PrintMessageEvent( MSG_TIMELIMIT );
|
|
GameHasBeenWon();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
BalanceTeams();
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case SUDDENDEATH:
|
|
{
|
|
if( IsGametypeFlagBased() ) /* CTF */
|
|
{
|
|
int team = WinningTeam();
|
|
if( team != -1 )
|
|
{
|
|
// TODO : implement pointLimitTimeout
|
|
NewState( GAMEREVIEW, NULL );
|
|
PrintMessageEvent( MSG_POINTLIMIT, team );
|
|
GameHasBeenWon();
|
|
}
|
|
break;
|
|
}
|
|
|
|
player = FragLeader();
|
|
if( player )
|
|
{
|
|
if( !fragLimitTimeout )
|
|
{
|
|
common->DPrintf( "enter sudden death FragLeader timeout, player %d is leader\n", player->entityNumber );
|
|
fragLimitTimeout = gameLocal.serverTime + FRAGLIMIT_DELAY;
|
|
}
|
|
if( gameLocal.serverTime > fragLimitTimeout )
|
|
{
|
|
NewState( GAMEREVIEW, player );
|
|
PrintMessageEvent( MSG_FRAGLIMIT, IsGametypeTeamBased() ? player->team : player->entityNumber );
|
|
GameHasBeenWon();
|
|
}
|
|
}
|
|
else if( fragLimitTimeout )
|
|
{
|
|
SuddenRespawn();
|
|
PrintMessageEvent( MSG_HOLYSHIT );
|
|
fragLimitTimeout = 0;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::Draw
|
|
================
|
|
*/
|
|
bool idMultiplayerGame::Draw( int clientNum )
|
|
{
|
|
idPlayer* player, *viewPlayer;
|
|
|
|
// clear the render entities for any players that don't need
|
|
// icons and which might not be thinking because they weren't in
|
|
// the last snapshot.
|
|
for( int i = 0; i < gameLocal.numClients; i++ )
|
|
{
|
|
player = static_cast<idPlayer*>( gameLocal.entities[ i ] );
|
|
if( player && !player->NeedsIcon() )
|
|
{
|
|
player->HidePlayerIcons();
|
|
}
|
|
}
|
|
|
|
player = viewPlayer = static_cast<idPlayer*>( gameLocal.entities[ clientNum ] );
|
|
|
|
if( player == NULL )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if( player->spectating )
|
|
{
|
|
viewPlayer = static_cast<idPlayer*>( gameLocal.entities[ player->spectator ] );
|
|
if( viewPlayer == NULL )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// if the player you are viewing is holding a flag, hide it.
|
|
idEntity* flag = GetTeamFlag( viewPlayer->team ? 0 : 1 );
|
|
if( flag )
|
|
{
|
|
if( viewPlayer->carryingFlag )
|
|
{
|
|
flag->Hide();
|
|
}
|
|
else
|
|
{
|
|
flag->Show();
|
|
}
|
|
}
|
|
}
|
|
|
|
UpdatePlayerRanks();
|
|
UpdateHud( viewPlayer, player->hudManager );
|
|
// use the hud of the local player
|
|
viewPlayer->playerView.RenderPlayerView( player->hudManager );
|
|
|
|
idStr spectatetext[ 2 ];
|
|
GetSpectateText( player, spectatetext, true );
|
|
|
|
if( scoreboardManager != NULL )
|
|
{
|
|
scoreboardManager->UpdateSpectating( spectatetext[0].c_str(), spectatetext[1].c_str() );
|
|
}
|
|
|
|
DrawChat( player );
|
|
DrawScoreBoard( player );
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::GetSpectateText
|
|
================
|
|
*/
|
|
void idMultiplayerGame::GetSpectateText( idPlayer* player, idStr spectatetext[ 2 ], bool scoreboard )
|
|
{
|
|
if( !player->spectating )
|
|
{
|
|
return;
|
|
}
|
|
if( player->wantSpectate && !scoreboard )
|
|
{
|
|
if( gameLocal.gameType == GAME_LASTMAN && gameState == GAMEON )
|
|
{
|
|
// If we're in GAMEON in lastman, you can't actully spawn, or you'll have an unfair
|
|
// advantage with more lives than everyone else.
|
|
spectatetext[ 0 ] = idLocalization::GetString( "#str_04246" );
|
|
spectatetext[ 0 ] += idLocalization::GetString( "#str_07003" );
|
|
}
|
|
else
|
|
{
|
|
|
|
if( gameLocal.gameType == GAME_TOURNEY &&
|
|
( currentTourneyPlayer[0] != -1 && currentTourneyPlayer[1] != -1 ) &&
|
|
( currentTourneyPlayer[0] != player->entityNumber && currentTourneyPlayer[1] != player->entityNumber ) )
|
|
{
|
|
|
|
spectatetext[ 0 ] = idLocalization::GetString( "#str_04246" );
|
|
switch( player->tourneyLine )
|
|
{
|
|
case 0:
|
|
spectatetext[ 0 ] += idLocalization::GetString( "#str_07003" );
|
|
break;
|
|
case 1:
|
|
spectatetext[ 0 ] += idLocalization::GetString( "#str_07004" );
|
|
break;
|
|
case 2:
|
|
spectatetext[ 0 ] += idLocalization::GetString( "#str_07005" );
|
|
break;
|
|
default:
|
|
spectatetext[ 0 ] += va( idLocalization::GetString( "#str_07006" ), player->tourneyLine );
|
|
break;
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
spectatetext[ 0 ] = idLocalization::GetString( "#str_respawn_message" );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if( gameLocal.gameType == GAME_TOURNEY )
|
|
{
|
|
spectatetext[ 0 ] = idLocalization::GetString( "#str_04246" );
|
|
switch( player->tourneyLine )
|
|
{
|
|
case 0:
|
|
spectatetext[ 0 ] += idLocalization::GetString( "#str_07003" );
|
|
break;
|
|
case 1:
|
|
spectatetext[ 0 ] += idLocalization::GetString( "#str_07004" );
|
|
break;
|
|
case 2:
|
|
spectatetext[ 0 ] += idLocalization::GetString( "#str_07005" );
|
|
break;
|
|
default:
|
|
spectatetext[ 0 ] += va( idLocalization::GetString( "#str_07006" ), player->tourneyLine );
|
|
break;
|
|
}
|
|
}
|
|
else if( gameLocal.gameType == GAME_LASTMAN )
|
|
{
|
|
spectatetext[ 0 ] = idLocalization::GetString( "#str_07007" );
|
|
}
|
|
else
|
|
{
|
|
spectatetext[ 0 ] = idLocalization::GetString( "#str_04246" );
|
|
}
|
|
}
|
|
if( player->spectator != player->entityNumber )
|
|
{
|
|
idLobbyBase& lobby = session->GetActingGameStateLobbyBase();
|
|
spectatetext[ 1 ] = va( idLocalization::GetString( "#str_07008" ), lobby.GetLobbyUserName( gameLocal.lobbyUserIDs[ player->spectator ] ) );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::UpdateHud
|
|
================
|
|
*/
|
|
void idMultiplayerGame::UpdateHud( idPlayer* player, idMenuHandler_HUD* hudManager )
|
|
{
|
|
|
|
if( hudManager && hudManager->GetHud() )
|
|
{
|
|
|
|
idMenuScreen_HUD* hud = hudManager->GetHud();
|
|
|
|
if( Warmup() )
|
|
{
|
|
hud->UpdateMessage( true, "#str_04251" );
|
|
|
|
if( IsGametypeTeamBased() )
|
|
{
|
|
hud->SetTeamScore( 0, 0 );
|
|
hud->SetTeamScore( 1, 0 );
|
|
}
|
|
|
|
}
|
|
else if( gameState == SUDDENDEATH )
|
|
{
|
|
hud->UpdateMessage( true, "#str_04252" );
|
|
}
|
|
else
|
|
{
|
|
hud->UpdateGameTime( GameTime() );
|
|
}
|
|
|
|
if( IsGametypeTeamBased() || IsGametypeFlagBased() )
|
|
{
|
|
hud->ToggleMPInfo( true, true, IsGametypeFlagBased() );
|
|
}
|
|
else
|
|
{
|
|
hud->ToggleMPInfo( true, false );
|
|
}
|
|
|
|
if( gameState == GAMEON || gameState == COUNTDOWN || gameState == WARMUP )
|
|
{
|
|
if( IsGametypeTeamBased() && !IsGametypeFlagBased() )
|
|
{
|
|
for( int i = 0; i < gameLocal.numClients; ++i )
|
|
{
|
|
idEntity* ent = gameLocal.entities[ i ];
|
|
if( ent == NULL || !ent->IsType( idPlayer::Type ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
idPlayer* player = static_cast< idPlayer* >( ent );
|
|
hud->SetTeamScore( player->team, playerState[ player->entityNumber ].teamFragCount );
|
|
}
|
|
}
|
|
}
|
|
|
|
if( IsGametypeFlagBased() || IsGametypeTeamBased() )
|
|
{
|
|
hud->SetTeam( player->team );
|
|
}
|
|
else
|
|
{
|
|
hud->SetTeam( -1 );
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::SetScoreboardActive
|
|
================
|
|
*/
|
|
void idMultiplayerGame::SetScoreboardActive( bool active )
|
|
{
|
|
if( scoreboardManager != NULL )
|
|
{
|
|
if( active )
|
|
{
|
|
if( IsGametypeTeamBased() || IsGametypeFlagBased() )
|
|
{
|
|
scoreboardManager->SetActivationScreen( SCOREBOARD_AREA_TEAM, MENU_TRANSITION_SIMPLE );
|
|
}
|
|
else
|
|
{
|
|
scoreboardManager->SetActivationScreen( SCOREBOARD_AREA_DEFAULT, MENU_TRANSITION_SIMPLE );
|
|
}
|
|
|
|
scoreboardManager->ActivateMenu( true );
|
|
}
|
|
else
|
|
{
|
|
|
|
scoreboardManager->ActivateMenu( false );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::DrawScoreBoard
|
|
================
|
|
*/
|
|
void idMultiplayerGame::DrawScoreBoard( idPlayer* player )
|
|
{
|
|
if( scoreboardManager && scoreboardManager->IsActive() == true )
|
|
{
|
|
UpdateScoreboard( scoreboardManager, player );
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idMultiplayerGame::ClearChatData
|
|
===============
|
|
*/
|
|
void idMultiplayerGame::ClearChatData()
|
|
{
|
|
chatHistoryIndex = 0;
|
|
chatHistorySize = 0;
|
|
chatDataUpdated = true;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idMultiplayerGame::AddChatLine
|
|
===============
|
|
*/
|
|
void idMultiplayerGame::AddChatLine( const char* fmt, ... )
|
|
{
|
|
idStr temp;
|
|
va_list argptr;
|
|
|
|
va_start( argptr, fmt );
|
|
vsprintf( temp, fmt, argptr );
|
|
va_end( argptr );
|
|
|
|
gameLocal.Printf( "%s\n", temp.c_str() );
|
|
|
|
chatHistory[ chatHistoryIndex % NUM_CHAT_NOTIFY ].line = temp;
|
|
chatHistory[ chatHistoryIndex % NUM_CHAT_NOTIFY ].fade = 6;
|
|
|
|
chatHistoryIndex++;
|
|
if( chatHistorySize < NUM_CHAT_NOTIFY )
|
|
{
|
|
chatHistorySize++;
|
|
}
|
|
chatDataUpdated = true;
|
|
lastChatLineTime = Sys_Milliseconds();
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idMultiplayerGame::DrawChat
|
|
===============
|
|
*/
|
|
void idMultiplayerGame::DrawChat( idPlayer* player )
|
|
{
|
|
int i, j;
|
|
|
|
if( player )
|
|
{
|
|
if( Sys_Milliseconds() - lastChatLineTime > CHAT_FADE_TIME )
|
|
{
|
|
if( chatHistorySize > 0 )
|
|
{
|
|
for( i = chatHistoryIndex - chatHistorySize; i < chatHistoryIndex; i++ )
|
|
{
|
|
chatHistory[ i % NUM_CHAT_NOTIFY ].fade--;
|
|
if( chatHistory[ i % NUM_CHAT_NOTIFY ].fade < 0 )
|
|
{
|
|
chatHistorySize--; // this assumes the removals are always at the beginning
|
|
}
|
|
}
|
|
chatDataUpdated = true;
|
|
}
|
|
lastChatLineTime = Sys_Milliseconds();
|
|
}
|
|
if( chatDataUpdated )
|
|
{
|
|
j = 0;
|
|
i = chatHistoryIndex - chatHistorySize;
|
|
while( i < chatHistoryIndex )
|
|
{
|
|
player->AddChatMessage( j, Min( 4, ( int )chatHistory[ i % NUM_CHAT_NOTIFY ].fade ), chatHistory[ i % NUM_CHAT_NOTIFY ].line );
|
|
j++;
|
|
i++;
|
|
}
|
|
while( j < NUM_CHAT_NOTIFY )
|
|
{
|
|
player->ClearChatMessage( j );
|
|
j++;
|
|
}
|
|
chatDataUpdated = false;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
//D3XP: Adding one to frag count to allow for the negative flag in numbers greater than 255
|
|
const int ASYNC_PLAYER_FRAG_BITS = -( idMath::BitsForInteger( MP_PLAYER_MAXFRAGS - MP_PLAYER_MINFRAGS ) + 1 ); // player can have negative frags
|
|
const int ASYNC_PLAYER_WINS_BITS = idMath::BitsForInteger( MP_PLAYER_MAXWINS );
|
|
const int ASYNC_PLAYER_PING_BITS = idMath::BitsForInteger( MP_PLAYER_MAXPING );
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::WriteToSnapshot
|
|
================
|
|
*/
|
|
void idMultiplayerGame::WriteToSnapshot( idBitMsg& msg ) const
|
|
{
|
|
int i;
|
|
int value;
|
|
|
|
// This is a hack - I need a place to read the lobby ids before the player entities are
|
|
// read (SpawnPlayer requires a valid lobby id for the player).
|
|
for( int i = 0; i < gameLocal.lobbyUserIDs.Num(); ++i )
|
|
{
|
|
gameLocal.lobbyUserIDs[i].WriteToMsg( msg );
|
|
}
|
|
|
|
msg.WriteByte( gameState );
|
|
msg.WriteLong( nextStateSwitch );
|
|
msg.WriteShort( currentTourneyPlayer[ 0 ] );
|
|
msg.WriteShort( currentTourneyPlayer[ 1 ] );
|
|
for( i = 0; i < MAX_CLIENTS; i++ )
|
|
{
|
|
// clamp all values to min/max possible value that we can send over
|
|
value = idMath::ClampInt( MP_PLAYER_MINFRAGS, MP_PLAYER_MAXFRAGS, playerState[i].fragCount );
|
|
msg.WriteBits( value, ASYNC_PLAYER_FRAG_BITS );
|
|
value = idMath::ClampInt( MP_PLAYER_MINFRAGS, MP_PLAYER_MAXFRAGS, playerState[i].teamFragCount );
|
|
msg.WriteBits( value, ASYNC_PLAYER_FRAG_BITS );
|
|
value = idMath::ClampInt( 0, MP_PLAYER_MAXWINS, playerState[i].wins );
|
|
msg.WriteBits( value, ASYNC_PLAYER_WINS_BITS );
|
|
value = idMath::ClampInt( 0, MP_PLAYER_MAXPING, playerState[i].ping );
|
|
msg.WriteBits( value, ASYNC_PLAYER_PING_BITS );
|
|
}
|
|
|
|
msg.WriteShort( teamPoints[0] );
|
|
msg.WriteShort( teamPoints[1] );
|
|
msg.WriteShort( player_red_flag );
|
|
msg.WriteShort( player_blue_flag );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::ReadFromSnapshot
|
|
================
|
|
*/
|
|
void idMultiplayerGame::ReadFromSnapshot( const idBitMsg& msg )
|
|
{
|
|
int i;
|
|
gameState_t newState;
|
|
|
|
// This is a hack - I need a place to read the lobby ids before the player entities are
|
|
// read (SpawnPlayer requires a valid lobby id for the player).
|
|
for( int i = 0; i < gameLocal.lobbyUserIDs.Num(); ++i )
|
|
{
|
|
gameLocal.lobbyUserIDs[i].ReadFromMsg( msg );
|
|
}
|
|
|
|
newState = ( idMultiplayerGame::gameState_t )msg.ReadByte();
|
|
nextStateSwitch = msg.ReadLong();
|
|
if( newState != gameState && newState < STATE_COUNT )
|
|
{
|
|
gameLocal.DPrintf( "%s -> %s\n", GameStateStrings[ gameState ], GameStateStrings[ newState ] );
|
|
gameState = newState;
|
|
|
|
switch( gameState )
|
|
{
|
|
case GAMEON:
|
|
{
|
|
NewState_GameOn_ServerAndClient();
|
|
break;
|
|
}
|
|
case GAMEREVIEW:
|
|
{
|
|
NewState_GameReview_ServerAndClient();
|
|
break;
|
|
}
|
|
case WARMUP:
|
|
{
|
|
NewState_Warmup_ServerAndClient();
|
|
break;
|
|
}
|
|
case COUNTDOWN:
|
|
{
|
|
NewState_Countdown_ServerAndClient();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
currentTourneyPlayer[ 0 ] = msg.ReadShort();
|
|
currentTourneyPlayer[ 1 ] = msg.ReadShort();
|
|
for( i = 0; i < MAX_CLIENTS; i++ )
|
|
{
|
|
playerState[i].fragCount = msg.ReadBits( ASYNC_PLAYER_FRAG_BITS );
|
|
playerState[i].teamFragCount = msg.ReadBits( ASYNC_PLAYER_FRAG_BITS );
|
|
playerState[i].wins = msg.ReadBits( ASYNC_PLAYER_WINS_BITS );
|
|
playerState[i].ping = msg.ReadBits( ASYNC_PLAYER_PING_BITS );
|
|
}
|
|
|
|
teamPoints[0] = msg.ReadShort();
|
|
teamPoints[1] = msg.ReadShort();
|
|
|
|
player_red_flag = msg.ReadShort();
|
|
player_blue_flag = msg.ReadShort();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::PlayGlobalSound
|
|
================
|
|
*/
|
|
void idMultiplayerGame::PlayGlobalSound( int toPlayerNum, snd_evt_t evt, const char* shader )
|
|
{
|
|
|
|
if( toPlayerNum < 0 || toPlayerNum == gameLocal.GetLocalClientNum() )
|
|
{
|
|
if( shader )
|
|
{
|
|
if( gameSoundWorld )
|
|
{
|
|
gameSoundWorld->PlayShaderDirectly( shader );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if( gameSoundWorld )
|
|
{
|
|
gameSoundWorld->PlayShaderDirectly( GlobalSoundStrings[ evt ] );
|
|
}
|
|
}
|
|
}
|
|
|
|
if( !common->IsClient() && toPlayerNum != gameLocal.GetLocalClientNum() )
|
|
{
|
|
idBitMsg outMsg;
|
|
byte msgBuf[1024];
|
|
outMsg.InitWrite( msgBuf, sizeof( msgBuf ) );
|
|
int type = 0;
|
|
|
|
if( shader )
|
|
{
|
|
const idSoundShader* shaderDecl = declManager->FindSound( shader );
|
|
if( !shaderDecl )
|
|
{
|
|
return;
|
|
}
|
|
outMsg.WriteLong( gameLocal.ServerRemapDecl( -1, DECL_SOUND, shaderDecl->Index() ) );
|
|
type = GAME_RELIABLE_MESSAGE_SOUND_INDEX;
|
|
}
|
|
else
|
|
{
|
|
outMsg.WriteByte( evt );
|
|
type = GAME_RELIABLE_MESSAGE_SOUND_EVENT;
|
|
}
|
|
if( toPlayerNum >= 0 )
|
|
{
|
|
session->GetActingGameStateLobbyBase().SendReliableToLobbyUser( gameLocal.lobbyUserIDs[toPlayerNum], type, outMsg );
|
|
}
|
|
else
|
|
{
|
|
session->GetActingGameStateLobbyBase().SendReliable( type, outMsg, false );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::PlayTeamSound
|
|
================
|
|
*/
|
|
void idMultiplayerGame::PlayTeamSound( int toTeam, snd_evt_t evt, const char* shader )
|
|
{
|
|
for( int i = 0; i < gameLocal.numClients; i++ )
|
|
{
|
|
idEntity* ent = gameLocal.entities[ i ];
|
|
if( !ent || !ent->IsType( idPlayer::Type ) )
|
|
{
|
|
continue;
|
|
}
|
|
idPlayer* player = static_cast<idPlayer*>( ent );
|
|
if( player->team != toTeam )
|
|
continue;
|
|
PlayGlobalSound( i, evt, shader );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::PrintMessageEvent
|
|
================
|
|
*/
|
|
void idMultiplayerGame::PrintMessageEvent( msg_evt_t evt, int parm1, int parm2 )
|
|
{
|
|
idLobbyBase& lobby = session->GetActingGameStateLobbyBase();
|
|
|
|
switch( evt )
|
|
{
|
|
case MSG_LEFTGAME:
|
|
assert( parm1 >= 0 );
|
|
AddChatLine( idLocalization::GetString( "#str_11604" ), lobby.GetLobbyUserName( gameLocal.lobbyUserIDs[parm1] ) );
|
|
break;
|
|
case MSG_SUICIDE:
|
|
assert( parm1 >= 0 );
|
|
AddChatLine( idLocalization::GetString( "#str_04293" ), lobby.GetLobbyUserName( gameLocal.lobbyUserIDs[parm1] ) );
|
|
break;
|
|
case MSG_KILLED:
|
|
assert( parm1 >= 0 && parm2 >= 0 );
|
|
AddChatLine( idLocalization::GetString( "#str_04292" ), lobby.GetLobbyUserName( gameLocal.lobbyUserIDs[parm1] ), lobby.GetLobbyUserName( gameLocal.lobbyUserIDs[parm2] ) );
|
|
break;
|
|
case MSG_KILLEDTEAM:
|
|
assert( parm1 >= 0 && parm2 >= 0 );
|
|
AddChatLine( idLocalization::GetString( "#str_04291" ), lobby.GetLobbyUserName( gameLocal.lobbyUserIDs[parm1] ), lobby.GetLobbyUserName( gameLocal.lobbyUserIDs[parm2] ) );
|
|
break;
|
|
case MSG_TELEFRAGGED:
|
|
assert( parm1 >= 0 && parm2 >= 0 );
|
|
AddChatLine( idLocalization::GetString( "#str_04290" ), lobby.GetLobbyUserName( gameLocal.lobbyUserIDs[parm1] ), lobby.GetLobbyUserName( gameLocal.lobbyUserIDs[parm2] ) );
|
|
break;
|
|
case MSG_DIED:
|
|
assert( parm1 >= 0 );
|
|
AddChatLine( idLocalization::GetString( "#str_04289" ), lobby.GetLobbyUserName( gameLocal.lobbyUserIDs[parm1] ) );
|
|
break;
|
|
case MSG_SUDDENDEATH:
|
|
AddChatLine( idLocalization::GetString( "#str_04287" ) );
|
|
break;
|
|
case MSG_JOINEDSPEC:
|
|
AddChatLine( idLocalization::GetString( "#str_04285" ), lobby.GetLobbyUserName( gameLocal.lobbyUserIDs[parm1] ) );
|
|
break;
|
|
case MSG_TIMELIMIT:
|
|
AddChatLine( idLocalization::GetString( "#str_04284" ) );
|
|
break;
|
|
case MSG_FRAGLIMIT:
|
|
if( gameLocal.gameType == GAME_LASTMAN )
|
|
{
|
|
AddChatLine( idLocalization::GetString( "#str_04283" ), lobby.GetLobbyUserName( gameLocal.lobbyUserIDs[parm1] ) );
|
|
}
|
|
else if( IsGametypeTeamBased() ) /* CTF */
|
|
{
|
|
AddChatLine( idLocalization::GetString( "#str_04282" ), idLocalization::GetString( teamNames[ parm1 ] ) );
|
|
}
|
|
else
|
|
{
|
|
AddChatLine( idLocalization::GetString( "#str_04281" ), lobby.GetLobbyUserName( gameLocal.lobbyUserIDs[parm1] ) );
|
|
}
|
|
break;
|
|
case MSG_JOINTEAM:
|
|
AddChatLine( idLocalization::GetString( "#str_04280" ), lobby.GetLobbyUserName( gameLocal.lobbyUserIDs[parm1] ), parm2 ? idLocalization::GetString( "#str_02500" ) : idLocalization::GetString( "#str_02499" ) );
|
|
break;
|
|
case MSG_HOLYSHIT:
|
|
AddChatLine( idLocalization::GetString( "#str_06732" ) );
|
|
break;
|
|
case MSG_POINTLIMIT:
|
|
AddChatLine( idLocalization::GetString( "#str_11100" ), parm1 ? idLocalization::GetString( "#str_11110" ) : idLocalization::GetString( "#str_11111" ) );
|
|
break;
|
|
|
|
case MSG_FLAGTAKEN :
|
|
if( gameLocal.GetLocalPlayer() == NULL )
|
|
break;
|
|
|
|
if( parm2 < 0 || parm2 >= MAX_CLIENTS )
|
|
break;
|
|
|
|
if( gameLocal.GetLocalPlayer()->team != parm1 )
|
|
{
|
|
AddChatLine( idLocalization::GetString( "#str_11101" ), lobby.GetLobbyUserName( gameLocal.lobbyUserIDs[parm2] ) ); // your team
|
|
}
|
|
else
|
|
{
|
|
AddChatLine( idLocalization::GetString( "#str_11102" ), lobby.GetLobbyUserName( gameLocal.lobbyUserIDs[parm2] ) ); // enemy
|
|
}
|
|
break;
|
|
|
|
case MSG_FLAGDROP :
|
|
if( gameLocal.GetLocalPlayer() == NULL )
|
|
break;
|
|
|
|
if( gameLocal.GetLocalPlayer()->team != parm1 )
|
|
{
|
|
AddChatLine( idLocalization::GetString( "#str_11103" ) ); // your team
|
|
}
|
|
else
|
|
{
|
|
AddChatLine( idLocalization::GetString( "#str_11104" ) ); // enemy
|
|
}
|
|
break;
|
|
|
|
case MSG_FLAGRETURN :
|
|
if( gameLocal.GetLocalPlayer() == NULL )
|
|
break;
|
|
|
|
if( parm2 >= 0 && parm2 < MAX_CLIENTS )
|
|
{
|
|
if( gameLocal.GetLocalPlayer()->team != parm1 )
|
|
{
|
|
AddChatLine( idLocalization::GetString( "#str_11120" ), lobby.GetLobbyUserName( gameLocal.lobbyUserIDs[parm2] ) ); // your team
|
|
}
|
|
else
|
|
{
|
|
AddChatLine( idLocalization::GetString( "#str_11121" ), lobby.GetLobbyUserName( gameLocal.lobbyUserIDs[parm2] ) ); // enemy
|
|
}
|
|
}
|
|
else
|
|
{
|
|
AddChatLine( idLocalization::GetString( "#str_11105" ), parm1 ? idLocalization::GetString( "#str_11110" ) : idLocalization::GetString( "#str_11111" ) );
|
|
}
|
|
break;
|
|
|
|
case MSG_FLAGCAPTURE :
|
|
if( gameLocal.GetLocalPlayer() == NULL )
|
|
break;
|
|
|
|
if( parm2 < 0 || parm2 >= MAX_CLIENTS )
|
|
break;
|
|
|
|
if( gameLocal.GetLocalPlayer()->team != parm1 )
|
|
{
|
|
AddChatLine( idLocalization::GetString( "#str_11122" ), lobby.GetLobbyUserName( gameLocal.lobbyUserIDs[parm2] ) ); // your team
|
|
}
|
|
else
|
|
{
|
|
AddChatLine( idLocalization::GetString( "#str_11123" ), lobby.GetLobbyUserName( gameLocal.lobbyUserIDs[parm2] ) ); // enemy
|
|
}
|
|
|
|
// AddChatLine( idLocalization::GetString( "#str_11106" ), parm1 ? idLocalization::GetString( "#str_11110" ) : idLocalization::GetString( "#str_11111" ) );
|
|
break;
|
|
|
|
case MSG_SCOREUPDATE:
|
|
AddChatLine( idLocalization::GetString( "#str_11107" ), parm1, parm2 );
|
|
break;
|
|
default:
|
|
gameLocal.DPrintf( "PrintMessageEvent: unknown message type %d\n", evt );
|
|
return;
|
|
}
|
|
if( !common->IsClient() )
|
|
{
|
|
idBitMsg outMsg;
|
|
byte msgBuf[1024];
|
|
outMsg.InitWrite( msgBuf, sizeof( msgBuf ) );
|
|
outMsg.WriteByte( evt );
|
|
outMsg.WriteByte( parm1 );
|
|
outMsg.WriteByte( parm2 );
|
|
session->GetActingGameStateLobbyBase().SendReliable( GAME_RELIABLE_MESSAGE_DB, outMsg, false );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::SuddenRespawns
|
|
solely for LMN if an end game ( fragLimitTimeout ) was entered and aborted before expiration
|
|
LMN players which still have lives left need to be respawned without being marked lastManOver
|
|
================
|
|
*/
|
|
void idMultiplayerGame::SuddenRespawn()
|
|
{
|
|
int i;
|
|
|
|
if( gameLocal.gameType != GAME_LASTMAN )
|
|
{
|
|
return;
|
|
}
|
|
|
|
for( i = 0; i < gameLocal.numClients; i++ )
|
|
{
|
|
if( !gameLocal.entities[ i ] || !gameLocal.entities[ i ]->IsType( idPlayer::Type ) )
|
|
{
|
|
continue;
|
|
}
|
|
if( !CanPlay( static_cast< idPlayer* >( gameLocal.entities[ i ] ) ) )
|
|
{
|
|
continue;
|
|
}
|
|
if( static_cast< idPlayer* >( gameLocal.entities[ i ] )->lastManOver )
|
|
{
|
|
continue;
|
|
}
|
|
static_cast< idPlayer* >( gameLocal.entities[ i ] )->lastManPlayAgain = true;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::CheckSpawns
|
|
================
|
|
*/
|
|
void idMultiplayerGame::CheckRespawns( idPlayer* spectator )
|
|
{
|
|
for( int i = 0 ; i < gameLocal.numClients ; i++ )
|
|
{
|
|
idEntity* ent = gameLocal.entities[ i ];
|
|
if( !ent || !ent->IsType( idPlayer::Type ) )
|
|
{
|
|
continue;
|
|
}
|
|
idPlayer* p = static_cast<idPlayer*>( ent );
|
|
// once we hit sudden death, nobody respawns till game has ended
|
|
if( WantRespawn( p ) || p == spectator )
|
|
{
|
|
if( gameState == SUDDENDEATH && gameLocal.gameType != GAME_LASTMAN )
|
|
{
|
|
// respawn rules while sudden death are different
|
|
// sudden death may trigger while a player is dead, so there are still cases where we need to respawn
|
|
// don't do any respawns while we are in end game delay though
|
|
if( !fragLimitTimeout )
|
|
{
|
|
if( IsGametypeTeamBased() || p->IsLeader() ) /* CTF */
|
|
{
|
|
#ifdef _DEBUG
|
|
if( gameLocal.gameType == GAME_TOURNEY )
|
|
{
|
|
assert( p->entityNumber == currentTourneyPlayer[ 0 ] || p->entityNumber == currentTourneyPlayer[ 1 ] );
|
|
}
|
|
#endif
|
|
p->ServerSpectate( false );
|
|
}
|
|
else if( !p->IsLeader() )
|
|
{
|
|
// sudden death is rolling, this player is not a leader, have him spectate
|
|
p->ServerSpectate( true );
|
|
CheckAbortGame();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if( gameLocal.gameType == GAME_DM || // CTF : 3wave sboily, was DM really included before?
|
|
IsGametypeTeamBased() )
|
|
{
|
|
if( gameState == WARMUP || gameState == COUNTDOWN || gameState == GAMEON )
|
|
{
|
|
p->ServerSpectate( false );
|
|
}
|
|
}
|
|
else if( gameLocal.gameType == GAME_TOURNEY )
|
|
{
|
|
if( i == currentTourneyPlayer[ 0 ] || i == currentTourneyPlayer[ 1 ] )
|
|
{
|
|
if( gameState == WARMUP || gameState == COUNTDOWN || gameState == GAMEON )
|
|
{
|
|
p->ServerSpectate( false );
|
|
idLib::Printf( "TOURNEY CheckRespawns :> Player %d On Deck \n", p->entityNumber );
|
|
}
|
|
}
|
|
else if( gameState == WARMUP )
|
|
{
|
|
// make sure empty tourney slots get filled first
|
|
FillTourneySlots( );
|
|
if( i == currentTourneyPlayer[ 0 ] || i == currentTourneyPlayer[ 1 ] )
|
|
{
|
|
p->ServerSpectate( false );
|
|
idLib::Printf( "TOURNEY CheckRespawns WARMUP :> Player %d On Deck \n", p->entityNumber );
|
|
}
|
|
}
|
|
}
|
|
else if( gameLocal.gameType == GAME_LASTMAN )
|
|
{
|
|
if( gameState == WARMUP || gameState == COUNTDOWN )
|
|
{
|
|
// Player has spawned in game, give him lives.
|
|
playerState[ i ].fragCount = gameLocal.serverInfo.GetInt( "si_fragLimit" );
|
|
p->ServerSpectate( false );
|
|
}
|
|
else if( gameState == GAMEON || gameState == SUDDENDEATH )
|
|
{
|
|
if( gameState == GAMEON && playerState[ i ].fragCount > 0 && p->lastManPresent )
|
|
{
|
|
assert( !p->lastManOver );
|
|
p->ServerSpectate( false );
|
|
}
|
|
else if( p->lastManPlayAgain && p->lastManPresent )
|
|
{
|
|
assert( gameState == SUDDENDEATH );
|
|
p->ServerSpectate( false );
|
|
}
|
|
else
|
|
{
|
|
// if a fragLimitTimeout was engaged, do NOT mark lastManOver as that could mean
|
|
// everyone ends up spectator and game is stalled with no end
|
|
// if the frag limit delay is engaged and cancels out before expiring, LMN players are
|
|
// respawned to play the tie again ( through SuddenRespawn and lastManPlayAgain )
|
|
if( !fragLimitTimeout && !p->lastManOver )
|
|
{
|
|
common->DPrintf( "client %d has lost all last man lives\n", i );
|
|
// end of the game for this guy, send him to spectators
|
|
p->lastManOver = true;
|
|
// clients don't have access to lastManOver
|
|
// so set the fragCount to something silly ( used in scoreboard and player ranking )
|
|
playerState[ i ].fragCount = LASTMAN_NOLIVES;
|
|
p->ServerSpectate( true );
|
|
|
|
//Check for a situation where the last two player dies at the same time and don't
|
|
//try to respawn manually...This was causing all players to go into spectate mode
|
|
//and the server got stuck
|
|
{
|
|
int j;
|
|
for( j = 0; j < gameLocal.numClients; j++ )
|
|
{
|
|
if( !gameLocal.entities[ j ] )
|
|
{
|
|
continue;
|
|
}
|
|
if( !CanPlay( static_cast< idPlayer* >( gameLocal.entities[ j ] ) ) )
|
|
{
|
|
continue;
|
|
}
|
|
if( !static_cast< idPlayer* >( gameLocal.entities[ j ] )->lastManOver )
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
if( j == gameLocal.numClients )
|
|
{
|
|
//Everyone is dead so don't allow this player to spectate
|
|
//so the match will end
|
|
p->ServerSpectate( false );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if( p->wantSpectate && !p->spectating )
|
|
{
|
|
playerState[ i ].fragCount = 0; // whenever you willingly go spectate during game, your score resets
|
|
p->ServerSpectate( true );
|
|
idLib::Printf( "TOURNEY CheckRespawns :> Player %d Wants Spectate \n", p->entityNumber );
|
|
UpdateTourneyLine();
|
|
CheckAbortGame();
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::DropWeapon
|
|
================
|
|
*/
|
|
void idMultiplayerGame::DropWeapon( int clientNum )
|
|
{
|
|
assert( !common->IsClient() );
|
|
idEntity* ent = gameLocal.entities[ clientNum ];
|
|
if( !ent || !ent->IsType( idPlayer::Type ) )
|
|
{
|
|
return;
|
|
}
|
|
static_cast< idPlayer* >( ent )->DropWeapon( false );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::DropWeapon_f
|
|
================
|
|
*/
|
|
void idMultiplayerGame::DropWeapon_f( const idCmdArgs& args )
|
|
{
|
|
if( !common->IsMultiplayer() )
|
|
{
|
|
common->Printf( "clientDropWeapon: only valid in multiplayer\n" );
|
|
return;
|
|
}
|
|
idBitMsg outMsg;
|
|
session->GetActingGameStateLobbyBase().SendReliableToHost( GAME_RELIABLE_MESSAGE_DROPWEAPON, outMsg );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::MessageMode_f
|
|
================
|
|
*/
|
|
void idMultiplayerGame::MessageMode_f( const idCmdArgs& args )
|
|
{
|
|
if( !common->IsMultiplayer() )
|
|
{
|
|
return;
|
|
}
|
|
gameLocal.mpGame.MessageMode( args );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::MessageMode
|
|
================
|
|
*/
|
|
void idMultiplayerGame::MessageMode( const idCmdArgs& args )
|
|
{
|
|
idEntity* ent = gameLocal.entities[ gameLocal.GetLocalClientNum() ];
|
|
if( !ent || !ent->IsType( idPlayer::Type ) )
|
|
{
|
|
return;
|
|
}
|
|
idPlayer* player = static_cast< idPlayer* >( ent );
|
|
if( player && !player->spectating )
|
|
{
|
|
if( args.Argc() != 2 )
|
|
{
|
|
player->isChatting = 1;
|
|
}
|
|
else
|
|
{
|
|
player->isChatting = 2;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::DisconnectClient
|
|
================
|
|
*/
|
|
void idMultiplayerGame::DisconnectClient( int clientNum )
|
|
{
|
|
if( lastWinner == clientNum )
|
|
{
|
|
lastWinner = -1;
|
|
}
|
|
|
|
// Show that the user has left the game.
|
|
PrintMessageEvent( MSG_LEFTGAME, clientNum, -1 );
|
|
|
|
UpdatePlayerRanks();
|
|
CheckAbortGame();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::CheckAbortGame
|
|
================
|
|
*/
|
|
void idMultiplayerGame::CheckAbortGame()
|
|
{
|
|
int i;
|
|
if( gameLocal.gameType == GAME_TOURNEY && gameState == WARMUP )
|
|
{
|
|
// if a tourney player joined spectators, let someone else have his spot
|
|
for( i = 0; i < 2; i++ )
|
|
{
|
|
if( !gameLocal.entities[ currentTourneyPlayer[ i ] ] || static_cast< idPlayer* >( gameLocal.entities[ currentTourneyPlayer[ i ] ] )->spectating )
|
|
{
|
|
currentTourneyPlayer[ i ] = -1;
|
|
}
|
|
}
|
|
}
|
|
// only checks for aborts -> game review below
|
|
if( gameState != COUNTDOWN && gameState != GAMEON && gameState != SUDDENDEATH )
|
|
{
|
|
return;
|
|
}
|
|
switch( gameLocal.gameType )
|
|
{
|
|
case GAME_TOURNEY:
|
|
for( i = 0; i < 2; i++ )
|
|
{
|
|
|
|
idPlayer* player = static_cast< idPlayer* >( gameLocal.entities[ currentTourneyPlayer[ i ] ] );
|
|
|
|
if( !gameLocal.entities[ currentTourneyPlayer[ i ] ] || player->spectating )
|
|
{
|
|
NewState( GAMEREVIEW );
|
|
return;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
if( !EnoughClientsToPlay() )
|
|
{
|
|
NewState( GAMEREVIEW );
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::MapRestart
|
|
================
|
|
*/
|
|
void idMultiplayerGame::MapRestart()
|
|
{
|
|
|
|
assert( !common->IsClient() );
|
|
if( gameState != WARMUP )
|
|
{
|
|
NewState( WARMUP );
|
|
nextState = INACTIVE;
|
|
nextStateSwitch = 0;
|
|
}
|
|
|
|
teamPoints[0] = 0;
|
|
teamPoints[1] = 0;
|
|
|
|
BalanceTeams();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::BalanceTeams
|
|
================
|
|
*/
|
|
void idMultiplayerGame::BalanceTeams()
|
|
{
|
|
if( !IsGametypeTeamBased() )
|
|
{
|
|
return;
|
|
}
|
|
int teamCount[ 2 ] = { 0, 0 };
|
|
NumActualClients( false, teamCount );
|
|
int outOfBalance = abs( teamCount[0] - teamCount[1] );
|
|
if( outOfBalance <= 1 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Teams are out of balance
|
|
// Move N players from the large team to the small team
|
|
int numPlayersToMove = outOfBalance / 2;
|
|
int smallTeam = MinIndex( teamCount[0], teamCount[1] );
|
|
int largeTeam = 1 - smallTeam;
|
|
|
|
idLobbyBase& lobby = session->GetActingGameStateLobbyBase();
|
|
|
|
// First move players from the large team that match a party token on the small team
|
|
for( int a = 0; a < gameLocal.numClients; a++ )
|
|
{
|
|
idPlayer* playerA = gameLocal.GetClientByNum( a );
|
|
if( playerA->team == largeTeam && CanPlay( playerA ) )
|
|
{
|
|
for( int b = 0; b < gameLocal.numClients; b++ )
|
|
{
|
|
if( a == b )
|
|
{
|
|
continue;
|
|
}
|
|
idPlayer* playerB = gameLocal.GetClientByNum( b );
|
|
if( playerB->team == smallTeam && CanPlay( playerB ) )
|
|
{
|
|
if( lobby.GetLobbyUserPartyToken( gameLocal.lobbyUserIDs[ a ] ) == lobby.GetLobbyUserPartyToken( gameLocal.lobbyUserIDs[ b ] ) )
|
|
{
|
|
SwitchToTeam( a, largeTeam, smallTeam );
|
|
numPlayersToMove--;
|
|
if( numPlayersToMove == 0 )
|
|
{
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Then move players from the large team that DON'T match party tokens on the large team
|
|
for( int a = 0; a < gameLocal.numClients; a++ )
|
|
{
|
|
idPlayer* playerA = gameLocal.GetClientByNum( a );
|
|
if( playerA->team == largeTeam && CanPlay( playerA ) )
|
|
{
|
|
bool match = false;
|
|
for( int b = 0; b < gameLocal.numClients; b++ )
|
|
{
|
|
if( a == b )
|
|
{
|
|
continue;
|
|
}
|
|
idPlayer* playerB = gameLocal.GetClientByNum( b );
|
|
if( playerB->team == largeTeam && CanPlay( playerB ) )
|
|
{
|
|
if( lobby.GetLobbyUserPartyToken( gameLocal.lobbyUserIDs[ a ] ) == lobby.GetLobbyUserPartyToken( gameLocal.lobbyUserIDs[ b ] ) )
|
|
{
|
|
match = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if( !match )
|
|
{
|
|
SwitchToTeam( a, largeTeam, smallTeam );
|
|
numPlayersToMove--;
|
|
if( numPlayersToMove == 0 )
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Then move any players from the large team to the small team
|
|
for( int a = 0; a < gameLocal.numClients; a++ )
|
|
{
|
|
idPlayer* playerA = gameLocal.GetClientByNum( a );
|
|
if( playerA->team == largeTeam && CanPlay( playerA ) )
|
|
{
|
|
SwitchToTeam( a, largeTeam, smallTeam );
|
|
numPlayersToMove--;
|
|
if( numPlayersToMove == 0 )
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::SwitchToTeam
|
|
================
|
|
*/
|
|
void idMultiplayerGame::SwitchToTeam( int clientNum, int oldteam, int newteam )
|
|
{
|
|
idPlayer* p = static_cast<idPlayer*>( gameLocal.entities[ clientNum ] );
|
|
p->team = newteam;
|
|
session->GetActingGameStateLobbyBase().SetLobbyUserTeam( gameLocal.lobbyUserIDs[ clientNum ], newteam );
|
|
session->SetVoiceGroupsToTeams();
|
|
|
|
assert( IsGametypeTeamBased() ); /* CTF */
|
|
assert( oldteam != newteam );
|
|
assert( !common->IsClient() );
|
|
|
|
if( !common->IsClient() && newteam >= 0 )
|
|
{
|
|
PrintMessageEvent( MSG_JOINTEAM, clientNum, newteam );
|
|
}
|
|
// assign the right teamFragCount
|
|
int i;
|
|
for( i = 0; i < gameLocal.numClients; i++ )
|
|
{
|
|
if( i == clientNum )
|
|
{
|
|
continue;
|
|
}
|
|
idEntity* ent = gameLocal.entities[ i ];
|
|
if( ent && ent->IsType( idPlayer::Type ) && static_cast< idPlayer* >( ent )->team == newteam )
|
|
{
|
|
playerState[ clientNum ].teamFragCount = playerState[ i ].teamFragCount;
|
|
break;
|
|
}
|
|
}
|
|
if( i == gameLocal.numClients )
|
|
{
|
|
// alone on this team
|
|
playerState[ clientNum ].teamFragCount = 0;
|
|
}
|
|
if( ( gameState == GAMEON || ( IsGametypeFlagBased() && gameState == SUDDENDEATH ) ) && oldteam != -1 )
|
|
{
|
|
// when changing teams during game, kill and respawn
|
|
if( p->IsInTeleport() )
|
|
{
|
|
p->ServerSendEvent( idPlayer::EVENT_ABORT_TELEPORTER, NULL, false );
|
|
p->SetPrivateCameraView( NULL );
|
|
}
|
|
p->Kill( true, true );
|
|
if( IsGametypeFlagBased() )
|
|
p->DropFlag();
|
|
CheckAbortGame();
|
|
}
|
|
else if( IsGametypeFlagBased() && oldteam != -1 )
|
|
{
|
|
p->DropFlag();
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::ProcessChatMessage
|
|
================
|
|
*/
|
|
void idMultiplayerGame::ProcessChatMessage( int clientNum, bool team, const char* name, const char* text, const char* sound )
|
|
{
|
|
idBitMsg outMsg;
|
|
byte msgBuf[ 256 ];
|
|
const char* prefix = NULL;
|
|
int send_to; // 0 - all, 1 - specs, 2 - team
|
|
int i;
|
|
idEntity* ent;
|
|
idPlayer* pfrom;
|
|
idStr prefixed_name;
|
|
|
|
assert( !common->IsClient() );
|
|
|
|
if( clientNum >= 0 )
|
|
{
|
|
pfrom = static_cast< idPlayer* >( gameLocal.entities[ clientNum ] );
|
|
if( !( pfrom && pfrom->IsType( idPlayer::Type ) ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if( pfrom->spectating )
|
|
{
|
|
prefix = "spectating";
|
|
if( team || ( !g_spectatorChat.GetBool() && ( gameState == GAMEON || gameState == SUDDENDEATH ) ) )
|
|
{
|
|
// to specs
|
|
send_to = 1;
|
|
}
|
|
else
|
|
{
|
|
// to all
|
|
send_to = 0;
|
|
}
|
|
}
|
|
else if( team )
|
|
{
|
|
prefix = "team";
|
|
// to team
|
|
send_to = 2;
|
|
}
|
|
else
|
|
{
|
|
// to all
|
|
send_to = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pfrom = NULL;
|
|
send_to = 0;
|
|
}
|
|
// put the message together
|
|
outMsg.InitWrite( msgBuf, sizeof( msgBuf ) );
|
|
if( prefix )
|
|
{
|
|
prefixed_name = va( "(%s) %s", prefix, name );
|
|
}
|
|
else
|
|
{
|
|
prefixed_name = name;
|
|
}
|
|
outMsg.WriteString( prefixed_name );
|
|
outMsg.WriteString( text, -1, false );
|
|
if( !send_to )
|
|
{
|
|
AddChatLine( "%s^0: %s\n", prefixed_name.c_str(), text );
|
|
session->GetActingGameStateLobbyBase().SendReliable( GAME_RELIABLE_MESSAGE_CHAT, outMsg, false );
|
|
if( sound )
|
|
{
|
|
PlayGlobalSound( -1, SND_COUNT, sound );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for( i = 0; i < gameLocal.numClients; i++ )
|
|
{
|
|
ent = gameLocal.entities[ i ];
|
|
if( !ent || !ent->IsType( idPlayer::Type ) )
|
|
{
|
|
continue;
|
|
}
|
|
idPlayer* pent = static_cast< idPlayer* >( ent );
|
|
if( send_to == 1 && pent->spectating )
|
|
{
|
|
if( sound )
|
|
{
|
|
PlayGlobalSound( i, SND_COUNT, sound );
|
|
}
|
|
if( i == gameLocal.GetLocalClientNum() )
|
|
{
|
|
AddChatLine( "%s^0: %s\n", prefixed_name.c_str(), text );
|
|
}
|
|
else
|
|
{
|
|
session->GetActingGameStateLobbyBase().SendReliableToLobbyUser( gameLocal.lobbyUserIDs[i], GAME_RELIABLE_MESSAGE_CHAT, outMsg );
|
|
}
|
|
}
|
|
else if( send_to == 2 && pent->team == pfrom->team )
|
|
{
|
|
if( sound )
|
|
{
|
|
PlayGlobalSound( i, SND_COUNT, sound );
|
|
}
|
|
if( i == gameLocal.GetLocalClientNum() )
|
|
{
|
|
AddChatLine( "%s^0: %s\n", prefixed_name.c_str(), text );
|
|
}
|
|
else
|
|
{
|
|
session->GetActingGameStateLobbyBase().SendReliableToLobbyUser( gameLocal.lobbyUserIDs[i], GAME_RELIABLE_MESSAGE_CHAT, outMsg );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::Precache
|
|
================
|
|
*/
|
|
void idMultiplayerGame::Precache()
|
|
{
|
|
if( !common->IsMultiplayer() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// player
|
|
declManager->FindType( DECL_ENTITYDEF, gameLocal.GetMPPlayerDefName(), false );
|
|
|
|
// skins
|
|
for( int i = 0; i < numSkins; i++ )
|
|
{
|
|
idStr baseSkinName = skinNames[ i ];
|
|
declManager->FindSkin( baseSkinName, false );
|
|
declManager->FindSkin( baseSkinName + "_berserk", false );
|
|
declManager->FindSkin( baseSkinName + "_invuln", false );
|
|
}
|
|
|
|
// MP game sounds
|
|
for( int i = 0; i < SND_COUNT; i++ )
|
|
{
|
|
declManager->FindSound( GlobalSoundStrings[ i ] );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::ToggleSpectate
|
|
================
|
|
*/
|
|
void idMultiplayerGame::ToggleSpectate()
|
|
{
|
|
assert( common->IsClient() || gameLocal.GetLocalClientNum() == 0 );
|
|
|
|
idPlayer* player = ( idPlayer* )gameLocal.entities[gameLocal.GetLocalClientNum()];
|
|
bool spectating = player->spectating;
|
|
// only allow toggling to spectate if spectators are enabled.
|
|
if( !spectating && !gameLocal.serverInfo.GetBool( "si_spectators" ) )
|
|
{
|
|
gameLocal.mpGame.AddChatLine( idLocalization::GetString( "#str_06747" ) );
|
|
return;
|
|
}
|
|
|
|
byte msgBuf[ 256 ];
|
|
idBitMsg outMsg;
|
|
outMsg.InitWrite( msgBuf, sizeof( msgBuf ) );
|
|
outMsg.WriteBool( !spectating );
|
|
outMsg.WriteByteAlign();
|
|
session->GetActingGameStateLobbyBase().SendReliableToHost( GAME_RELIABLE_MESSAGE_SPECTATE, outMsg );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::CanPlay
|
|
================
|
|
*/
|
|
bool idMultiplayerGame::CanPlay( idPlayer* p )
|
|
{
|
|
return !p->wantSpectate;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::WantRespawn
|
|
================
|
|
*/
|
|
bool idMultiplayerGame::WantRespawn( idPlayer* p )
|
|
{
|
|
return p->forceRespawn && !p->wantSpectate;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::VoiceChat
|
|
================
|
|
*/
|
|
void idMultiplayerGame::VoiceChat_f( const idCmdArgs& args )
|
|
{
|
|
gameLocal.mpGame.VoiceChat( args, false );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::VoiceChatTeam
|
|
================
|
|
*/
|
|
void idMultiplayerGame::VoiceChatTeam_f( const idCmdArgs& args )
|
|
{
|
|
gameLocal.mpGame.VoiceChat( args, true );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::VoiceChat
|
|
================
|
|
*/
|
|
void idMultiplayerGame::VoiceChat( const idCmdArgs& args, bool team )
|
|
{
|
|
idBitMsg outMsg;
|
|
byte msgBuf[128];
|
|
const char* voc;
|
|
const idDict* spawnArgs;
|
|
const idKeyValue* keyval;
|
|
int index;
|
|
|
|
if( !common->IsMultiplayer() )
|
|
{
|
|
common->Printf( "clientVoiceChat: only valid in multiplayer\n" );
|
|
return;
|
|
}
|
|
if( args.Argc() != 2 )
|
|
{
|
|
common->Printf( "clientVoiceChat: bad args\n" );
|
|
return;
|
|
}
|
|
// throttle
|
|
if( gameLocal.realClientTime < voiceChatThrottle )
|
|
{
|
|
return;
|
|
}
|
|
if( gameLocal.GetLocalPlayer() == NULL )
|
|
{
|
|
return;
|
|
}
|
|
|
|
voc = args.Argv( 1 );
|
|
|
|
spawnArgs = &gameLocal.GetLocalPlayer()->spawnArgs;
|
|
|
|
keyval = spawnArgs->MatchPrefix( "snd_voc_", NULL );
|
|
index = 0;
|
|
while( keyval )
|
|
{
|
|
if( !keyval->GetValue().Icmp( voc ) )
|
|
{
|
|
break;
|
|
}
|
|
keyval = spawnArgs->MatchPrefix( "snd_voc_", keyval );
|
|
index++;
|
|
}
|
|
if( !keyval )
|
|
{
|
|
common->Printf( "Voice command not found: %s\n", voc );
|
|
return;
|
|
}
|
|
voiceChatThrottle = gameLocal.realClientTime + 1000;
|
|
|
|
outMsg.InitWrite( msgBuf, sizeof( msgBuf ) );
|
|
outMsg.WriteLong( index );
|
|
outMsg.WriteBits( team ? 1 : 0, 1 );
|
|
session->GetActingGameStateLobbyBase().SendReliableToHost( GAME_RELIABLE_MESSAGE_VCHAT, outMsg );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::ProcessVoiceChat
|
|
================
|
|
*/
|
|
void idMultiplayerGame::ProcessVoiceChat( int clientNum, bool team, int index )
|
|
{
|
|
const idDict* spawnArgs;
|
|
const idKeyValue* keyval;
|
|
idStr name;
|
|
idStr snd_key;
|
|
idStr text_key;
|
|
idPlayer* p;
|
|
|
|
p = static_cast< idPlayer* >( gameLocal.entities[ clientNum ] );
|
|
if( !( p && p->IsType( idPlayer::Type ) ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if( p->spectating )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// lookup the sound def
|
|
spawnArgs = &p->spawnArgs;
|
|
|
|
keyval = spawnArgs->MatchPrefix( "snd_voc_", NULL );
|
|
while( index > 0 && keyval )
|
|
{
|
|
keyval = spawnArgs->MatchPrefix( "snd_voc_", keyval );
|
|
index--;
|
|
}
|
|
if( !keyval )
|
|
{
|
|
common->DPrintf( "ProcessVoiceChat: unknown chat index %d\n", index );
|
|
return;
|
|
}
|
|
snd_key = keyval->GetKey();
|
|
name = session->GetActingGameStateLobbyBase().GetLobbyUserName( gameLocal.lobbyUserIDs[ clientNum ] );
|
|
sprintf( text_key, "txt_%s", snd_key.Right( snd_key.Length() - 4 ).c_str() );
|
|
if( team || gameState == COUNTDOWN || gameState == GAMEREVIEW )
|
|
{
|
|
ProcessChatMessage( clientNum, team, name, spawnArgs->GetString( text_key ), spawnArgs->GetString( snd_key ) );
|
|
}
|
|
else
|
|
{
|
|
p->StartSound( snd_key, SND_CHANNEL_ANY, 0, true, NULL );
|
|
ProcessChatMessage( clientNum, team, name, spawnArgs->GetString( text_key ), NULL );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::ServerWriteInitialReliableMessages
|
|
================
|
|
*/
|
|
void idMultiplayerGame::ServerWriteInitialReliableMessages( int clientNum, lobbyUserID_t lobbyUserID )
|
|
{
|
|
idBitMsg outMsg;
|
|
byte msgBuf[ MAX_GAME_MESSAGE_SIZE ];
|
|
|
|
outMsg.InitWrite( msgBuf, sizeof( msgBuf ) );
|
|
outMsg.BeginWriting();
|
|
// send the game state and start time
|
|
outMsg.WriteByte( gameState );
|
|
outMsg.WriteLong( matchStartedTime );
|
|
outMsg.WriteShort( startFragLimit );
|
|
// send the powerup states and the spectate states
|
|
for( int i = 0; i < gameLocal.numClients; i++ )
|
|
{
|
|
idEntity* ent = gameLocal.entities[ i ];
|
|
if( i != clientNum && ent && ent->IsType( idPlayer::Type ) )
|
|
{
|
|
outMsg.WriteByte( i );
|
|
outMsg.WriteBits( static_cast< idPlayer* >( ent )->inventory.powerups, 15 );
|
|
outMsg.WriteBits( static_cast< idPlayer* >( ent )->spectating, 1 );
|
|
}
|
|
}
|
|
outMsg.WriteByte( MAX_CLIENTS );
|
|
session->GetActingGameStateLobbyBase().SendReliableToLobbyUser( lobbyUserID, GAME_RELIABLE_MESSAGE_STARTSTATE, outMsg );
|
|
|
|
// warmup time
|
|
if( gameState == COUNTDOWN )
|
|
{
|
|
outMsg.BeginWriting();
|
|
outMsg.WriteByte( GAME_RELIABLE_MESSAGE_WARMUPTIME );
|
|
outMsg.WriteLong( warmupEndTime );
|
|
session->GetActingGameStateLobbyBase().SendReliableToLobbyUser( lobbyUserID, GAME_RELIABLE_MESSAGE_WARMUPTIME, outMsg );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::ClientReadStartState
|
|
================
|
|
*/
|
|
void idMultiplayerGame::ClientReadStartState( const idBitMsg& msg )
|
|
{
|
|
// read the state in preparation for reading snapshot updates
|
|
gameState = ( idMultiplayerGame::gameState_t )msg.ReadByte();
|
|
matchStartedTime = msg.ReadLong( );
|
|
startFragLimit = msg.ReadShort( );
|
|
|
|
int client;
|
|
while( ( client = msg.ReadByte() ) != MAX_CLIENTS )
|
|
{
|
|
|
|
// Do not process players that are not here.
|
|
if( gameLocal.entities[ client ] == NULL )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
assert( gameLocal.entities[ client ] && gameLocal.entities[ client ]->IsType( idPlayer::Type ) );
|
|
int powerup = msg.ReadBits( 15 );
|
|
for( int i = 0; i < MAX_POWERUPS; i++ )
|
|
{
|
|
if( powerup & ( 1 << i ) )
|
|
{
|
|
static_cast< idPlayer* >( gameLocal.entities[ client ] )->GivePowerUp( i, 0, ITEM_GIVE_FEEDBACK | ITEM_GIVE_UPDATE_STATE );
|
|
}
|
|
}
|
|
bool spectate = ( msg.ReadBits( 1 ) != 0 );
|
|
static_cast< idPlayer* >( gameLocal.entities[ client ] )->Spectate( spectate );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::ClientReadWarmupTime
|
|
================
|
|
*/
|
|
void idMultiplayerGame::ClientReadWarmupTime( const idBitMsg& msg )
|
|
{
|
|
warmupEndTime = msg.ReadLong();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::ClientReadWarmupTime
|
|
================
|
|
*/
|
|
void idMultiplayerGame::ClientReadMatchStartedTime( const idBitMsg& msg )
|
|
{
|
|
matchStartedTime = msg.ReadLong();
|
|
}
|
|
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::ClientReadAchievementUnlock
|
|
================
|
|
*/
|
|
void idMultiplayerGame::ClientReadAchievementUnlock( const idBitMsg& msg )
|
|
{
|
|
|
|
int playerid = msg.ReadByte();
|
|
achievement_t achieve = ( achievement_t )msg.ReadByte();
|
|
|
|
idPlayer* player = static_cast<idPlayer*>( gameLocal.entities[ playerid ] );
|
|
|
|
if( player != NULL )
|
|
{
|
|
|
|
idLib::Printf( "Client Receiving Achievement\n" );
|
|
player->GetAchievementManager().EventCompletesAchievement( achieve );
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::IsGametypeTeamBased
|
|
================
|
|
*/
|
|
bool idMultiplayerGame::IsGametypeTeamBased() /* CTF */
|
|
{
|
|
switch( gameLocal.gameType )
|
|
{
|
|
case GAME_SP:
|
|
case GAME_DM:
|
|
case GAME_TOURNEY:
|
|
case GAME_LASTMAN:
|
|
return false;
|
|
case GAME_CTF:
|
|
case GAME_TDM:
|
|
return true;
|
|
|
|
default:
|
|
assert( !"Add support for your new gametype here." );
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::IsGametypeFlagBased
|
|
================
|
|
*/
|
|
bool idMultiplayerGame::IsGametypeFlagBased()
|
|
{
|
|
switch( gameLocal.gameType )
|
|
{
|
|
case GAME_SP:
|
|
case GAME_DM:
|
|
case GAME_TOURNEY:
|
|
case GAME_LASTMAN:
|
|
case GAME_TDM:
|
|
return false;
|
|
case GAME_CTF:
|
|
return true;
|
|
default:
|
|
assert( !"Add support for your new gametype here." );
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::GetTeamFlag
|
|
================
|
|
*/
|
|
idItemTeam* idMultiplayerGame::GetTeamFlag( int team )
|
|
{
|
|
assert( team == 0 || team == 1 );
|
|
|
|
if( !IsGametypeFlagBased() || ( team != 0 && team != 1 ) ) /* CTF */
|
|
return NULL;
|
|
|
|
// TODO : just call on map start
|
|
FindTeamFlags();
|
|
|
|
return teamFlags[team];
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::GetTeamFlag
|
|
================
|
|
*/
|
|
void idMultiplayerGame::FindTeamFlags()
|
|
{
|
|
const char* flagDefs[2] =
|
|
{
|
|
"team_CTF_redflag",
|
|
"team_CTF_blueflag"
|
|
};
|
|
|
|
for( int i = 0; i < 2; i++ )
|
|
{
|
|
idEntity* entity = gameLocal.FindEntityUsingDef( NULL, flagDefs[i] );
|
|
do
|
|
{
|
|
if( entity == NULL )
|
|
return;
|
|
|
|
idItemTeam* flag = static_cast<idItemTeam*>( entity );
|
|
|
|
if( flag->team == i )
|
|
{
|
|
teamFlags[i] = flag;
|
|
break;
|
|
}
|
|
|
|
entity = gameLocal.FindEntityUsingDef( entity, flagDefs[i] );
|
|
}
|
|
while( entity );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::GetFlagStatus
|
|
================
|
|
*/
|
|
flagStatus_t idMultiplayerGame::GetFlagStatus( int team )
|
|
{
|
|
//assert( IsGametypeFlagBased() );
|
|
|
|
idItemTeam* teamFlag = GetTeamFlag( team );
|
|
//assert( teamFlag != NULL );
|
|
|
|
if( teamFlag != NULL )
|
|
{
|
|
if( teamFlag->carried == false && teamFlag->dropped == false )
|
|
return FLAGSTATUS_INBASE;
|
|
|
|
if( teamFlag->carried == true )
|
|
return FLAGSTATUS_TAKEN;
|
|
|
|
if( teamFlag->carried == false && teamFlag->dropped == true )
|
|
return FLAGSTATUS_STRAY;
|
|
}
|
|
|
|
//assert( !"Invalid flag state." );
|
|
return FLAGSTATUS_NONE;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::SetFlagMsgs
|
|
================
|
|
*/
|
|
void idMultiplayerGame::SetFlagMsg( bool b )
|
|
{
|
|
flagMsgOn = b;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::IsFlagMsgOn
|
|
================
|
|
*/
|
|
bool idMultiplayerGame::IsFlagMsgOn()
|
|
{
|
|
return ( GetGameState() == WARMUP || GetGameState() == GAMEON || GetGameState() == SUDDENDEATH ) && flagMsgOn;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::ReloadScoreboard
|
|
================
|
|
*/
|
|
void idMultiplayerGame::ReloadScoreboard()
|
|
{
|
|
|
|
if( scoreboardManager != NULL )
|
|
{
|
|
scoreboardManager->Initialize( "scoreboard", common->SW() );
|
|
}
|
|
|
|
Precache();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMultiplayerGame::GetGameModes
|
|
================
|
|
*/
|
|
int idMultiplayerGame::GetGameModes( const char** * gameModes, const char** * gameModesDisplay )
|
|
{
|
|
|
|
bool defaultValue = true;
|
|
|
|
if( session->GetTitleStorageBool( "CTF_Enabled", defaultValue ) )
|
|
{
|
|
*gameModes = gameTypeNames_WithCTF;
|
|
*gameModesDisplay = gameTypeDisplayNames_WithCTF;
|
|
|
|
return GAME_COUNT;
|
|
}
|
|
else
|
|
{
|
|
|
|
*gameModes = gameTypeNames_WithoutCTF;
|
|
*gameModesDisplay = gameTypeDisplayNames_WithoutCTF;
|
|
|
|
return GAME_COUNT - 1;
|
|
}
|
|
}
|