dhewm3-sdk/game/MultiplayerGame.cpp
dhewg afebd7e1e5 Untangle the epic precompiled.h mess
Don't include the lazy precompiled.h everywhere, only what's
required for the compilation unit.
platform.h needs to be included instead to provide all essential
defines and types.
All includes use the relative path to the neo or the game
specific root.
Move all idlib related includes from idlib/Lib.h to precompiled.h.
precompiled.h still exists for the MFC stuff in tools/.
Add some missing header guards.
2018-08-20 01:46:28 +02:00

3413 lines
100 KiB
C++

/*
===========================================================================
Doom 3 GPL Source Code
Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code").
Doom 3 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 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 Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below.
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 "sys/platform.h"
#include "idlib/BitMsg.h"
#include "idlib/Str.h"
#include "idlib/LangDict.h"
#include "framework/async/NetworkSystem.h"
#include "framework/FileSystem.h"
#include "ui/UserInterface.h"
#include "gamesys/SysCvar.h"
#include "Player.h"
#include "Game_local.h"
#include "MultiplayerGame.h"
// could be a problem if players manage to go down sudden deaths till this .. oh well
#define LASTMAN_NOLIVES -20
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/feedback/voc_youwin.wav",
"sound/feedback/voc_youlose.wav",
"sound/feedback/fight.wav",
"sound/feedback/vote_now.wav",
"sound/feedback/vote_passed.wav",
"sound/feedback/vote_failed.wav",
"sound/feedback/three.wav",
"sound/feedback/two.wav",
"sound/feedback/one.wav",
"sound/feedback/sudden_death.wav",
};
// handy verbose
const char *idMultiplayerGame::GameStateStrings[] = {
"INACTIVE",
"WARMUP",
"COUNTDOWN",
"GAMEON",
"SUDDENDEATH",
"GAMEREVIEW",
"NEXTGAME"
};
const char *idMultiplayerGame::MPGuis[] = {
"guis/mphud.gui",
"guis/mpmain.gui",
"guis/mpmsgmode.gui",
"guis/netmenu.gui",
NULL
};
const char *idMultiplayerGame::ThrottleVars[] = {
"ui_spectate",
"ui_ready",
"ui_team",
NULL
};
const char *idMultiplayerGame::ThrottleVarsInEnglish[] = {
"#str_06738",
"#str_06737",
"#str_01991",
NULL
};
const int idMultiplayerGame::ThrottleDelay[] = {
8,
5,
5
};
/*
================
idMultiplayerGame::idMultiplayerGame
================
*/
idMultiplayerGame::idMultiplayerGame() {
scoreBoard = NULL;
spectateGui = NULL;
guiChat = NULL;
mainGui = NULL;
mapList = NULL;
msgmodeGui = NULL;
lastGameType = GAME_SP;
Clear();
}
/*
================
idMultiplayerGame::Shutdown
================
*/
void idMultiplayerGame::Shutdown( void ) {
Clear();
}
/*
================
idMultiplayerGame::SetMenuSkin
================
*/
void idMultiplayerGame::SetMenuSkin( void ) {
// skins
idStr str = cvarSystem->GetCVarString( "mod_validSkins" );
idStr uiSkin = cvarSystem->GetCVarString( "ui_skin" );
idStr skin;
int skinId = 1;
int count = 1;
while ( str.Length() ) {
int n = str.Find( ";" );
if ( n >= 0 ) {
skin = str.Left( n );
str = str.Right( str.Length() - n - 1 );
} else {
skin = str;
str = "";
}
if ( skin.Icmp( uiSkin ) == 0 ) {
skinId = count;
}
count++;
}
for ( int i = 0; i < count; i++ ) {
mainGui->SetStateInt( va( "skin%i", i+1 ), 0 );
}
mainGui->SetStateInt( va( "skin%i", skinId ), 1 );
}
/*
================
idMultiplayerGame::Reset
================
*/
void idMultiplayerGame::Reset() {
Clear();
assert( !scoreBoard && !spectateGui && !guiChat && !mainGui && !mapList );
scoreBoard = uiManager->FindGui( "guis/scoreboard.gui", true, false, true );
spectateGui = uiManager->FindGui( "guis/spectate.gui", true, false, true );
guiChat = uiManager->FindGui( "guis/chat.gui", true, false, true );
mainGui = uiManager->FindGui( "guis/mpmain.gui", true, false, true );
mapList = uiManager->AllocListGUI( );
mapList->Config( mainGui, "mapList" );
// set this GUI so that our Draw function is still called when it becomes the active/fullscreen GUI
mainGui->SetStateBool( "gameDraw", true );
mainGui->SetKeyBindingNames();
mainGui->SetStateInt( "com_machineSpec", cvarSystem->GetCVarInteger( "com_machineSpec" ) );
SetMenuSkin();
msgmodeGui = uiManager->FindGui( "guis/mpmsgmode.gui", true, false, true );
msgmodeGui->SetStateBool( "gameDraw", true );
ClearGuis();
ClearChatData();
warmupEndTime = 0;
}
/*
================
idMultiplayerGame::ServerClientConnect
================
*/
void idMultiplayerGame::ServerClientConnect( int clientNum ) {
memset( &playerState[ clientNum ], 0, sizeof( playerState[ clientNum ] ) );
}
/*
================
idMultiplayerGame::SpawnPlayer
================
*/
void idMultiplayerGame::SpawnPlayer( int clientNum ) {
bool ingame = playerState[ clientNum ].ingame;
memset( &playerState[ clientNum ], 0, sizeof( playerState[ clientNum ] ) );
if ( !gameLocal.isClient ) {
idPlayer *p = static_cast< idPlayer * >( gameLocal.entities[ clientNum ] );
p->spawnedTime = gameLocal.time;
if ( gameLocal.gameType == GAME_TDM ) {
SwitchToTeam( clientNum, -1, p->team );
}
p->tourneyRank = 0;
if ( gameLocal.gameType == GAME_TOURNEY && gameState == GAMEON ) {
p->tourneyRank++;
}
playerState[ clientNum ].ingame = ingame;
}
}
/*
================
idMultiplayerGame::Clear
================
*/
void idMultiplayerGame::Clear() {
int i;
gameState = INACTIVE;
nextState = INACTIVE;
pingUpdateTime = 0;
vote = VOTE_NONE;
voteTimeOut = 0;
voteExecTime = 0;
nextStateSwitch = 0;
matchStartedTime = 0;
currentTourneyPlayer[ 0 ] = -1;
currentTourneyPlayer[ 1 ] = -1;
one = two = three = false;
memset( &playerState, 0 , sizeof( playerState ) );
lastWinner = -1;
currentMenu = 0;
bCurrentMenuMsg = false;
nextMenu = 0;
pureReady = false;
scoreBoard = NULL;
spectateGui = NULL;
guiChat = NULL;
mainGui = NULL;
msgmodeGui = NULL;
if ( mapList ) {
uiManager->FreeListGUI( mapList );
mapList = NULL;
}
fragLimitTimeout = 0;
memset( &switchThrottle, 0, sizeof( switchThrottle ) );
voiceChatThrottle = 0;
for ( i = 0; i < NUM_CHAT_NOTIFY; i++ ) {
chatHistory[ i ].line.Clear();
}
warmupText.Clear();
voteValue.Clear();
voteString.Clear();
startFragLimit = -1;
}
/*
================
idMultiplayerGame::ClearGuis
================
*/
void idMultiplayerGame::ClearGuis() {
int i;
for ( i = 0; i < MAX_CLIENTS; i++ ) {
scoreBoard->SetStateString( va( "player%i",i+1 ), "" );
scoreBoard->SetStateString( va( "player%i_score", i+1 ), "" );
scoreBoard->SetStateString( va( "player%i_tdm_tscore", i+1 ), "" );
scoreBoard->SetStateString( va( "player%i_tdm_score", i+1 ), "" );
scoreBoard->SetStateString( va( "player%i_wins", i+1 ), "" );
scoreBoard->SetStateString( va( "player%i_status", i+1 ), "" );
scoreBoard->SetStateInt( va( "rank%i", i+1 ), 0 );
scoreBoard->SetStateInt( "rank_self", 0 );
idPlayer *player = static_cast<idPlayer *>( gameLocal.entities[ i ] );
if ( !player || !player->hud ) {
continue;
}
player->hud->SetStateString( va( "player%i",i+1 ), "" );
player->hud->SetStateString( va( "player%i_score", i+1 ), "" );
player->hud->SetStateString( va( "player%i_ready", i+1 ), "" );
scoreBoard->SetStateInt( va( "rank%i", i+1 ), 0 );
player->hud->SetStateInt( "rank_self", 0 );
}
}
/*
================
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 ( gameLocal.gameType == GAME_TDM ) {
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::UpdateScoreboard
================
*/
void idMultiplayerGame::UpdateScoreboard( idUserInterface *scoreBoard, idPlayer *player ) {
int i, j, iline, k;
idStr gameinfo;
idStr livesinfo;
idStr timeinfo;
idEntity *ent;
idPlayer *p;
int value;
scoreBoard->SetStateString( "scoretext", gameLocal.gameType == GAME_LASTMAN ? common->GetLanguageDict()->GetString( "#str_04242" ) : common->GetLanguageDict()->GetString( "#str_04243" ) );
iline = 0; // the display lines
if ( gameState != WARMUP ) {
for ( i = 0; i < numRankedPlayers; i++ ) {
// ranked player
iline++;
scoreBoard->SetStateString( va( "player%i", iline ), rankedPlayers[ i ]->GetUserInfo()->GetString( "ui_name" ) );
if ( gameLocal.gameType == GAME_TDM ) {
value = idMath::ClampInt( MP_PLAYER_MINFRAGS, MP_PLAYER_MAXFRAGS, playerState[ rankedPlayers[ i ]->entityNumber ].fragCount );
scoreBoard->SetStateInt( va( "player%i_tdm_score", iline ), value );
value = idMath::ClampInt( MP_PLAYER_MINFRAGS, MP_PLAYER_MAXFRAGS, playerState[ rankedPlayers[ i ]->entityNumber ].teamFragCount );
scoreBoard->SetStateString( va( "player%i_tdm_tscore", iline ), va( "/ %i", value ) );
scoreBoard->SetStateString( va( "player%i_score", iline ), "" );
} else {
value = idMath::ClampInt( MP_PLAYER_MINFRAGS, MP_PLAYER_MAXFRAGS, playerState[ rankedPlayers[ i ]->entityNumber ].fragCount );
scoreBoard->SetStateInt( va( "player%i_score", iline ), value );
scoreBoard->SetStateString( va( "player%i_tdm_tscore", iline ), "" );
scoreBoard->SetStateString( va( "player%i_tdm_score", iline ), "" );
}
value = idMath::ClampInt( 0, MP_PLAYER_MAXWINS, playerState[ rankedPlayers[ i ]->entityNumber ].wins );
scoreBoard->SetStateInt( va( "player%i_wins", iline ), value );
scoreBoard->SetStateInt( va( "player%i_ping", iline ), playerState[ rankedPlayers[ i ]->entityNumber ].ping );
// set the color band
scoreBoard->SetStateInt( va( "rank%i", iline ), 1 );
UpdateRankColor( scoreBoard, "rank%i_color%i", iline, rankedPlayers[ i ]->colorBar );
if ( rankedPlayers[ i ] == player ) {
// highlight who we are
scoreBoard->SetStateInt( "rank_self", iline );
}
}
}
// if warmup, this draws everyone, otherwise it goes over spectators only
// when doing warmup we loop twice to draw ready/not ready first *then* spectators
// NOTE: in tourney, shows spectators according to their playing rank order?
for ( k = 0; k < ( gameState == WARMUP ? 2 : 1 ); k++ ) {
for ( i = 0; i < MAX_CLIENTS; i++ ) {
ent = gameLocal.entities[ i ];
if ( !ent || !ent->IsType( idPlayer::Type ) ) {
continue;
}
if ( gameState != WARMUP ) {
// check he's not covered by ranks already
for ( j = 0; j < numRankedPlayers; j++ ) {
if ( ent == rankedPlayers[ j ] ) {
break;
}
}
if ( j != numRankedPlayers ) {
continue;
}
}
p = static_cast< idPlayer * >( ent );
if ( gameState == WARMUP ) {
if ( k == 0 && p->spectating ) {
continue;
}
if ( k == 1 && !p->spectating ) {
continue;
}
}
iline++;
if ( !playerState[ i ].ingame ) {
scoreBoard->SetStateString( va( "player%i", iline ), common->GetLanguageDict()->GetString( "#str_04244" ) );
scoreBoard->SetStateString( va( "player%i_score", iline ), common->GetLanguageDict()->GetString( "#str_04245" ) );
// no color band
scoreBoard->SetStateInt( va( "rank%i", iline ), 0 );
} else {
scoreBoard->SetStateString( va( "player%i", iline ), gameLocal.userInfo[ i ].GetString( "ui_name" ) );
if ( gameState == WARMUP ) {
if ( p->spectating ) {
scoreBoard->SetStateString( va( "player%i_score", iline ), common->GetLanguageDict()->GetString( "#str_04246" ) );
// no color band
scoreBoard->SetStateInt( va( "rank%i", iline ), 0 );
} else {
scoreBoard->SetStateString( va( "player%i_score", iline ), p->IsReady() ? common->GetLanguageDict()->GetString( "#str_04247" ) : common->GetLanguageDict()->GetString( "#str_04248" ) );
// set the color band
scoreBoard->SetStateInt( va( "rank%i", iline ), 1 );
UpdateRankColor( scoreBoard, "rank%i_color%i", iline, p->colorBar );
}
} else {
if ( gameLocal.gameType == GAME_LASTMAN && playerState[ i ].fragCount == LASTMAN_NOLIVES ) {
scoreBoard->SetStateString( va( "player%i_score", iline ), common->GetLanguageDict()->GetString( "#str_06736" ) );
// set the color band
scoreBoard->SetStateInt( va( "rank%i", iline ), 1 );
UpdateRankColor( scoreBoard, "rank%i_color%i", iline, p->colorBar );
} else {
scoreBoard->SetStateString( va( "player%i_score", iline ), common->GetLanguageDict()->GetString( "#str_04246" ) );
// no color band
scoreBoard->SetStateInt( va( "rank%i", iline ), 0 );
}
}
}
scoreBoard->SetStateString( va( "player%i_tdm_tscore", iline ), "" );
scoreBoard->SetStateString( va( "player%i_tdm_score", iline ), "" );
scoreBoard->SetStateString( va( "player%i_wins", iline ), "" );
scoreBoard->SetStateInt( va( "player%i_ping", iline ), playerState[ i ].ping );
if ( i == player->entityNumber ) {
// highlight who we are
scoreBoard->SetStateInt( "rank_self", iline );
}
}
}
// clear remaining lines (empty slots)
iline++;
while ( iline < 5 ) {
scoreBoard->SetStateString( va( "player%i", iline ), "" );
scoreBoard->SetStateString( va( "player%i_score", iline ), "" );
scoreBoard->SetStateString( va( "player%i_tdm_tscore", iline ), "" );
scoreBoard->SetStateString( va( "player%i_tdm_score", iline ), "" );
scoreBoard->SetStateString( va( "player%i_wins", iline ), "" );
scoreBoard->SetStateString( va( "player%i_ping", iline ), "" );
scoreBoard->SetStateInt( va( "rank%i", iline ), 0 );
iline++;
}
gameinfo = va( "%s: %s", common->GetLanguageDict()->GetString( "#str_02376" ), gameLocal.serverInfo.GetString( "si_gameType" ) );
if ( gameLocal.gameType == GAME_LASTMAN ) {
if ( gameState == GAMEON || gameState == SUDDENDEATH ) {
livesinfo = va( "%s: %i", common->GetLanguageDict()->GetString( "#str_04264" ), startFragLimit );
} else {
livesinfo = va( "%s: %i", common->GetLanguageDict()->GetString( "#str_04264" ), gameLocal.serverInfo.GetInt( "si_fragLimit" ) );
}
} else {
livesinfo = va( "%s: %i", common->GetLanguageDict()->GetString( "#str_01982" ), gameLocal.serverInfo.GetInt( "si_fragLimit" ) );
}
if ( gameLocal.serverInfo.GetInt( "si_timeLimit" ) > 0 ) {
timeinfo = va( "%s: %i", common->GetLanguageDict()->GetString( "#str_01983" ), gameLocal.serverInfo.GetInt( "si_timeLimit" ) );
} else {
timeinfo = va("%s", common->GetLanguageDict()->GetString( "#str_07209" ));
}
scoreBoard->SetStateString( "gameinfo", gameinfo );
scoreBoard->SetStateString( "livesinfo", livesinfo );
scoreBoard->SetStateString( "timeinfo", timeinfo );
scoreBoard->Redraw( gameLocal.time );
}
/*
================
idMultiplayerGame::GameTime
================
*/
const char *idMultiplayerGame::GameTime() {
static char buff[16];
int m, s, t, ms;
if ( gameState == COUNTDOWN ) {
ms = warmupEndTime - gameLocal.realClientTime;
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.time - matchStartedTime );
} else {
ms = gameLocal.time - 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 team[ 2 ];
int clients = NumActualClients( false, &team[ 0 ] );
if ( gameLocal.gameType == GAME_TDM ) {
return clients >= 2 && team[ 0 ] && team[ 1 ];
} else {
return clients >= 2;
}
}
/*
================
idMultiplayerGame::AllPlayersReady
================
*/
bool idMultiplayerGame::AllPlayersReady() {
int i;
idEntity *ent;
idPlayer *p;
int team[ 2 ];
if ( NumActualClients( false, &team[ 0 ] ) <= 1 ) {
return false;
}
if ( gameLocal.gameType == GAME_TDM ) {
if ( !team[ 0 ] || !team[ 1 ] ) {
return false;
}
}
if ( !gameLocal.serverInfo.GetBool( "si_warmup" ) ) {
return true;
}
for( i = 0; i < gameLocal.numClients; i++ ) {
if ( gameLocal.gameType == GAME_TOURNEY && i != currentTourneyPlayer[ 0 ] && i != currentTourneyPlayer[ 1 ] ) {
continue;
}
ent = gameLocal.entities[ i ];
if ( !ent || !ent->IsType( idPlayer::Type ) ) {
continue;
}
p = static_cast< idPlayer * >( ent );
if ( CanPlay( p ) && !p->IsReady() ) {
return false;
}
team[ p->team ]++;
}
return true;
}
/*
================
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;
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 ( gameLocal.gameType == GAME_TDM ) {
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.time >= matchStartedTime + timeLimit * 60000 ) {
return true;
}
}
return false;
}
/*
================
idMultiplayerGame::FragLeader
return the current winner ( or a player from the winning team )
NULL if even
================
*/
idPlayer *idMultiplayerGame::FragLeader( void ) {
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 = ( gameLocal.gameType == GAME_TDM ) ? playerState[i].teamFragCount : playerState[i].fragCount;
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 ( gameLocal.gameType == GAME_TDM ) {
teamLead[ p->team ] = true;
}
}
}
if ( gameLocal.gameType != GAME_TDM ) {
// 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 ( gameLocal.gameType == GAME_TDM ) {
if ( player == winner || ( player != winner && player->team == winner->team ) ) {
playerState[ i ].wins++;
PlayGlobalSound( player->entityNumber, SND_YOUWIN );
} else {
PlayGlobalSound( player->entityNumber, SND_YOULOSE );
}
} else if ( gameLocal.gameType == GAME_LASTMAN ) {
if ( player == winner ) {
playerState[ i ].wins++;
PlayGlobalSound( player->entityNumber, SND_YOUWIN );
} else if ( !player->wantSpectate ) {
PlayGlobalSound( player->entityNumber, SND_YOULOSE );
}
} else if ( gameLocal.gameType == GAME_TOURNEY ) {
if ( player == winner ) {
playerState[ i ].wins++;
PlayGlobalSound( player->entityNumber, SND_YOUWIN );
} else if ( i == currentTourneyPlayer[ 0 ] || i == currentTourneyPlayer[ 1 ] ) {
PlayGlobalSound( player->entityNumber, SND_YOULOSE );
}
} else {
if ( player == winner ) {
playerState[i].wins++;
PlayGlobalSound( player->entityNumber, SND_YOUWIN );
} else if ( !player->wantSpectate ) {
PlayGlobalSound( player->entityNumber, SND_YOULOSE );
}
}
}
}
if ( winner ) {
lastWinner = winner->entityNumber;
} else {
lastWinner = -1;
}
}
/*
================
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( !gameLocal.isClient );
if ( killer ) {
if ( gameLocal.gameType == GAME_LASTMAN ) {
playerState[ dead->entityNumber ].fragCount--;
} else if ( gameLocal.gameType == GAME_TDM ) {
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( -1, MSG_SUICIDE, dead->entityNumber );
} else if ( killer ) {
if ( telefrag ) {
PrintMessageEvent( -1, MSG_TELEFRAGGED, dead->entityNumber, killer->entityNumber );
} else if ( gameLocal.gameType == GAME_TDM && dead->team == killer->team ) {
PrintMessageEvent( -1, MSG_KILLEDTEAM, dead->entityNumber, killer->entityNumber );
} else {
PrintMessageEvent( -1, MSG_KILLED, dead->entityNumber, killer->entityNumber );
}
} else {
PrintMessageEvent( -1, 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=%d tks=%d", team, playerState[ clientNum ].fragCount, playerState[ clientNum ].teamFragCount );
return;
}
/*
================
idMultiplayerGame::PlayerVote
================
*/
void idMultiplayerGame::PlayerVote( int clientNum, playerVote_t vote ) {
playerState[ clientNum ].vote = vote;
}
/*
================
idMultiplayerGame::DumpTourneyLine
================
*/
void idMultiplayerGame::DumpTourneyLine( void ) {
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( !gameLocal.isClient );
gameLocal.DPrintf( "%s -> %s\n", GameStateStrings[ gameState ], GameStateStrings[ news ] );
switch( news ) {
case GAMEON: {
gameLocal.LocalMapRestart();
outMsg.Init( msgBuf, sizeof( msgBuf ) );
outMsg.WriteByte( GAME_RELIABLE_MESSAGE_RESTART );
outMsg.WriteBits( 0, 1 );
networkSystem->ServerSendReliableMessage( -1, outMsg );
PlayGlobalSound( -1, SND_FIGHT );
matchStartedTime = gameLocal.time;
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->SetLeader( false ); // don't carry the flag from previous games
if ( gameLocal.gameType == GAME_TOURNEY && currentTourneyPlayer[ 0 ] != i && currentTourneyPlayer[ 1 ] != i ) {
p->ServerSpectate( true );
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 );
if ( gameLocal.gameType == GAME_TOURNEY ) {
p->tourneyRank = 0;
}
}
}
if ( CanPlay( p ) ) {
p->lastManPresent = true;
} else {
p->lastManPresent = false;
}
}
cvarSystem->SetCVarString( "ui_ready", "Not Ready" );
switchThrottle[ 1 ] = 0; // passby the throttle
startFragLimit = gameLocal.serverInfo.GetInt( "si_fragLimit" );
break;
}
case GAMEREVIEW: {
nextState = INACTIVE; // used to abort a game. cancel out any upcoming state change
// set all players not ready and spectating
for( i = 0; i < gameLocal.numClients; i++ ) {
idEntity *ent = gameLocal.entities[ i ];
if ( !ent || !ent->IsType( idPlayer::Type ) ) {
continue;
}
static_cast< idPlayer *>( ent )->forcedReady = false;
static_cast<idPlayer *>(ent)->ServerSpectate( true );
}
UpdateWinsLosses( player );
break;
}
case SUDDENDEATH: {
PrintMessageEvent( -1, MSG_SUDDENDEATH );
PlayGlobalSound( -1, SND_SUDDENDEATH );
break;
}
case COUNTDOWN: {
idBitMsg outMsg;
byte msgBuf[ 128 ];
warmupEndTime = gameLocal.time + 1000*cvarSystem->GetCVarInteger( "g_countDown" );
outMsg.Init( msgBuf, sizeof( msgBuf ) );
outMsg.WriteByte( GAME_RELIABLE_MESSAGE_WARMUPTIME );
outMsg.WriteLong( warmupEndTime );
networkSystem->ServerSendReliableMessage( -1, outMsg );
break;
}
default:
break;
}
gameState = news;
}
/*
================
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;
// 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 ) {
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
}
}
/*
================
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( void ) {
int i, j, imax, max, globalmax = -1;
idPlayer *p;
assert( !gameLocal.isClient );
if ( gameLocal.gameType != GAME_TOURNEY ) {
return;
}
for ( j = 1; j <= gameLocal.numClients; j++ ) {
max = -1; imax = -1;
for ( i = 0; i < gameLocal.numClients; i++ ) {
if ( currentTourneyPlayer[ 0 ] == i || currentTourneyPlayer[ 1 ] == i ) {
continue;
}
p = static_cast< idPlayer * >( gameLocal.entities[ i ] );
if ( !p || p->wantSpectate ) {
continue;
}
if ( p->tourneyRank > max && ( globalmax == -1 || p->tourneyRank < globalmax ) ) {
imax = i;
max = p->tourneyRank;
}
}
if ( imax == -1 ) {
break;
}
idBitMsg outMsg;
byte msgBuf[1024];
outMsg.Init( msgBuf, sizeof( msgBuf ) );
outMsg.WriteByte( GAME_RELIABLE_MESSAGE_TOURNEYLINE );
outMsg.WriteByte( j );
networkSystem->ServerSendReliableMessage( imax, 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 );
} else {
ent = gameLocal.entities[ i ];
if ( ent && ent->IsType( idPlayer::Type ) ) {
player = static_cast<idPlayer *>( gameLocal.entities[ i ] );
player->ServerSpectate( true );
}
}
}
UpdateTourneyLine();
}
/*
================
idMultiplayerGame::ExecuteVote
the votes are checked for validity/relevance before they are started
we assume that they are still legit when reaching here
================
*/
void idMultiplayerGame::ExecuteVote( void ) {
bool needRestart;
switch ( vote ) {
case VOTE_RESTART:
gameLocal.MapRestart();
break;
case VOTE_TIMELIMIT:
si_timeLimit.SetInteger( atoi( voteValue ) );
needRestart = gameLocal.NeedRestart();
cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rescanSI" );
if ( needRestart ) {
cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "nextMap" );
}
break;
case VOTE_FRAGLIMIT:
si_fragLimit.SetInteger( atoi( voteValue ) );
needRestart = gameLocal.NeedRestart();
cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rescanSI" );
if ( needRestart ) {
cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "nextMap" );
}
break;
case VOTE_GAMETYPE:
si_gameType.SetString( voteValue );
gameLocal.MapRestart();
break;
case VOTE_KICK:
cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "kick %s", voteValue.c_str() ) );
break;
case VOTE_MAP:
si_map.SetString( voteValue );
gameLocal.MapRestart();
break;
case VOTE_SPECTATORS:
si_spectators.SetBool( !si_spectators.GetBool() );
needRestart = gameLocal.NeedRestart();
cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rescanSI" );
if ( needRestart ) {
cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "nextMap" );
}
break;
case VOTE_NEXTMAP:
cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "serverNextMap\n" );
break;
}
}
/*
================
idMultiplayerGame::CheckVote
================
*/
void idMultiplayerGame::CheckVote( void ) {
int numVoters, i;
if ( vote == VOTE_NONE ) {
return;
}
if ( voteExecTime ) {
if ( gameLocal.time > voteExecTime ) {
voteExecTime = 0;
ClientUpdateVote( VOTE_RESET, 0, 0 );
ExecuteVote();
vote = VOTE_NONE;
}
return;
}
// count voting players
numVoters = 0;
for ( i = 0; i < gameLocal.numClients; i++ ) {
idEntity *ent = gameLocal.entities[ i ];
if ( !ent || !ent->IsType( idPlayer::Type ) ) {
continue;
}
if ( playerState[ i ].vote != PLAYER_VOTE_NONE ) {
numVoters++;
}
}
if ( !numVoters ) {
// abort
vote = VOTE_NONE;
ClientUpdateVote( VOTE_ABORTED, yesVotes, noVotes );
return;
}
if ( yesVotes / numVoters > 0.5f ) {
ClientUpdateVote( VOTE_PASSED, yesVotes, noVotes );
voteExecTime = gameLocal.time + 2000;
return;
}
if ( gameLocal.time > voteTimeOut || noVotes / numVoters >= 0.5f ) {
ClientUpdateVote( VOTE_FAILED, yesVotes, noVotes );
vote = VOTE_NONE;
return;
}
}
/*
================
idMultiplayerGame::Warmup
================
*/
bool idMultiplayerGame::Warmup() {
return ( gameState == WARMUP );
}
/*
================
idMultiplayerGame::Run
================
*/
void idMultiplayerGame::Run() {
int i, timeLeft;
idPlayer *player;
int gameReviewPause;
assert( gameLocal.isMultiplayer );
assert( !gameLocal.isClient );
pureReady = true;
if ( gameState == INACTIVE ) {
lastGameType = gameLocal.gameType;
NewState( WARMUP );
}
CheckVote();
CheckRespawns();
if ( nextState != INACTIVE && gameLocal.time > nextStateSwitch ) {
NewState( nextState );
nextState = INACTIVE;
}
// don't update the ping every frame to save bandwidth
if ( gameLocal.time > pingUpdateTime ) {
for ( i = 0; i < gameLocal.numClients; i++ ) {
playerState[i].ping = networkSystem->ServerGetClientPing( i );
}
pingUpdateTime = gameLocal.time + 1000;
}
warmupText = "";
switch( gameState ) {
case GAMEREVIEW: {
if ( nextState == INACTIVE ) {
gameReviewPause = cvarSystem->GetCVarInteger( "g_gameReviewPause" );
nextState = NEXTGAME;
nextStateSwitch = gameLocal.time + 1000 * gameReviewPause;
}
break;
}
case NEXTGAME: {
if ( nextState == INACTIVE ) {
// game rotation, new map, gametype etc.
if ( gameLocal.NextMap() ) {
cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "serverMapRestart\n" );
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 ( AllPlayersReady() ) {
NewState( COUNTDOWN );
nextState = GAMEON;
nextStateSwitch = gameLocal.time + 1000 * cvarSystem->GetCVarInteger( "g_countDown" );
}
warmupText = "Warming up.. waiting for players to get ready";
one = two = three = false;
break;
}
case COUNTDOWN: {
timeLeft = ( nextStateSwitch - gameLocal.time ) / 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;
}
warmupText = va( "Match starts in %i", timeLeft );
break;
}
case GAMEON: {
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.time + FRAGLIMIT_DELAY;
}
if ( gameLocal.time > fragLimitTimeout ) {
NewState( GAMEREVIEW, player );
PrintMessageEvent( -1, MSG_FRAGLIMIT, player->entityNumber );
}
} 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( -1, MSG_HOLYSHIT );
fragLimitTimeout = 0;
NewState( SUDDENDEATH );
} else if ( TimeLimitHit() ) {
player = FragLeader();
if ( !player ) {
NewState( SUDDENDEATH );
} else {
NewState( GAMEREVIEW, player );
PrintMessageEvent( -1, MSG_TIMELIMIT );
}
}
}
break;
}
case SUDDENDEATH: {
player = FragLeader();
if ( player ) {
if ( !fragLimitTimeout ) {
common->DPrintf( "enter sudden death FragLeader timeout, player %d is leader\n", player->entityNumber );
fragLimitTimeout = gameLocal.time + FRAGLIMIT_DELAY;
}
if ( gameLocal.time > fragLimitTimeout ) {
NewState( GAMEREVIEW, player );
PrintMessageEvent( -1, MSG_FRAGLIMIT, player->entityNumber );
}
} else if ( fragLimitTimeout ) {
SuddenRespawn();
PrintMessageEvent( -1, MSG_HOLYSHIT );
fragLimitTimeout = 0;
}
break;
}
}
}
/*
================
idMultiplayerGame::UpdateMainGui
================
*/
void idMultiplayerGame::UpdateMainGui( void ) {
int i;
mainGui->SetStateInt( "readyon", gameState == WARMUP ? 1 : 0 );
mainGui->SetStateInt( "readyoff", gameState != WARMUP ? 1 : 0 );
idStr strReady = cvarSystem->GetCVarString( "ui_ready" );
if ( strReady.Icmp( "ready") == 0 ){
strReady = common->GetLanguageDict()->GetString( "#str_04248" );
} else {
strReady = common->GetLanguageDict()->GetString( "#str_04247" );
}
mainGui->SetStateString( "ui_ready", strReady );
mainGui->SetStateInt( "teamon", gameLocal.gameType == GAME_TDM ? 1 : 0 );
mainGui->SetStateInt( "teamoff", gameLocal.gameType != GAME_TDM ? 1 : 0 );
if ( gameLocal.gameType == GAME_TDM ) {
idPlayer *p = gameLocal.GetClientByNum( gameLocal.localClientNum );
mainGui->SetStateInt( "team", p->team );
}
// setup vote
mainGui->SetStateInt( "voteon", ( vote != VOTE_NONE && !voted ) ? 1 : 0 );
mainGui->SetStateInt( "voteoff", ( vote != VOTE_NONE && !voted ) ? 0 : 1 );
// last man hack
mainGui->SetStateInt( "isLastMan", gameLocal.gameType == GAME_LASTMAN ? 1 : 0 );
// send the current serverinfo values
for ( i = 0; i < gameLocal.serverInfo.GetNumKeyVals(); i++ ) {
const idKeyValue *keyval = gameLocal.serverInfo.GetKeyVal( i );
mainGui->SetStateString( keyval->GetKey(), keyval->GetValue() );
}
mainGui->StateChanged( gameLocal.time );
mainGui->SetStateString( "driver_prompt", "0" );
}
/*
================
idMultiplayerGame::StartMenu
================
*/
idUserInterface* idMultiplayerGame::StartMenu( void ) {
if ( mainGui == NULL ) {
return NULL;
}
int i, j;
if ( currentMenu ) {
currentMenu = 0;
cvarSystem->SetCVarBool( "ui_chat", false );
} else {
if ( nextMenu >= 2 ) {
currentMenu = nextMenu;
} else {
// for default and explicit
currentMenu = 1;
}
cvarSystem->SetCVarBool( "ui_chat", true );
}
nextMenu = 0;
gameLocal.sessionCommand = ""; // in case we used "game_startMenu" to trigger the menu
if ( currentMenu == 1 ) {
UpdateMainGui();
// UpdateMainGui sets most things, but it doesn't set these because
// it'd be pointless and/or harmful to set them every frame (for various reasons)
// Currenty the gui doesn't update properly if they change anyway, so we'll leave it like this.
// setup callvote
if ( vote == VOTE_NONE ) {
bool callvote_ok = false;
for ( i = 0; i < VOTE_COUNT; i++ ) {
// flag on means vote is denied, so default value 0 means all votes and -1 disables
mainGui->SetStateInt( va( "vote%d", i ), g_voteFlags.GetInteger() & ( 1 << i ) ? 0 : 1 );
if ( !( g_voteFlags.GetInteger() & ( 1 << i ) ) ) {
callvote_ok = true;
}
}
mainGui->SetStateInt( "callvote", callvote_ok );
} else {
mainGui->SetStateInt( "callvote", 2 );
}
// player kick data
idStr kickList;
j = 0;
for ( i = 0; i < gameLocal.numClients; i++ ) {
if ( gameLocal.entities[ i ] && gameLocal.entities[ i ]->IsType( idPlayer::Type ) ) {
if ( kickList.Length() ) {
kickList += ";";
}
kickList += va( "\"%d - %s\"", i, gameLocal.userInfo[ i ].GetString( "ui_name" ) );
kickVoteMap[ j ] = i;
j++;
}
}
mainGui->SetStateString( "kickChoices", kickList );
mainGui->SetStateString( "chattext", "" );
mainGui->Activate( true, gameLocal.time );
return mainGui;
} else if ( currentMenu == 2 ) {
// the setup is done in MessageMode
msgmodeGui->Activate( true, gameLocal.time );
cvarSystem->SetCVarBool( "ui_chat", true );
return msgmodeGui;
}
return NULL;
}
/*
================
idMultiplayerGame::DisableMenu
================
*/
void idMultiplayerGame::DisableMenu( void ) {
gameLocal.sessionCommand = ""; // in case we used "game_startMenu" to trigger the menu
if ( currentMenu == 1 ) {
mainGui->Activate( false, gameLocal.time );
} else if ( currentMenu == 2 ) {
msgmodeGui->Activate( false, gameLocal.time );
}
currentMenu = 0;
nextMenu = 0;
cvarSystem->SetCVarBool( "ui_chat", false );
}
/*
================
idMultiplayerGame::SetMapShot
================
*/
void idMultiplayerGame::SetMapShot( void ) {
char screenshot[ MAX_STRING_CHARS ];
int mapNum = mapList->GetSelection( NULL, 0 );
const idDict *dict = NULL;
if ( mapNum >= 0 ) {
dict = fileSystem->GetMapDecl( mapNum );
}
fileSystem->FindMapScreenshot( dict ? dict->GetString( "path" ) : "", screenshot, MAX_STRING_CHARS );
mainGui->SetStateString( "current_levelshot", screenshot );
}
/*
================
idMultiplayerGame::HandleGuiCommands
================
*/
const char* idMultiplayerGame::HandleGuiCommands( const char *_menuCommand ) {
idUserInterface *currentGui;
const char *voteValue;
int vote_clientNum;
int icmd;
idCmdArgs args;
if ( !_menuCommand[ 0 ] ) {
common->Printf( "idMultiplayerGame::HandleGuiCommands: empty command\n" );
return "continue";
}
assert( currentMenu );
if ( currentMenu == 1 ) {
currentGui = mainGui;
} else {
currentGui = msgmodeGui;
}
args.TokenizeString( _menuCommand, false );
for( icmd = 0; icmd < args.Argc(); ) {
const char *cmd = args.Argv( icmd++ );
if ( !idStr::Icmp( cmd, ";" ) ) {
continue;
} else if ( !idStr::Icmp( cmd, "video" ) ) {
idStr vcmd;
if ( args.Argc() - icmd >= 1 ) {
vcmd = args.Argv( icmd++ );
}
int oldSpec = cvarSystem->GetCVarInteger( "com_machineSpec" );
if ( idStr::Icmp( vcmd, "low" ) == 0 ) {
cvarSystem->SetCVarInteger( "com_machineSpec", 0 );
} else if ( idStr::Icmp( vcmd, "medium" ) == 0 ) {
cvarSystem->SetCVarInteger( "com_machineSpec", 1 );
} else if ( idStr::Icmp( vcmd, "high" ) == 0 ) {
cvarSystem->SetCVarInteger( "com_machineSpec", 2 );
} else if ( idStr::Icmp( vcmd, "ultra" ) == 0 ) {
cvarSystem->SetCVarInteger( "com_machineSpec", 3 );
} else if ( idStr::Icmp( vcmd, "recommended" ) == 0 ) {
cmdSystem->BufferCommandText( CMD_EXEC_NOW, "setMachineSpec\n" );
}
if ( oldSpec != cvarSystem->GetCVarInteger( "com_machineSpec" ) ) {
currentGui->SetStateInt( "com_machineSpec", cvarSystem->GetCVarInteger( "com_machineSpec" ) );
currentGui->StateChanged( gameLocal.realClientTime );
cmdSystem->BufferCommandText( CMD_EXEC_NOW, "execMachineSpec\n" );
}
if ( idStr::Icmp( vcmd, "restart" ) == 0) {
cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "vid_restart\n" );
}
continue;
} else if ( !idStr::Icmp( cmd, "play" ) ) {
if ( args.Argc() - icmd >= 1 ) {
idStr snd = args.Argv( icmd++ );
int channel = 1;
if ( snd.Length() == 1 ) {
channel = atoi( snd );
snd = args.Argv( icmd++ );
}
gameSoundWorld->PlayShaderDirectly( snd, channel );
}
continue;
} else if ( !idStr::Icmp( cmd, "mpSkin" ) ) {
idStr skin;
if ( args.Argc() - icmd >= 1 ) {
skin = args.Argv( icmd++ );
cvarSystem->SetCVarString( "ui_skin", skin );
}
SetMenuSkin();
continue;
} else if ( !idStr::Icmp( cmd, "quit" ) ) {
cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "quit\n" );
return NULL;
} else if ( !idStr::Icmp( cmd, "disconnect" ) ) {
cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "disconnect\n" );
return NULL;
} else if ( !idStr::Icmp( cmd, "close" ) ) {
DisableMenu( );
return NULL;
} else if ( !idStr::Icmp( cmd, "spectate" ) ) {
ToggleSpectate();
DisableMenu( );
return NULL;
} else if ( !idStr::Icmp( cmd, "chatmessage" ) ) {
int mode = currentGui->State().GetInt( "messagemode" );
if ( mode ) {
cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "sayTeam \"%s\"", currentGui->State().GetString( "chattext" ) ) );
} else {
cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "say \"%s\"", currentGui->State().GetString( "chattext" ) ) );
}
currentGui->SetStateString( "chattext", "" );
if ( currentMenu == 1 ) {
return "continue";
} else {
DisableMenu();
return NULL;
}
} else if ( !idStr::Icmp( cmd, "readytoggle" ) ) {
ToggleReady( );
DisableMenu( );
return NULL;
} else if ( !idStr::Icmp( cmd, "teamtoggle" ) ) {
ToggleTeam( );
DisableMenu( );
return NULL;
} else if ( !idStr::Icmp( cmd, "callVote" ) ) {
vote_flags_t voteIndex = (vote_flags_t)mainGui->State().GetInt( "voteIndex" );
if ( voteIndex == VOTE_MAP ) {
int mapNum = mapList->GetSelection( NULL, 0 );
if ( mapNum >= 0 ) {
const idDict *dict = fileSystem->GetMapDecl( mapNum );
if ( dict ) {
ClientCallVote( VOTE_MAP, dict->GetString( "path" ) );
}
}
} else {
voteValue = mainGui->State().GetString( "str_voteValue" );
if ( voteIndex == VOTE_KICK ) {
vote_clientNum = kickVoteMap[ atoi( voteValue ) ];
ClientCallVote( voteIndex, va( "%d", vote_clientNum ) );
} else {
ClientCallVote( voteIndex, voteValue );
}
}
DisableMenu();
return NULL;
} else if ( !idStr::Icmp( cmd, "voteyes" ) ) {
CastVote( gameLocal.localClientNum, true );
DisableMenu();
return NULL;
} else if ( !idStr::Icmp( cmd, "voteno" ) ) {
CastVote( gameLocal.localClientNum, false );
DisableMenu();
return NULL;
} else if ( !idStr::Icmp( cmd, "bind" ) ) {
if ( args.Argc() - icmd >= 2 ) {
idStr key = args.Argv( icmd++ );
idStr bind = args.Argv( icmd++ );
cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "bindunbindtwo \"%s\" \"%s\"", key.c_str(), bind.c_str() ) );
mainGui->SetKeyBindingNames();
}
continue;
} else if ( !idStr::Icmp( cmd, "clearbind" ) ) {
if ( args.Argc() - icmd >= 1 ) {
idStr bind = args.Argv( icmd++ );
cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "unbind \"%s\"", bind.c_str() ) );
mainGui->SetKeyBindingNames();
}
continue;
} else if ( !idStr::Icmp( cmd, "MAPScan" ) ) {
const char *gametype = gameLocal.serverInfo.GetString( "si_gameType" );
if ( gametype == NULL || *gametype == 0 || idStr::Icmp( gametype, "singleplayer" ) == 0 ) {
gametype = "Deathmatch";
}
int i, num;
idStr si_map = gameLocal.serverInfo.GetString("si_map");
const idDict *dict;
mapList->Clear();
mapList->SetSelection( -1 );
num = fileSystem->GetNumMaps();
for ( i = 0; i < num; i++ ) {
dict = fileSystem->GetMapDecl( i );
if ( dict ) {
// any MP gametype supported
bool isMP = false;
int igt = GAME_SP + 1;
while ( si_gameTypeArgs[ igt ] ) {
if ( dict->GetBool( si_gameTypeArgs[ igt ] ) ) {
isMP = true;
break;
}
igt++;
}
if ( isMP ) {
const char *mapName = dict->GetString( "name" );
if ( mapName[0] == '\0' ) {
mapName = dict->GetString( "path" );
}
mapName = common->GetLanguageDict()->GetString( mapName );
mapList->Add( i, mapName );
if ( !si_map.Icmp( dict->GetString( "path" ) ) ) {
mapList->SetSelection( mapList->Num() - 1 );
}
}
}
}
// set the current level shot
SetMapShot( );
return "continue";
} else if ( !idStr::Icmp( cmd, "click_maplist" ) ) {
SetMapShot( );
return "continue";
} else if ( strstr( cmd, "sound" ) == cmd ) {
// pass that back to the core, will know what to do with it
return _menuCommand;
}
common->Printf( "idMultiplayerGame::HandleGuiCommands: '%s' unknown\n", cmd );
}
return "continue";
}
/*
================
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;
}
}
UpdatePlayerRanks();
UpdateHud( viewPlayer, player->hud );
// use the hud of the local player
viewPlayer->playerView.RenderPlayerView( player->hud );
if ( currentMenu ) {
#if 0
// uncomment this if you want to track when players are in a menu
if ( !bCurrentMenuMsg ) {
idBitMsg outMsg;
byte msgBuf[ 128 ];
outMsg.Init( msgBuf, sizeof( msgBuf ) );
outMsg.WriteByte( GAME_RELIABLE_MESSAGE_MENU );
outMsg.WriteBits( 1, 1 );
networkSystem->ClientSendReliableMessage( outMsg );
bCurrentMenuMsg = true;
}
#endif
if ( player->wantSpectate ) {
mainGui->SetStateString( "spectext", common->GetLanguageDict()->GetString( "#str_04249" ) );
} else {
mainGui->SetStateString( "spectext", common->GetLanguageDict()->GetString( "#str_04250" ) );
}
DrawChat();
if ( currentMenu == 1 ) {
UpdateMainGui();
mainGui->Redraw( gameLocal.time );
} else {
msgmodeGui->Redraw( gameLocal.time );
}
} else {
#if 0
// uncomment this if you want to track when players are in a menu
if ( bCurrentMenuMsg ) {
idBitMsg outMsg;
byte msgBuf[ 128 ];
outMsg.Init( msgBuf, sizeof( msgBuf ) );
outMsg.WriteByte( GAME_RELIABLE_MESSAGE_MENU );
outMsg.WriteBits( 0, 1 );
networkSystem->ClientSendReliableMessage( outMsg );
bCurrentMenuMsg = false;
}
#endif
if ( player->spectating ) {
idStr spectatetext[ 2 ];
int ispecline = 0;
if ( gameLocal.gameType == GAME_TOURNEY ) {
if ( !player->wantSpectate ) {
spectatetext[ 0 ] = common->GetLanguageDict()->GetString( "#str_04246" );
switch ( player->tourneyLine ) {
case 0:
spectatetext[ 0 ] += common->GetLanguageDict()->GetString( "#str_07003" );
break;
case 1:
spectatetext[ 0 ] += common->GetLanguageDict()->GetString( "#str_07004" );
break;
case 2:
spectatetext[ 0 ] += common->GetLanguageDict()->GetString( "#str_07005" );
break;
default:
spectatetext[ 0 ] += va( common->GetLanguageDict()->GetString( "#str_07006" ), player->tourneyLine );
break;
}
ispecline++;
}
} else if ( gameLocal.gameType == GAME_LASTMAN ) {
if ( !player->wantSpectate ) {
spectatetext[ 0 ] = common->GetLanguageDict()->GetString( "#str_07007" );
ispecline++;
}
}
if ( player->spectator != player->entityNumber ) {
spectatetext[ ispecline ] = va( common->GetLanguageDict()->GetString( "#str_07008" ), viewPlayer->GetUserInfo()->GetString( "ui_name" ) );
} else if ( !ispecline ) {
spectatetext[ 0 ] = common->GetLanguageDict()->GetString( "#str_04246" );
}
spectateGui->SetStateString( "spectatetext0", spectatetext[0].c_str() );
spectateGui->SetStateString( "spectatetext1", spectatetext[1].c_str() );
if ( vote != VOTE_NONE ) {
spectateGui->SetStateString( "vote", va( "%s (y: %d n: %d)", voteString.c_str(), (int)yesVotes, (int)noVotes ) );
} else {
spectateGui->SetStateString( "vote", "" );
}
spectateGui->Redraw( gameLocal.time );
}
DrawChat();
DrawScoreBoard( player );
}
return true;
}
/*
================
idMultiplayerGame::UpdateHud
================
*/
void idMultiplayerGame::UpdateHud( idPlayer *player, idUserInterface *hud ) {
int i;
if ( !hud ) {
return;
}
hud->SetStateBool( "warmup", Warmup() );
if ( gameState == WARMUP ) {
if ( player->IsReady() ) {
hud->SetStateString( "warmuptext", common->GetLanguageDict()->GetString( "#str_04251" ) );
} else {
hud->SetStateString( "warmuptext", common->GetLanguageDict()->GetString( "#str_07002" ) );
}
}
hud->SetStateString( "timer", ( Warmup() ) ? common->GetLanguageDict()->GetString( "#str_04251" ) : ( gameState == SUDDENDEATH ) ? common->GetLanguageDict()->GetString( "#str_04252" ) : GameTime() );
if ( vote != VOTE_NONE ) {
hud->SetStateString( "vote", va( "%s (y: %d n: %d)", voteString.c_str(), (int)yesVotes, (int)noVotes ) );
} else {
hud->SetStateString( "vote", "" );
}
hud->SetStateInt( "rank_self", 0 );
if ( gameState == GAMEON ) {
for ( i = 0; i < numRankedPlayers; i++ ) {
if ( gameLocal.gameType == GAME_TDM ) {
hud->SetStateInt( va( "player%i_score", i+1 ), playerState[ rankedPlayers[ i ]->entityNumber ].teamFragCount );
} else {
hud->SetStateInt( va( "player%i_score", i+1 ), playerState[ rankedPlayers[ i ]->entityNumber ].fragCount );
}
hud->SetStateInt( va( "rank%i", i+1 ), 1 );
UpdateRankColor( hud, "rank%i_color%i", i+1, rankedPlayers[ i ]->colorBar );
if ( rankedPlayers[ i ] == player ) {
hud->SetStateInt( "rank_self", i+1 );
}
}
}
for ( i = ( gameState == GAMEON ? numRankedPlayers : 0 ) ; i < 5; i++ ) {
hud->SetStateString( va( "player%i", i+1 ), "" );
hud->SetStateString( va( "player%i_score", i+1 ), "" );
hud->SetStateInt( va( "rank%i", i+1 ), 0 );
}
}
/*
================
idMultiplayerGame::DrawScoreBoard
================
*/
void idMultiplayerGame::DrawScoreBoard( idPlayer *player ) {
if ( player->scoreBoardOpen || gameState == GAMEREVIEW ) {
if ( !playerState[ player->entityNumber ].scoreBoardUp ) {
scoreBoard->Activate( true, gameLocal.time );
playerState[ player->entityNumber ].scoreBoardUp = true;
}
UpdateScoreboard( scoreBoard, player );
} else {
if ( playerState[ player->entityNumber ].scoreBoardUp ) {
scoreBoard->Activate( false, gameLocal.time );
playerState[ player->entityNumber ].scoreBoardUp = false;
}
}
}
/*
===============
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 = gameLocal.time;
}
/*
===============
idMultiplayerGame::DrawChat
===============
*/
void idMultiplayerGame::DrawChat() {
int i, j;
if ( guiChat ) {
if ( gameLocal.time - 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 = gameLocal.time;
}
if ( chatDataUpdated ) {
j = 0;
i = chatHistoryIndex - chatHistorySize;
while ( i < chatHistoryIndex ) {
guiChat->SetStateString( va( "chat%i", j ), chatHistory[ i % NUM_CHAT_NOTIFY ].line );
// don't set alpha above 4, the gui only knows that
guiChat->SetStateInt( va( "alpha%i", j ), Min( 4, (int)chatHistory[ i % NUM_CHAT_NOTIFY ].fade ) );
j++; i++;
}
while ( j < NUM_CHAT_NOTIFY ) {
guiChat->SetStateString( va( "chat%i", j ), "" );
j++;
}
guiChat->Activate( true, gameLocal.time );
chatDataUpdated = false;
}
guiChat->Redraw( gameLocal.time );
}
}
const int ASYNC_PLAYER_FRAG_BITS = -idMath::BitsForInteger( MP_PLAYER_MAXFRAGS - MP_PLAYER_MINFRAGS ); // 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( idBitMsgDelta &msg ) const {
int i;
int value;
msg.WriteByte( gameState );
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.WriteBits( playerState[i].ingame, 1 );
}
}
/*
================
idMultiplayerGame::ReadFromSnapshot
================
*/
void idMultiplayerGame::ReadFromSnapshot( const idBitMsgDelta &msg ) {
int i;
gameState_t newState;
newState = (idMultiplayerGame::gameState_t)msg.ReadByte();
if ( newState != gameState ) {
gameLocal.DPrintf( "%s -> %s\n", GameStateStrings[ gameState ], GameStateStrings[ newState ] );
gameState = newState;
// these could be gathered in a BGNewState() kind of thing, as we have to do them in NewState as well
if ( gameState == GAMEON ) {
matchStartedTime = gameLocal.time;
cvarSystem->SetCVarString( "ui_ready", "Not Ready" );
switchThrottle[ 1 ] = 0; // passby the throttle
startFragLimit = gameLocal.serverInfo.GetInt( "si_fragLimit" );
}
}
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 );
playerState[i].ingame = msg.ReadBits( 1 ) != 0;
}
}
/*
================
idMultiplayerGame::PlayGlobalSound
================
*/
void idMultiplayerGame::PlayGlobalSound( int to, snd_evt_t evt, const char *shader ) {
const idSoundShader *shaderDecl;
if ( to == -1 || to == gameLocal.localClientNum ) {
if ( shader ) {
gameSoundWorld->PlayShaderDirectly( shader );
} else {
gameSoundWorld->PlayShaderDirectly( GlobalSoundStrings[ evt ] );
}
}
if ( !gameLocal.isClient ) {
idBitMsg outMsg;
byte msgBuf[1024];
outMsg.Init( msgBuf, sizeof( msgBuf ) );
if ( shader ) {
shaderDecl = declManager->FindSound( shader );
if ( !shaderDecl ) {
return;
}
outMsg.WriteByte( GAME_RELIABLE_MESSAGE_SOUND_INDEX );
outMsg.WriteLong( gameLocal.ServerRemapDecl( to, DECL_SOUND, shaderDecl->Index() ) );
} else {
outMsg.WriteByte( GAME_RELIABLE_MESSAGE_SOUND_EVENT );
outMsg.WriteByte( evt );
}
networkSystem->ServerSendReliableMessage( to, outMsg );
}
}
/*
================
idMultiplayerGame::PrintMessageEvent
================
*/
void idMultiplayerGame::PrintMessageEvent( int to, msg_evt_t evt, int parm1, int parm2 ) {
switch ( evt ) {
case MSG_SUICIDE:
assert( parm1 >= 0 );
AddChatLine( common->GetLanguageDict()->GetString( "#str_04293" ), gameLocal.userInfo[ parm1 ].GetString( "ui_name" ) );
break;
case MSG_KILLED:
assert( parm1 >= 0 && parm2 >= 0 );
AddChatLine( common->GetLanguageDict()->GetString( "#str_04292" ), gameLocal.userInfo[ parm1 ].GetString( "ui_name" ), gameLocal.userInfo[ parm2 ].GetString( "ui_name" ) );
break;
case MSG_KILLEDTEAM:
assert( parm1 >= 0 && parm2 >= 0 );
AddChatLine( common->GetLanguageDict()->GetString( "#str_04291" ), gameLocal.userInfo[ parm1 ].GetString( "ui_name" ), gameLocal.userInfo[ parm2 ].GetString( "ui_name" ) );
break;
case MSG_TELEFRAGGED:
assert( parm1 >= 0 && parm2 >= 0 );
AddChatLine( common->GetLanguageDict()->GetString( "#str_04290" ), gameLocal.userInfo[ parm1 ].GetString( "ui_name" ), gameLocal.userInfo[ parm2 ].GetString( "ui_name" ) );
break;
case MSG_DIED:
assert( parm1 >= 0 );
AddChatLine( common->GetLanguageDict()->GetString( "#str_04289" ), gameLocal.userInfo[ parm1 ].GetString( "ui_name" ) );
break;
case MSG_VOTE:
AddChatLine( common->GetLanguageDict()->GetString( "#str_04288" ) );
break;
case MSG_SUDDENDEATH:
AddChatLine( common->GetLanguageDict()->GetString( "#str_04287" ) );
break;
case MSG_FORCEREADY:
AddChatLine( common->GetLanguageDict()->GetString( "#str_04286" ), gameLocal.userInfo[ parm1 ].GetString( "ui_name" ) );
if ( gameLocal.entities[ parm1 ] && gameLocal.entities[ parm1 ]->IsType( idPlayer::Type ) ) {
static_cast< idPlayer * >( gameLocal.entities[ parm1 ] )->forcedReady = true;
}
break;
case MSG_JOINEDSPEC:
AddChatLine( common->GetLanguageDict()->GetString( "#str_04285" ), gameLocal.userInfo[ parm1 ].GetString( "ui_name" ) );
break;
case MSG_TIMELIMIT:
AddChatLine( common->GetLanguageDict()->GetString( "#str_04284" ) );
break;
case MSG_FRAGLIMIT:
if ( gameLocal.gameType == GAME_LASTMAN ) {
AddChatLine( common->GetLanguageDict()->GetString( "#str_04283" ), gameLocal.userInfo[ parm1 ].GetString( "ui_name" ) );
} else if ( gameLocal.gameType == GAME_TDM ) {
AddChatLine( common->GetLanguageDict()->GetString( "#str_04282" ), gameLocal.userInfo[ parm1 ].GetString( "ui_team" ) );
} else {
AddChatLine( common->GetLanguageDict()->GetString( "#str_04281" ), gameLocal.userInfo[ parm1 ].GetString( "ui_name" ) );
}
break;
case MSG_JOINTEAM:
AddChatLine( common->GetLanguageDict()->GetString( "#str_04280" ), gameLocal.userInfo[ parm1 ].GetString( "ui_name" ), parm2 ? common->GetLanguageDict()->GetString( "#str_02500" ) : common->GetLanguageDict()->GetString( "#str_02499" ) );
break;
case MSG_HOLYSHIT:
AddChatLine( common->GetLanguageDict()->GetString( "#str_06732" ) );
break;
default:
gameLocal.DPrintf( "PrintMessageEvent: unknown message type %d\n", evt );
return;
}
if ( !gameLocal.isClient ) {
idBitMsg outMsg;
byte msgBuf[1024];
outMsg.Init( msgBuf, sizeof( msgBuf ) );
outMsg.WriteByte( GAME_RELIABLE_MESSAGE_DB );
outMsg.WriteByte( evt );
outMsg.WriteByte( parm1 );
outMsg.WriteByte( parm2 );
networkSystem->ServerSendReliableMessage( to, outMsg );
}
}
/*
================
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( void ) {
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 ( gameLocal.gameType == GAME_TDM || p->IsLeader() ) {
#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 ||
gameLocal.gameType == GAME_TDM ) {
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 );
}
} else if ( gameState == WARMUP ) {
// make sure empty tourney slots get filled first
FillTourneySlots( );
if ( i == currentTourneyPlayer[ 0 ] || i == currentTourneyPlayer[ 1 ] ) {
p->ServerSpectate( false );
}
}
} else if ( gameLocal.gameType == GAME_LASTMAN ) {
if ( gameState == WARMUP || gameState == COUNTDOWN ) {
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 );
UpdateTourneyLine();
CheckAbortGame();
}
}
}
/*
================
idMultiplayerGame::ForceReady
================
*/
void idMultiplayerGame::ForceReady( ) {
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 );
if ( !p->IsReady() ) {
PrintMessageEvent( -1, MSG_FORCEREADY, i );
p->forcedReady = true;
}
}
}
/*
================
idMultiplayerGame::ForceReady_f
================
*/
void idMultiplayerGame::ForceReady_f( const idCmdArgs &args ) {
if ( !gameLocal.isMultiplayer || gameLocal.isClient ) {
common->Printf( "forceReady: multiplayer server only\n" );
return;
}
gameLocal.mpGame.ForceReady();
}
/*
================
idMultiplayerGame::DropWeapon
================
*/
void idMultiplayerGame::DropWeapon( int clientNum ) {
assert( !gameLocal.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 ( !gameLocal.isMultiplayer ) {
common->Printf( "clientDropWeapon: only valid in multiplayer\n" );
return;
}
idBitMsg outMsg;
byte msgBuf[128];
outMsg.Init( msgBuf, sizeof( msgBuf ) );
outMsg.WriteByte( GAME_RELIABLE_MESSAGE_DROPWEAPON );
networkSystem->ClientSendReliableMessage( outMsg );
}
/*
================
idMultiplayerGame::MessageMode_f
================
*/
void idMultiplayerGame::MessageMode_f( const idCmdArgs &args ) {
gameLocal.mpGame.MessageMode( args );
}
/*
================
idMultiplayerGame::MessageMode
================
*/
void idMultiplayerGame::MessageMode( const idCmdArgs &args ) {
const char *mode;
int imode;
if ( !gameLocal.isMultiplayer ) {
common->Printf( "clientMessageMode: only valid in multiplayer\n" );
return;
}
if ( !mainGui ) {
common->Printf( "no local client\n" );
return;
}
mode = args.Argv( 1 );
if ( !mode[ 0 ] ) {
imode = 0;
} else {
imode = atoi( mode );
}
msgmodeGui->SetStateString( "messagemode", imode ? "1" : "0" );
msgmodeGui->SetStateString( "chattext", "" );
nextMenu = 2;
// let the session know that we want our ingame main menu opened
gameLocal.sessionCommand = "game_startmenu";
}
/*
================
idMultiplayerGame::Vote_f
FIXME: voting from console
================
*/
void idMultiplayerGame::Vote_f( const idCmdArgs &args ) { }
/*
================
idMultiplayerGame::CallVote_f
FIXME: voting from console
================
*/
void idMultiplayerGame::CallVote_f( const idCmdArgs &args ) { }
/*
================
idMultiplayerGame::ServerStartVote
================
*/
void idMultiplayerGame::ServerStartVote( int clientNum, vote_flags_t voteIndex, const char *value ) {
int i;
assert( vote == VOTE_NONE );
// setup
yesVotes = 1;
noVotes = 0;
vote = voteIndex;
voteValue = value;
voteTimeOut = gameLocal.time + 20000;
// mark players allowed to vote - only current ingame players, players joining during vote will be ignored
for ( i = 0; i < gameLocal.numClients; i++ ) {
if ( gameLocal.entities[ i ] && gameLocal.entities[ i ]->IsType( idPlayer::Type ) ) {
playerState[ i ].vote = ( i == clientNum ) ? PLAYER_VOTE_YES : PLAYER_VOTE_WAIT;
} else {
playerState[i].vote = PLAYER_VOTE_NONE;
}
}
}
/*
================
idMultiplayerGame::ClientStartVote
================
*/
void idMultiplayerGame::ClientStartVote( int clientNum, const char *_voteString ) {
idBitMsg outMsg;
byte msgBuf[ MAX_GAME_MESSAGE_SIZE ];
if ( !gameLocal.isClient ) {
outMsg.Init( msgBuf, sizeof( msgBuf ) );
outMsg.WriteByte( GAME_RELIABLE_MESSAGE_STARTVOTE );
outMsg.WriteByte( clientNum );
outMsg.WriteString( _voteString );
networkSystem->ServerSendReliableMessage( -1, outMsg );
}
voteString = _voteString;
AddChatLine( va( common->GetLanguageDict()->GetString( "#str_04279" ), gameLocal.userInfo[ clientNum ].GetString( "ui_name" ) ) );
gameSoundWorld->PlayShaderDirectly( GlobalSoundStrings[ SND_VOTE ] );
if ( clientNum == gameLocal.localClientNum ) {
voted = true;
} else {
voted = false;
}
if ( gameLocal.isClient ) {
// the the vote value to something so the vote line is displayed
vote = VOTE_RESTART;
yesVotes = 1;
noVotes = 0;
}
}
/*
================
idMultiplayerGame::ClientUpdateVote
================
*/
void idMultiplayerGame::ClientUpdateVote( vote_result_t status, int yesCount, int noCount ) {
idBitMsg outMsg;
byte msgBuf[ MAX_GAME_MESSAGE_SIZE ];
if ( !gameLocal.isClient ) {
outMsg.Init( msgBuf, sizeof( msgBuf ) );
outMsg.WriteByte( GAME_RELIABLE_MESSAGE_UPDATEVOTE );
outMsg.WriteByte( status );
outMsg.WriteByte( yesCount );
outMsg.WriteByte( noCount );
networkSystem->ServerSendReliableMessage( -1, outMsg );
}
if ( vote == VOTE_NONE ) {
// clients coming in late don't get the vote start and are not allowed to vote
return;
}
switch ( status ) {
case VOTE_FAILED:
AddChatLine( common->GetLanguageDict()->GetString( "#str_04278" ) );
gameSoundWorld->PlayShaderDirectly( GlobalSoundStrings[ SND_VOTE_FAILED ] );
if ( gameLocal.isClient ) {
vote = VOTE_NONE;
}
break;
case VOTE_PASSED:
AddChatLine( common->GetLanguageDict()->GetString( "#str_04277" ) );
gameSoundWorld->PlayShaderDirectly( GlobalSoundStrings[ SND_VOTE_PASSED ] );
break;
case VOTE_RESET:
if ( gameLocal.isClient ) {
vote = VOTE_NONE;
}
break;
case VOTE_ABORTED:
AddChatLine( common->GetLanguageDict()->GetString( "#str_04276" ) );
if ( gameLocal.isClient ) {
vote = VOTE_NONE;
}
break;
default:
break;
}
if ( gameLocal.isClient ) {
yesVotes = yesCount;
noVotes = noCount;
}
}
/*
================
idMultiplayerGame::ClientCallVote
================
*/
void idMultiplayerGame::ClientCallVote( vote_flags_t voteIndex, const char *voteValue ) {
idBitMsg outMsg;
byte msgBuf[ MAX_GAME_MESSAGE_SIZE ];
// send
outMsg.Init( msgBuf, sizeof( msgBuf ) );
outMsg.WriteByte( GAME_RELIABLE_MESSAGE_CALLVOTE );
outMsg.WriteByte( voteIndex );
outMsg.WriteString( voteValue );
networkSystem->ClientSendReliableMessage( outMsg );
}
/*
================
idMultiplayerGame::CastVote
================
*/
void idMultiplayerGame::CastVote( int clientNum, bool castVote ) {
idBitMsg outMsg;
byte msgBuf[ 128 ];
if ( clientNum == gameLocal.localClientNum ) {
voted = true;
}
if ( gameLocal.isClient ) {
outMsg.Init( msgBuf, sizeof( msgBuf ) );
outMsg.WriteByte( GAME_RELIABLE_MESSAGE_CASTVOTE );
outMsg.WriteByte( castVote );
networkSystem->ClientSendReliableMessage( outMsg );
return;
}
// sanity
if ( vote == VOTE_NONE ) {
gameLocal.ServerSendChatMessage( clientNum, "server", common->GetLanguageDict()->GetString( "#str_04275" ) );
common->DPrintf( "client %d: cast vote while no vote in progress\n", clientNum );
return;
}
if ( playerState[ clientNum ].vote != PLAYER_VOTE_WAIT ) {
gameLocal.ServerSendChatMessage( clientNum, "server", common->GetLanguageDict()->GetString( "#str_04274" ) );
common->DPrintf( "client %d: cast vote - vote %d != PLAYER_VOTE_WAIT\n", clientNum, playerState[ clientNum ].vote );
return;
}
if ( castVote ) {
playerState[ clientNum ].vote = PLAYER_VOTE_YES;
yesVotes++;
} else {
playerState[ clientNum ].vote = PLAYER_VOTE_NO;
noVotes++;
}
ClientUpdateVote( VOTE_UPDATE, yesVotes, noVotes );
}
/*
================
idMultiplayerGame::ServerCallVote
================
*/
void idMultiplayerGame::ServerCallVote( int clientNum, const idBitMsg &msg ) {
vote_flags_t voteIndex;
int vote_timeLimit, vote_fragLimit, vote_clientNum, vote_gameTypeIndex; //, vote_kickIndex;
char value[ MAX_STRING_CHARS ];
assert( clientNum != -1 );
assert( !gameLocal.isClient );
voteIndex = (vote_flags_t)msg.ReadByte( );
msg.ReadString( value, sizeof( value ) );
// sanity checks - setup the vote
if ( vote != VOTE_NONE ) {
gameLocal.ServerSendChatMessage( clientNum, "server", common->GetLanguageDict()->GetString( "#str_04273" ) );
common->DPrintf( "client %d: called vote while voting already in progress - ignored\n", clientNum );
return;
}
switch ( voteIndex ) {
case VOTE_RESTART:
ServerStartVote( clientNum, voteIndex, "" );
ClientStartVote( clientNum, common->GetLanguageDict()->GetString( "#str_04271" ) );
break;
case VOTE_NEXTMAP:
ServerStartVote( clientNum, voteIndex, "" );
ClientStartVote( clientNum, common->GetLanguageDict()->GetString( "#str_04272" ) );
break;
case VOTE_TIMELIMIT:
vote_timeLimit = strtol( value, NULL, 10 );
if ( vote_timeLimit == gameLocal.serverInfo.GetInt( "si_timeLimit" ) ) {
gameLocal.ServerSendChatMessage( clientNum, "server", common->GetLanguageDict()->GetString( "#str_04270" ) );
common->DPrintf( "client %d: already at the voted Time Limit\n", clientNum );
return;
}
if ( vote_timeLimit < si_timeLimit.GetMinValue() || vote_timeLimit > si_timeLimit.GetMaxValue() ) {
gameLocal.ServerSendChatMessage( clientNum, "server", common->GetLanguageDict()->GetString( "#str_04269" ) );
common->DPrintf( "client %d: timelimit value out of range for vote: %s\n", clientNum, value );
return;
}
ServerStartVote( clientNum, voteIndex, value );
ClientStartVote( clientNum, va( common->GetLanguageDict()->GetString( "#str_04268" ), vote_timeLimit ) );
break;
case VOTE_FRAGLIMIT:
vote_fragLimit = strtol( value, NULL, 10 );
if ( vote_fragLimit == gameLocal.serverInfo.GetInt( "si_fragLimit" ) ) {
gameLocal.ServerSendChatMessage( clientNum, "server", common->GetLanguageDict()->GetString( "#str_04267" ) );
common->DPrintf( "client %d: already at the voted Frag Limit\n", clientNum );
return;
}
if ( vote_fragLimit < si_fragLimit.GetMinValue() || vote_fragLimit > si_fragLimit.GetMaxValue() ) {
gameLocal.ServerSendChatMessage( clientNum, "server", common->GetLanguageDict()->GetString( "#str_04266" ) );
common->DPrintf( "client %d: fraglimit value out of range for vote: %s\n", clientNum, value );
return;
}
ServerStartVote( clientNum, voteIndex, value );
ClientStartVote( clientNum, va( common->GetLanguageDict()->GetString( "#str_04303" ), gameLocal.gameType == GAME_LASTMAN ? common->GetLanguageDict()->GetString( "#str_04264" ) : common->GetLanguageDict()->GetString( "#str_04265" ), vote_fragLimit ) );
break;
case VOTE_GAMETYPE:
vote_gameTypeIndex = strtol( value, NULL, 10 );
assert( vote_gameTypeIndex >= 0 && vote_gameTypeIndex <= 3 );
switch ( vote_gameTypeIndex ) {
case 0:
strcpy( value, "Deathmatch" );
break;
case 1:
strcpy( value, "Tourney" );
break;
case 2:
strcpy( value, "Team DM" );
break;
case 3:
strcpy( value, "Last Man" );
break;
}
if ( !idStr::Icmp( value, gameLocal.serverInfo.GetString( "si_gameType" ) ) ) {
gameLocal.ServerSendChatMessage( clientNum, "server", common->GetLanguageDict()->GetString( "#str_04259" ) );
common->DPrintf( "client %d: already at the voted Game Type\n", clientNum );
return;
}
ServerStartVote( clientNum, voteIndex, value );
ClientStartVote( clientNum, va( common->GetLanguageDict()->GetString( "#str_04258" ), value ) );
break;
case VOTE_KICK:
vote_clientNum = strtol( value, NULL, 10 );
if ( vote_clientNum == gameLocal.localClientNum ) {
gameLocal.ServerSendChatMessage( clientNum, "server", common->GetLanguageDict()->GetString( "#str_04257" ) );
common->DPrintf( "client %d: called kick for the server host\n", clientNum );
return;
}
ServerStartVote( clientNum, voteIndex, va( "%d", vote_clientNum ) );
ClientStartVote( clientNum, va( common->GetLanguageDict()->GetString( "#str_04302" ), vote_clientNum, gameLocal.userInfo[ vote_clientNum ].GetString( "ui_name" ) ) );
break;
case VOTE_MAP: {
if ( idStr::FindText( gameLocal.serverInfo.GetString( "si_map" ), value ) != -1 ) {
gameLocal.ServerSendChatMessage( clientNum, "server", va( common->GetLanguageDict()->GetString( "#str_04295" ), value ) );
common->DPrintf( "client %d: already running the voted map: %s\n", clientNum, value );
return;
}
int num = fileSystem->GetNumMaps();
int i;
const idDict *dict;
bool haveMap = false;
for ( i = 0; i < num; i++ ) {
dict = fileSystem->GetMapDecl( i );
if ( dict && !idStr::Icmp( dict->GetString( "path" ), value ) ) {
haveMap = true;
break;
}
}
if ( !haveMap ) {
gameLocal.ServerSendChatMessage( clientNum, "server", va( common->GetLanguageDict()->GetString( "#str_04296" ), value ) );
common->Printf( "client %d: map not found: %s\n", clientNum, value );
return;
}
ServerStartVote( clientNum, voteIndex, value );
ClientStartVote( clientNum, va( common->GetLanguageDict()->GetString( "#str_04256" ), common->GetLanguageDict()->GetString( dict ? dict->GetString( "name" ) : value ) ) );
break;
}
case VOTE_SPECTATORS:
if ( gameLocal.serverInfo.GetBool( "si_spectators" ) ) {
ServerStartVote( clientNum, voteIndex, "" );
ClientStartVote( clientNum, common->GetLanguageDict()->GetString( "#str_04255" ) );
} else {
ServerStartVote( clientNum, voteIndex, "" );
ClientStartVote( clientNum, common->GetLanguageDict()->GetString( "#str_04254" ) );
}
break;
default:
gameLocal.ServerSendChatMessage( clientNum, "server", va( common->GetLanguageDict()->GetString( "#str_04297" ), (int)voteIndex ) );
common->DPrintf( "client %d: unknown vote index %d\n", clientNum, voteIndex );
}
}
/*
================
idMultiplayerGame::DisconnectClient
================
*/
void idMultiplayerGame::DisconnectClient( int clientNum ) {
if ( lastWinner == clientNum ) {
lastWinner = -1;
}
UpdatePlayerRanks();
CheckAbortGame();
}
/*
================
idMultiplayerGame::CheckAbortGame
================
*/
void idMultiplayerGame::CheckAbortGame( void ) {
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++ ) {
if ( !gameLocal.entities[ currentTourneyPlayer[ i ] ] || static_cast< idPlayer * >( gameLocal.entities[ currentTourneyPlayer[ i ] ] )->spectating ) {
NewState( GAMEREVIEW );
return;
}
}
break;
default:
if ( !EnoughClientsToPlay() ) {
NewState( GAMEREVIEW );
}
break;
}
}
/*
================
idMultiplayerGame::WantKilled
================
*/
void idMultiplayerGame::WantKilled( int clientNum ) {
idEntity *ent = gameLocal.entities[ clientNum ];
if ( ent && ent->IsType( idPlayer::Type ) ) {
static_cast<idPlayer *>( ent )->Kill( false, false );
}
}
/*
================
idMultiplayerGame::MapRestart
================
*/
void idMultiplayerGame::MapRestart( void ) {
int clientNum;
assert( !gameLocal.isClient );
if ( gameState != WARMUP ) {
NewState( WARMUP );
nextState = INACTIVE;
nextStateSwitch = 0;
}
if ( g_balanceTDM.GetBool() && lastGameType != GAME_TDM && gameLocal.gameType == GAME_TDM ) {
for ( clientNum = 0; clientNum < gameLocal.numClients; clientNum++ ) {
if ( gameLocal.entities[ clientNum ] && gameLocal.entities[ clientNum ]->IsType( idPlayer::Type ) ) {
if ( static_cast< idPlayer* >( gameLocal.entities[ clientNum ] )->BalanceTDM() ) {
// core is in charge of syncing down userinfo changes
// it will also call back game through SetUserInfo with the current info for update
cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "updateUI %d\n", clientNum ) );
}
}
}
}
lastGameType = gameLocal.gameType;
}
/*
================
idMultiplayerGame::SwitchToTeam
================
*/
void idMultiplayerGame::SwitchToTeam( int clientNum, int oldteam, int newteam ) {
idEntity *ent;
int i;
assert( gameLocal.gameType == GAME_TDM );
assert( oldteam != newteam );
assert( !gameLocal.isClient );
if ( !gameLocal.isClient && newteam >= 0 && IsInGame( clientNum ) ) {
PrintMessageEvent( -1, MSG_JOINTEAM, clientNum, newteam );
}
// assign the right teamFragCount
for( i = 0; i < gameLocal.numClients; i++ ) {
if ( i == clientNum ) {
continue;
}
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 && oldteam != -1 ) {
// when changing teams during game, kill and respawn
idPlayer *p = static_cast<idPlayer *>( gameLocal.entities[ clientNum ] );
if ( p->IsInTeleport() ) {
p->ServerSendEvent( idPlayer::EVENT_ABORT_TELEPORTER, NULL, false, -1 );
p->SetPrivateCameraView( NULL );
}
p->Kill( true, true );
CheckAbortGame();
}
}
/*
================
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 *p;
idStr prefixed_name;
assert( !gameLocal.isClient );
if ( clientNum >= 0 ) {
p = static_cast< idPlayer * >( gameLocal.entities[ clientNum ] );
if ( !( p && p->IsType( idPlayer::Type ) ) ) {
return;
}
if ( p->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 {
p = NULL;
send_to = 0;
}
// put the message together
outMsg.Init( msgBuf, sizeof( msgBuf ) );
outMsg.WriteByte( GAME_RELIABLE_MESSAGE_CHAT );
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 );
networkSystem->ServerSendReliableMessage( -1, outMsg );
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;
}
if ( send_to == 1 && static_cast< idPlayer * >( ent )->spectating ) {
if ( sound ) {
PlayGlobalSound( i, SND_COUNT, sound );
}
if ( i == gameLocal.localClientNum ) {
AddChatLine( "%s^0: %s\n", prefixed_name.c_str(), text );
} else {
networkSystem->ServerSendReliableMessage( i, outMsg );
}
} else if ( send_to == 2 && static_cast< idPlayer * >( ent )->team == p->team ) {
if ( sound ) {
PlayGlobalSound( i, SND_COUNT, sound );
}
if ( i == gameLocal.localClientNum ) {
AddChatLine( "%s^0: %s\n", prefixed_name.c_str(), text );
} else {
networkSystem->ServerSendReliableMessage( i, outMsg );
}
}
}
}
}
/*
================
idMultiplayerGame::Precache
================
*/
void idMultiplayerGame::Precache( void ) {
int i;
idFile *f;
if ( !gameLocal.isMultiplayer ) {
return;
}
gameLocal.FindEntityDefDict( "player_doommarine", false );;
// skins
idStr str = cvarSystem->GetCVarString( "mod_validSkins" );
idStr skin;
while ( str.Length() ) {
int n = str.Find( ";" );
if ( n >= 0 ) {
skin = str.Left( n );
str = str.Right( str.Length() - n - 1 );
} else {
skin = str;
str = "";
}
declManager->FindSkin( skin, false );
}
for ( i = 0; ui_skinArgs[ i ]; i++ ) {
declManager->FindSkin( ui_skinArgs[ i ], false );
}
// MP game sounds
for ( i = 0; i < SND_COUNT; i++ ) {
f = fileSystem->OpenFileRead( GlobalSoundStrings[ i ] );
fileSystem->CloseFile( f );
}
// MP guis. just make sure we hit all of them
i = 0;
while ( MPGuis[ i ] ) {
uiManager->FindGui( MPGuis[ i ], true );
i++;
}
}
/*
================
idMultiplayerGame::ToggleSpectate
================
*/
void idMultiplayerGame::ToggleSpectate( void ) {
bool spectating;
assert( gameLocal.isClient || gameLocal.localClientNum == 0 );
spectating = ( idStr::Icmp( cvarSystem->GetCVarString( "ui_spectate" ), "Spectate" ) == 0 );
if ( spectating ) {
// always allow toggling to play
cvarSystem->SetCVarString( "ui_spectate", "Play" );
} else {
// only allow toggling to spectate if spectators are enabled.
if ( gameLocal.serverInfo.GetBool( "si_spectators" ) ) {
cvarSystem->SetCVarString( "ui_spectate", "Spectate" );
} else {
gameLocal.mpGame.AddChatLine( common->GetLanguageDict()->GetString( "#str_06747" ) );
}
}
}
/*
================
idMultiplayerGame::ToggleReady
================
*/
void idMultiplayerGame::ToggleReady( void ) {
bool ready;
assert( gameLocal.isClient || gameLocal.localClientNum == 0 );
ready = ( idStr::Icmp( cvarSystem->GetCVarString( "ui_ready" ), "Ready" ) == 0 );
if ( ready ) {
cvarSystem->SetCVarString( "ui_ready", "Not Ready" );
} else {
cvarSystem->SetCVarString( "ui_ready", "Ready" );
}
}
/*
================
idMultiplayerGame::ToggleTeam
================
*/
void idMultiplayerGame::ToggleTeam( void ) {
bool team;
assert( gameLocal.isClient || gameLocal.localClientNum == 0 );
team = ( idStr::Icmp( cvarSystem->GetCVarString( "ui_team" ), "Red" ) == 0 );
if ( team ) {
cvarSystem->SetCVarString( "ui_team", "Blue" );
} else {
cvarSystem->SetCVarString( "ui_team", "Red" );
}
}
/*
================
idMultiplayerGame::ToggleUserInfo
================
*/
void idMultiplayerGame::ThrottleUserInfo( void ) {
int i;
assert( gameLocal.localClientNum >= 0 );
i = 0;
while ( ThrottleVars[ i ] ) {
if ( idStr::Icmp( gameLocal.userInfo[ gameLocal.localClientNum ].GetString( ThrottleVars[ i ] ),
cvarSystem->GetCVarString( ThrottleVars[ i ] ) ) ) {
if ( gameLocal.realClientTime < switchThrottle[ i ] ) {
AddChatLine( common->GetLanguageDict()->GetString( "#str_04299" ), common->GetLanguageDict()->GetString( ThrottleVarsInEnglish[ i ] ), ( switchThrottle[ i ] - gameLocal.time ) / 1000 + 1 );
cvarSystem->SetCVarString( ThrottleVars[ i ], gameLocal.userInfo[ gameLocal.localClientNum ].GetString( ThrottleVars[ i ] ) );
} else {
switchThrottle[ i ] = gameLocal.time + ThrottleDelay[ i ] * 1000;
}
}
i++;
}
}
/*
================
idMultiplayerGame::CanPlay
================
*/
bool idMultiplayerGame::CanPlay( idPlayer *p ) {
return !p->wantSpectate && playerState[ p->entityNumber ].ingame;
}
/*
================
idMultiplayerGame::EnterGame
================
*/
void idMultiplayerGame::EnterGame( int clientNum ) {
assert( !gameLocal.isClient );
if ( !playerState[ clientNum ].ingame ) {
playerState[ clientNum ].ingame = true;
if ( gameLocal.isMultiplayer ) {
// can't use PrintMessageEvent as clients don't know the nickname yet
gameLocal.ServerSendChatMessage( -1, common->GetLanguageDict()->GetString( "#str_02047" ), va( common->GetLanguageDict()->GetString( "#str_07177" ), gameLocal.userInfo[ clientNum ].GetString( "ui_name" ) ) );
}
}
}
/*
================
idMultiplayerGame::WantRespawn
================
*/
bool idMultiplayerGame::WantRespawn( idPlayer *p ) {
return p->forceRespawn && !p->wantSpectate && playerState[ p->entityNumber ].ingame;
}
/*
================
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 ( !gameLocal.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;
}
voc = args.Argv( 1 );
spawnArgs = gameLocal.FindEntityDefDict( "player_doommarine", false );
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.Init( msgBuf, sizeof( msgBuf ) );
outMsg.WriteByte( GAME_RELIABLE_MESSAGE_VCHAT );
outMsg.WriteLong( index );
outMsg.WriteBits( team ? 1 : 0, 1 );
networkSystem->ClientSendReliableMessage( 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 = gameLocal.FindEntityDefDict( "player_doommarine", false );
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 = gameLocal.userInfo[ clientNum ].GetString( "ui_name" );
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 ) {
idBitMsg outMsg;
byte msgBuf[ MAX_GAME_MESSAGE_SIZE ];
int i;
idEntity *ent;
outMsg.Init( msgBuf, sizeof( msgBuf ) );
outMsg.BeginWriting();
outMsg.WriteByte( GAME_RELIABLE_MESSAGE_STARTSTATE );
// 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( i = 0; i < gameLocal.numClients; i++ ) {
ent = gameLocal.entities[ i ];
if ( i != clientNum && ent && ent->IsType( idPlayer::Type ) ) {
outMsg.WriteShort( i );
outMsg.WriteShort( static_cast< idPlayer * >( ent )->inventory.powerups );
outMsg.WriteBits( static_cast< idPlayer * >( ent )->spectating, 1 );
}
}
outMsg.WriteShort( MAX_CLIENTS );
networkSystem->ServerSendReliableMessage( clientNum, outMsg );
// we send SI in connectResponse messages, but it may have been modified already
outMsg.BeginWriting( );
outMsg.WriteByte( GAME_RELIABLE_MESSAGE_SERVERINFO );
outMsg.WriteDeltaDict( gameLocal.serverInfo, NULL );
networkSystem->ServerSendReliableMessage( clientNum, outMsg );
// warmup time
if ( gameState == COUNTDOWN ) {
outMsg.BeginWriting();
outMsg.WriteByte( GAME_RELIABLE_MESSAGE_WARMUPTIME );
outMsg.WriteLong( warmupEndTime );
networkSystem->ServerSendReliableMessage( clientNum, outMsg );
}
}
/*
================
idMultiplayerGame::ClientReadStartState
================
*/
void idMultiplayerGame::ClientReadStartState( const idBitMsg &msg ) {
int i, client, powerup;
// read the state in preparation for reading snapshot updates
gameState = (idMultiplayerGame::gameState_t)msg.ReadByte();
matchStartedTime = msg.ReadLong( );
startFragLimit = msg.ReadShort( );
while ( ( client = msg.ReadShort() ) != MAX_CLIENTS ) {
assert( gameLocal.entities[ client ] && gameLocal.entities[ client ]->IsType( idPlayer::Type ) );
powerup = msg.ReadShort();
for ( i = 0; i < MAX_POWERUPS; i++ ) {
if ( powerup & ( 1 << i ) ) {
static_cast< idPlayer * >( gameLocal.entities[ client ] )->GivePowerUp( i, 0 );
}
}
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();
}