quake4-sdk/source/mpgame/mp/GameState.cpp

3134 lines
92 KiB
C++

//----------------------------------------------------------------
// GameState.cpp
//
// Copyright 2002-2005 Raven Software
//----------------------------------------------------------------
#include "../../idlib/precompiled.h"
#pragma hdrstop
#include "GameState.h"
/*
===============================================================================
rvGameState
Game state info for deathmatch, team deathmatch
===============================================================================
*/
/*
================
rvGameState::rvGameState
================
*/
rvGameState::rvGameState( bool allocPrevious ) {
Clear();
if( allocPrevious ) {
previousGameState = new rvGameState( false );
} else {
previousGameState = NULL;
}
trackPrevious = allocPrevious;
}
/*
================
rvGameState::~rvGameState
================
*/
rvGameState::~rvGameState( void ) {
Clear();
delete previousGameState;
previousGameState = NULL;
}
/*
================
rvGameState::Clear
================
*/
void rvGameState::Clear( void ) {
currentState = INACTIVE;
nextState = INACTIVE;
nextStateTime = 0;
fragLimitTimeout = 0;
}
/*
================
rvGameState::SendState
================
*/
void rvGameState::SendState( const idMessageSender &sender, int clientNum ) {
idBitMsg outMsg;
byte msgBuf[MAX_GAME_MESSAGE_SIZE];
assert( (gameLocal.isServer || gameLocal.isRepeater) && trackPrevious );
if( clientNum == -1 && (*this) == (*previousGameState) ) {
return;
}
outMsg.Init( msgBuf, sizeof( msgBuf ) );
outMsg.WriteByte( GAME_RELIABLE_MESSAGE_GAMESTATE );
WriteState( outMsg );
sender.Send( outMsg );
// don't update the state if we are working for a single client
if ( clientNum == -1 ) {
outMsg.ReadByte(); // pop off the msg ID
ReceiveState( outMsg );
}
}
/*
===============
rvGameState::WriteState
===============
*/
void rvGameState::WriteState( idBitMsg &msg ) {
PackState( msg );
}
/*
================
rvGameState::SendInitialState
================
*/
void rvGameState::SendInitialState( const idMessageSender &sender, int clientNum ) {
rvGameState* previousState = previousGameState;
rvGameState invalidState;
previousGameState = &invalidState;
SendState( sender, clientNum );
previousGameState = previousState;
}
/*
================
rvGameState::ReceiveState
================
*/
void rvGameState::ReceiveState( const idBitMsg& msg ) {
if ( !BaseUnpackState( msg ) ) {
return;
}
if ( gameLocal.localClientNum >= 0 ) {
GameStateChanged();
}
(*previousGameState) = (*this);
}
/*
================
rvGameState::PackState
================
*/
void rvGameState::PackState( idBitMsg& outMsg ) {
// server and client changing their rvGameState subclass by guesswork
// you can't rely on it being in sync at all times, so read and verify the type first
outMsg.WriteByte( gameLocal.gameType );
// for now, we only transmit 3 bytes. If we need to sync more data, we should
// only transmit the diff
outMsg.WriteByte( currentState );
outMsg.WriteByte( nextState );
outMsg.WriteLong( nextStateTime );
}
/*
================
rvGameState::BaseUnpackState
================
*/
bool rvGameState::BaseUnpackState( const idBitMsg& inMsg ) {
gameType_t t = (gameType_t)inMsg.ReadByte();
if ( t != gameLocal.gameType ) {
common->Warning( "rvGameState::UnpackState: client gametype (%d) is out of sync with server (%d). Ignoring", gameLocal.gameType, t );
return false;
}
currentState = (mpGameState_t)inMsg.ReadByte();
nextState = (mpGameState_t)inMsg.ReadByte();
nextStateTime = inMsg.ReadLong();
return true;
}
/*
================
rvGameState::GameStateChanged
================
*/
void rvGameState::GameStateChanged( void ) {
idPlayer* player = gameLocal.GetLocalPlayer();
if ( !player ) {
if ( gameLocal.IsServerDemoPlaying() && gameLocal.GetDemoFollowClient() >= 0 ) {
player = static_cast< idPlayer * >( gameLocal.entities[ gameLocal.GetDemoFollowClient() ] );
}
}
if ( !player ) {
gameLocal.Warning( "rvGameState::GameStateChanged() - NULL local player\n" ) ;
return;
}
// Check for a currentState change
if( currentState != previousGameState->currentState ) {
if( currentState == WARMUP ) {
if( gameLocal.gameType != GAME_TOURNEY ) {
player->GUIMainNotice( common->GetLocalizedString( "#str_107706" ), true );
}
soundSystem->SetActiveSoundWorld( true );
// reset stats on the client-side
if( gameLocal.isClient ) {
statManager->Init();
}
} else if( currentState == COUNTDOWN ) {
if( gameLocal.gameType != GAME_TOURNEY ) {
player->GUIMainNotice( common->GetLocalizedString( "#str_107706" ), true );
}
soundSystem->SetActiveSoundWorld(true);
if( gameLocal.gameType != GAME_TOURNEY && previousGameState->currentState != INACTIVE ) {
gameLocal.mpGame.RemoveAnnouncerSound( AS_GENERAL_PREPARE_TO_FIGHT );
gameLocal.mpGame.RemoveAnnouncerSound( AS_GENERAL_THREE );
gameLocal.mpGame.RemoveAnnouncerSound( AS_GENERAL_TWO );
gameLocal.mpGame.RemoveAnnouncerSound( AS_GENERAL_ONE );
gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_PREPARE_TO_FIGHT, gameLocal.time );
gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_THREE, nextStateTime - 3000 );
gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_TWO, nextStateTime - 2000 );
gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_ONE, nextStateTime - 1000 );
}
} else if( currentState == GAMEON ) {
if ( !player->vsMsgState ) {
player->GUIMainNotice( "" );
player->GUIFragNotice( "" );
} else {
player->vsMsgState = false;
}
if( gameLocal.gameType != GAME_TOURNEY ) {
gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_FIGHT, gameLocal.time );
}
//if ( gameLocal.gameType == GAME_DEADZONE ) {
// if ( player->team == TEAM_MARINE )
// gameLocal.mpGame.ScheduleAnnouncerSound( AS_TEAM_JOIN_MARINE, gameLocal.time );
// else
// gameLocal.mpGame.ScheduleAnnouncerSound( AS_TEAM_JOIN_STROGG, gameLocal.time );
//}
cvarSystem->SetCVarString( "ui_ready", "Not Ready" );
soundSystem->SetActiveSoundWorld( true );
// clear stats on client
if( gameLocal.isClient ) {
statManager->Init();
}
} else if( currentState == SUDDENDEATH ) {
soundSystem->SetActiveSoundWorld( true );
gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_SUDDEN_DEATH, gameLocal.time );
gameLocal.GetLocalPlayer()->GUIMainNotice( common->GetLocalizedString( "#str_104287" ) );
} else if( currentState == GAMEREVIEW ) {
// RITUAL BEGIN
gameLocal.mpGame.isBuyingAllowedRightNow = false;
// RITUAL END
gameLocal.mpGame.ShowStatSummary();
}
gameLocal.mpGame.ScheduleTimeAnnouncements( );
}
}
/*
================
rvGameState::SpawnDeadZonePowerup
================
*/
void rvGameState::SpawnDeadZonePowerup( void ) {
idEntity *ent;
riDeadZonePowerup* spawnSpot = 0;
int count = 0;
for ( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) {
// If its not a DeadZone powerup then skip it
if ( !ent->IsType( riDeadZonePowerup::GetClassType() ) ) {
continue;
}
// Make sure its the right type first
riDeadZonePowerup* flag;
flag = static_cast<riDeadZonePowerup*>(ent);
if ( flag->powerup != POWERUP_DEADZONE || flag->IsVisible() ) {
continue;
}
if ( flag->spawnArgs.GetBool("dropped", "0") && !flag->IsVisible() ) {
flag->PostEventMS( &EV_Remove, 0 );
} else {
count++;
if ( !(rand()%(count)) ) {
spawnSpot = flag;
}
}
}
if ( spawnSpot ) {
spawnSpot->PostEventMS( &EV_RespawnItem, 0 );
spawnSpot->srvReady = 1; // Go ahead and set this, so the loop works properly.
} else {
gameLocal.Error("Couldn't find enough dead zone spawn spots for the number of dead zone artifacts specified in the map def!");
}
}
/*
================
rvGameState::Run
================
*/
void rvGameState::Run( void ) {
if ( currentState == INACTIVE ) {
#ifdef _XENON
if(Live()->RoundsPlayed() < cvarSystem->GetCVarInteger("si_matchRounds"))
#endif
{
NewState( WARMUP );
}
}
if ( nextState != INACTIVE && gameLocal.time > nextStateTime ) {
NewState( nextState );
nextState = INACTIVE;
}
switch( currentState ) {
case INACTIVE:
break;
case GAMEREVIEW: {
if ( nextState == INACTIVE ) {
statManager->SendAllStats();
nextState = NEXTGAME;
// allow a little extra time in tourney since we have to display end brackets
if( gameLocal.gameType == GAME_TOURNEY ) {
nextStateTime = gameLocal.time + 5000 + (1000 * cvarSystem->GetCVarInteger( "g_gameReviewPause" ));
} else {
nextStateTime = gameLocal.time + (1000 * cvarSystem->GetCVarInteger( "g_gameReviewPause" ));
}
}
break;
}
case NEXTGAME: {
// the core will force a restart at 12 hours max
// but it's nicer if we can wait for a game transition to perform the restart so the game is not interrupted
// perform a restart once we are past 8 hours
if ( networkSystem->ServerGetServerTime() > 8*60*60*1000 ) {
gameLocal.sessionCommand = "nextMap";
return;
}
if ( nextState == INACTIVE ) {
// game rotation, new map, gametype etc.
// only cycle in tourney if tourneylimit is higher than the specified value
if( gameLocal.gameType != GAME_TOURNEY || ((rvTourneyGameState*)this)->GetTourneyCount() >= gameLocal.serverInfo.GetInt( "si_tourneyLimit" ) ) {
// whether we switch to the next map or not, reset the tourney count
if( gameLocal.gameType == GAME_TOURNEY ) {
((rvTourneyGameState*)this)->SetTourneyCount( 0 );
}
if ( gameLocal.NextMap() ) {
cmdSystem->BufferCommandText( CMD_EXEC_NOW, "serverMapRestart\n" );
return;
}
}
NewState( WARMUP );
// put everyone back in from endgame spectate
for ( int i = 0; i < gameLocal.numClients; i++ ) {
idEntity *ent = gameLocal.entities[ i ];
if ( ent && ent->IsType( idPlayer::GetClassType() ) ) {
if ( !static_cast< idPlayer * >( ent )->wantSpectate ) {
gameLocal.mpGame.CheckRespawns( static_cast<idPlayer *>( ent ) );
}
}
}
}
break;
}
case WARMUP: {
// check to see if we actually want to do a warmup, or if we fall through
//RAVEN BEGIN
//asalmon: Live has its own rules for ending warm up
#ifdef _XENON
if(!Live()->RollCall())
{
break;
}
#endif
//RAVEN END
if( !gameLocal.serverInfo.GetBool( "si_warmup" ) && gameLocal.gameType != GAME_TOURNEY ) {
// tourney always needs a warmup, to ensure that at least 2 players get seeded for the tournament.
NewState( GAMEON );
} else if ( gameLocal.mpGame.AllPlayersReady() ) {
NewState( COUNTDOWN );
nextState = GAMEON;
nextStateTime = gameLocal.time + 1000 * gameLocal.serverInfo.GetInt( "si_countDown" );
}
break;
}
case COUNTDOWN: {
break;
}
}
}
/*
================
rvGameState::NewState
================
*/
void rvGameState::NewState( mpGameState_t newState ) {
idBitMsg outMsg;
byte msgBuf[MAX_GAME_MESSAGE_SIZE];
int i;
assert( (newState != currentState) && gameLocal.isServer );
switch( newState ) {
case WARMUP: {
// asalmon: start the stat manager as soon as the game starts
statManager->Init();
statManager->BeginGame();
// if shuffle is on, shuffle the teams around
if( gameLocal.IsTeamGame() && gameLocal.serverInfo.GetBool( "si_shuffle" ) ) {
gameLocal.mpGame.ShuffleTeams();
}
// allow damage in warmup
//gameLocal.mpGame.EnableDamage( false );
//asalmon: clear out lingering team scores.
gameLocal.mpGame.ClearTeamScores();
if( gameLocal.gameType != GAME_TOURNEY ) {
for( i = 0; i < gameLocal.numClients; i++ ) {
idEntity *ent = gameLocal.entities[ i ];
if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) {
continue;
}
((idPlayer*)ent)->JoinInstance( 0 );
}
}
break;
}
case GAMEON: {
// allow damage in warmup
//gameLocal.mpGame.EnableDamage( true );
gameLocal.LocalMapRestart();
// RITUAL BEGIN
// squirrel: Buying & Deadzone
for( i = 0; i < gameLocal.numClients; i++ ) {
idEntity *ent = gameLocal.entities[ i ];
if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) {
continue;
}
idPlayer* player = static_cast< idPlayer* >(ent);
player->inventory.carryOverWeapons = 0;
player->ResetCash();
// If the buy menu is up during a server restart,
// make sure to refresh it.
gameLocal.mpGame.RedrawLocalBuyMenu();
}
if ( gameLocal.mpGame.IsBuyingAllowedInTheCurrentGameMode() )
{
gameLocal.mpGame.isBuyingAllowedRightNow = true;
// Give all the clients full ammo since this is the start of the round.
for( int i = 0; i < gameLocal.numClients; i++ ) {
idPlayer* p = (idPlayer*)gameLocal.entities[ i ];
if( p == NULL || !p->IsType( idPlayer::GetClassType() ) )
continue;
GiveStuffToPlayer(p, "ammo", "");
p->inventory.weapons |= p->inventory.carryOverWeapons & CARRYOVER_WEAPONS_MASK;
}
}
if ( gameLocal.gameType == GAME_DEADZONE ) {
// Spawn the powerups!
const char *mapName = gameLocal.serverInfo.GetString( "si_map" );
const idDict *mapDict = fileSystem->GetMapDecl( mapName );
if ( mapDict )
gameLocal.mpGame.deadZonePowerupCount = mapDict->GetInt("deadZonePowerupCount", "3");
else
gameLocal.mpGame.deadZonePowerupCount = 3;
int pcount = gameLocal.mpGame.deadZonePowerupCount;
if ( pcount == -1 )
pcount = 3; // Good default.
pcount = idMath::ClampInt(1, 12, pcount);
for ( int i = 0; i<pcount; i++ ) {
SpawnDeadZonePowerup();
}
}
// RITUAL END
// asalmon: reset the stats when the warmup period is over
statManager->Init();
statManager->BeginGame();
outMsg.Init( msgBuf, sizeof( msgBuf ) );
outMsg.WriteByte( GAME_RELIABLE_MESSAGE_RESTART );
outMsg.WriteBits( 0, 1 );
networkSystem->ServerSendReliableMessage( -1, outMsg );
gameLocal.mpGame.SetMatchStartedTime( gameLocal.time );
fragLimitTimeout = 0;
// write server initial reliable messages to give everyone new base
for( i = 0; i < MAX_CLIENTS; i++ ) {
// dont send this to server - we have all the data already and this will
// just trigger extra gamestate detection
if ( gameLocal.entities[ i ] && i != gameLocal.localClientNum ) {
gameLocal.mpGame.ServerWriteInitialReliableMessages( serverReliableSender.To( i, true ), i );
}
}
if ( gameLocal.isRepeater ) {
gameLocal.mpGame.ServerWriteInitialReliableMessages( repeaterReliableSender.To( -1 ), ENTITYNUM_NONE );
}
for( i = 0; i < gameLocal.numClients; i++ ) {
idEntity *ent = gameLocal.entities[ i ];
if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) {
continue;
}
idPlayer *p = static_cast<idPlayer *>( ent );
p->SetLeader( false ); // don't carry the flag from previous games
gameLocal.mpGame.SetPlayerScore( p, 0 );
gameLocal.mpGame.SetPlayerTeamScore( p, 0 );
// in normal gameplay modes, spawn the player in. For tourney, the tourney manager handles spawning
if( gameLocal.gameType != GAME_TOURNEY ) {
p->JoinInstance( 0 );
// in team games, let UserInfoChanged() spawn people on the right teams
if( !gameLocal.IsTeamGame() || p->team != -1 ) {
p->ServerSpectate( static_cast<idPlayer *>(ent)->wantSpectate );
}
}
}
gameLocal.mpGame.ClearTeamScores();
cvarSystem->SetCVarString( "ui_ready", "Not Ready" );
gameLocal.mpGame.switchThrottle[ 1 ] = 0; // passby the throttle
break;
}
case GAMEREVIEW: {
statManager->EndGame();
//statManager->DebugPrint();
nextState = INACTIVE; // used to abort a game. cancel out any upcoming state change
for( i = 0; i < gameLocal.numClients; i++ ) {
idEntity *ent = gameLocal.entities[ i ];
// RAVEN BEGIN
// jnewquist: Use accessor for static class type
if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) {
// RAVEN END
continue;
}
// RITUAL BEGIN
// squirrel: support for Buying in multiplayer
idPlayer* player = static_cast< idPlayer* >(ent);
player->inventory.carryOverWeapons = 0;
player->ResetCash();
player->forcedReady = false;
player->ServerSpectate( true );
static_cast< idPlayer *>( ent )->forcedReady = false;
static_cast<idPlayer *>(ent)->ServerSpectate( true );
// RITUAL END
}
break;
}
case SUDDENDEATH: {
gameLocal.mpGame.PrintMessageEvent( -1, MSG_SUDDENDEATH );
//unmark all leaders, so we make sure we only let the proper people respawn
for( i = 0; i < gameLocal.numClients; i++ ) {
idEntity *ent = gameLocal.entities[ i ];
if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) {
continue;
}
idPlayer *p = static_cast<idPlayer *>( ent );
p->SetLeader( false ); // don't carry the flag from previous games
}
// only restart in team games if si_suddenDeathRestart is set.
if( !gameLocal.IsTeamGame() || gameLocal.serverInfo.GetBool( "si_suddenDeathRestart" ) ) {
gameLocal.LocalMapRestart();
}
// Mark everyone tied for the lead as leaders
i = 0;
idPlayer* leader = gameLocal.mpGame.GetRankedPlayer( i );
if( leader ) {
int highScore = gameLocal.mpGame.GetScore( leader );
while( leader ) {
if( gameLocal.mpGame.GetScore( leader ) < highScore ) {
break;
}
leader->SetLeader( true );
leader = gameLocal.mpGame.GetRankedPlayer( ++i );
}
}
// RITUAL BEGIN
// squirrel: Buying & Deadzone
/// Reset players' cash and inventory if si_suddenDeathRestart is set
if( !gameLocal.IsTeamGame() || gameLocal.serverInfo.GetBool( "si_suddenDeathRestart" ) )
{
for( i = 0; i < gameLocal.numClients; i++ ) {
idEntity *ent = gameLocal.entities[ i ];
if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) {
continue;
}
idPlayer* player = static_cast< idPlayer* >(ent);
player->inventory.carryOverWeapons = 0;
player->ResetCash();
}
}
if ( gameLocal.mpGame.IsBuyingAllowedInTheCurrentGameMode() )
{
gameLocal.mpGame.isBuyingAllowedRightNow = true;
// Give all the clients full ammo since this is the start of the round.
for( int i = 0; i < gameLocal.numClients; i++ ) {
idPlayer* p = (idPlayer*)gameLocal.entities[ i ];
if( p == NULL || !p->IsType( idPlayer::GetClassType() ) )
continue;
GiveStuffToPlayer(p, "ammo", "");
p->inventory.weapons |= p->inventory.carryOverWeapons & CARRYOVER_WEAPONS_MASK;
}
}
if ( gameLocal.gameType == GAME_DEADZONE ) {
// Spawn the powerups!
const char *mapName = gameLocal.serverInfo.GetString( "si_map" );
const idDict *mapDict = fileSystem->GetMapDecl( mapName );
if ( mapDict )
gameLocal.mpGame.deadZonePowerupCount = mapDict->GetInt("deadZonePowerupCount", "3");
else
gameLocal.mpGame.deadZonePowerupCount = 3;
int pcount = gameLocal.mpGame.deadZonePowerupCount;
if ( pcount == -1 )
pcount = 3; // Good default.
pcount = idMath::ClampInt(1, 12, pcount);
for ( int i = 0; i<pcount; i++ ) {
SpawnDeadZonePowerup();
}
}
// RITUAL END
if ( gameLocal.gameType == GAME_DM ) {
//send all the non-leaders to spectatormode?
for( i = 0; i < gameLocal.numClients; i++ ) {
idEntity *ent = gameLocal.entities[ i ];
// RAVEN BEGIN
// jnewquist: Use accessor for static class type
if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) {
// RAVEN END
continue;
}
if ( static_cast< idPlayer *>( ent )->IsLeader() ) {
static_cast<idPlayer *>(ent)->ServerSpectate( false );
continue;
}
static_cast< idPlayer *>( ent )->forcedReady = false;
static_cast<idPlayer *>(ent)->ServerSpectate( true );
}
}
break;
}
default: {
break;
}
}
currentState = newState;
}
/*
================
rvGameState::ClientDisconnect
================
*/
void rvGameState::ClientDisconnect( idPlayer* player ) {
return;
}
/*
================
rvGameState::Spectate
================
*/
void rvGameState::Spectate( idPlayer* player ) {
if( player->spectating && player->wantSpectate ) {
gameLocal.mpGame.ClearFrags( player->entityNumber );
}
return;
}
/*
================
rvGameState::operator==
================
*/
bool rvGameState::operator==( const rvGameState& rhs ) const {
return (
( currentState == rhs.currentState ) &&
( nextState == rhs.nextState ) &&
( nextStateTime == rhs.nextStateTime )
);
}
/*
================
rvGameState::operator!=
================
*/
bool rvGameState::operator!=( const rvGameState& rhs ) const {
return (
( currentState != rhs.currentState ) ||
( nextState != rhs.nextState ) ||
( nextStateTime != rhs.nextStateTime )
);
}
/*
================
rvGameState::operator=
================
*/
rvGameState& rvGameState::operator=( const rvGameState& rhs ) {
currentState = rhs.currentState;
nextState = rhs.nextState;
nextStateTime = rhs.nextStateTime;
return (*this);
}
/*
===============
rvGameState::WriteNetworkInfo
===============
*/
void rvGameState::WriteNetworkInfo( idFile *file, int clientNum ) {
idBitMsg msg;
byte msgBuf[ MAX_GAME_MESSAGE_SIZE ];
msg.Init( msgBuf, sizeof( msgBuf ) );
msg.BeginWriting();
WriteState( msg );
file->WriteInt( msg.GetSize() );
file->Write( msg.GetData(), msg.GetSize() );
}
/*
===============
rvGameState::ReadNetworkInfo
===============
*/
void rvGameState::ReadNetworkInfo( idFile *file, int clientNum ) {
idBitMsg msg;
byte msgBuf[ MAX_GAME_MESSAGE_SIZE ];
int size;
file->ReadInt( size );
msg.Init( msgBuf, size );
msg.SetSize( size );
file->Read( msg.GetData(), size );
ReceiveState( msg );
}
/*
===============================================================================
rvDMGameState
Game state info for DM
===============================================================================
*/
rvDMGameState::rvDMGameState( bool allocPrevious ) : rvGameState( allocPrevious ) {
trackPrevious = allocPrevious;
}
void rvDMGameState::Run( void ) {
idPlayer* player = NULL;
rvGameState::Run();
switch( currentState ) {
case GAMEON: {
player = gameLocal.mpGame.FragLimitHit();
bool tiedForFirst = false;
idPlayer* first = gameLocal.mpGame.GetRankedPlayer( 0 );
idPlayer* second = gameLocal.mpGame.GetRankedPlayer( 1 );
if( player == NULL ) {
if( first && second && gameLocal.mpGame.GetScore( first ) == gameLocal.mpGame.GetScore( second ) ) {
tiedForFirst = true;
}
}
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 );
gameLocal.mpGame.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
//
// jshepard: OR it means that the winner killed himself during the fraglimit delay, and the
// game needs to roll on.
if( first && second && (gameLocal.mpGame.GetScore( first ) == gameLocal.mpGame.GetScore( second )) ) {
//this is a tie...
if( gameLocal.mpGame.GetScore( first ) >= gameLocal.serverInfo.GetInt( "si_fragLimit" ) ) {
//and it must be tied at fraglimit, so sudden death.
NewState( SUDDENDEATH );
}
}
//otherwise, just keep playing as normal.
fragLimitTimeout = 0;
} else if ( gameLocal.mpGame.TimeLimitHit() ) {
gameLocal.mpGame.PrintMessageEvent( -1, MSG_TIMELIMIT );
if( tiedForFirst ) {
// if tied at timelimit hit, goto sudden death
fragLimitTimeout = 0;
NewState( SUDDENDEATH );
} else {
// or just end the game
NewState( GAMEREVIEW );
}
} else if( tiedForFirst && gameLocal.serverInfo.GetInt( "si_fragLimit" ) > 0 && gameLocal.mpGame.GetScore( first ) >= gameLocal.serverInfo.GetInt( "si_fragLimit" ) ) {
// check for the rare case that two players both hit the fraglimit the same frame
// two people tied at fraglimit, advance to sudden death after a delay
fragLimitTimeout = gameLocal.time + FRAGLIMIT_DELAY;
}
break;
}
case SUDDENDEATH: {
player = gameLocal.mpGame.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 );
gameLocal.mpGame.PrintMessageEvent( -1, MSG_FRAGLIMIT, player->entityNumber );
}
} else if ( fragLimitTimeout ) {
gameLocal.mpGame.PrintMessageEvent( -1, MSG_HOLYSHIT );
fragLimitTimeout = 0;
}
break;
}
}
}
/*
===============================================================================
rvTeamDMGameState
Game state info for Team DM
===============================================================================
*/
rvTeamDMGameState::rvTeamDMGameState( bool allocPrevious ) : rvGameState( allocPrevious ) {
trackPrevious = allocPrevious;
}
void rvTeamDMGameState::Run( void ) {
rvGameState::Run();
switch( currentState ) {
case GAMEON: {
int team = ( ( gameLocal.mpGame.GetScoreForTeam( TEAM_MARINE ) >= gameLocal.serverInfo.GetInt( "si_fragLimit" ) ) ? TEAM_MARINE : ( ( gameLocal.mpGame.GetScoreForTeam( TEAM_STROGG ) >= gameLocal.serverInfo.GetInt( "si_fragLimit" ) ) ? TEAM_STROGG : -1 ) );
if( gameLocal.serverInfo.GetInt( "si_fragLimit" ) <= 0 ) {
// no fraglimit
team = -1;
}
bool tiedForFirst = gameLocal.mpGame.GetScoreForTeam( TEAM_MARINE ) == gameLocal.mpGame.GetScoreForTeam( TEAM_STROGG );
if ( team >= 0 && !tiedForFirst ) {
if ( !fragLimitTimeout ) {
common->DPrintf( "enter FragLimit timeout, team %d is leader\n", team );
fragLimitTimeout = gameLocal.time + FRAGLIMIT_DELAY;
}
if ( gameLocal.time > fragLimitTimeout ) {
NewState( GAMEREVIEW );
gameLocal.mpGame.PrintMessageEvent( -1, MSG_FRAGLIMIT, team );
}
} 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
//
// jshepard: OR it means that the winner killed himself during the fraglimit delay, and the
// game needs to roll on.
if( tiedForFirst ) {
//this is a tie
if( gameLocal.mpGame.GetScoreForTeam( TEAM_MARINE ) >= gameLocal.serverInfo.GetInt( "si_fragLimit" ) ) {
//and it's tied at the fraglimit.
NewState( SUDDENDEATH );
}
//not a tie, game on.
fragLimitTimeout = 0;
}
} else if ( gameLocal.mpGame.TimeLimitHit() ) {
gameLocal.mpGame.PrintMessageEvent( -1, MSG_TIMELIMIT );
if( tiedForFirst ) {
// if tied at timelimit hit, goto sudden death
fragLimitTimeout = 0;
NewState( SUDDENDEATH );
} else {
// or just end the game
NewState( GAMEREVIEW );
}
} else if( tiedForFirst && team >= 0 ) {
// check for the rare case that two teams both hit the fraglimit the same frame
// two people tied at fraglimit, advance to sudden death after a delay
fragLimitTimeout = gameLocal.time + FRAGLIMIT_DELAY;
}
break;
}
case SUDDENDEATH: {
int team = gameLocal.mpGame.GetScoreForTeam( TEAM_MARINE ) > gameLocal.mpGame.GetScoreForTeam( TEAM_STROGG ) ? TEAM_MARINE : TEAM_STROGG;
bool tiedForFirst = false;
if( gameLocal.mpGame.GetScoreForTeam( TEAM_MARINE ) == gameLocal.mpGame.GetScoreForTeam( TEAM_STROGG ) ) {
team = -1;
tiedForFirst = true;
}
if ( team >= 0 && !tiedForFirst ) {
if ( !fragLimitTimeout ) {
common->DPrintf( "enter sudden death FragLeader timeout, team %d is leader\n", team );
fragLimitTimeout = gameLocal.time + FRAGLIMIT_DELAY;
}
if ( gameLocal.time > fragLimitTimeout ) {
NewState( GAMEREVIEW );
gameLocal.mpGame.PrintMessageEvent( -1, MSG_FRAGLIMIT, team );
}
} else if ( fragLimitTimeout ) {
gameLocal.mpGame.PrintMessageEvent( -1, MSG_HOLYSHIT );
fragLimitTimeout = 0;
}
break;
}
}
}
/*
===============================================================================
rvCTFGameState
Game state info for CTF
===============================================================================
*/
/*
================
rvCTFGameState::rvCTFGameState
================
*/
rvCTFGameState::rvCTFGameState( bool allocPrevious ) : rvGameState( false ) {
Clear();
if( allocPrevious ) {
previousGameState = new rvCTFGameState( false );
} else {
previousGameState = NULL;
}
trackPrevious = allocPrevious;
}
/*
================
rvCTFGameState::Clear
================
*/
void rvCTFGameState::Clear( void ) {
rvGameState::Clear();
// mekberg: clear previous game state.
if ( previousGameState ) {
previousGameState->Clear( );
}
for( int i = 0; i < TEAM_MAX; i++ ) {
flagStatus[ i ].state = FS_AT_BASE;
flagStatus[ i ].clientNum = -1;
}
for( int i = 0; i < MAX_AP; i++ ) {
apState[ i ] = AS_NEUTRAL;
}
}
/*
================
rvCTFGameState::SendState
================
*/
void rvCTFGameState::SendState( const idMessageSender &sender, int clientNum ) {
idBitMsg outMsg;
byte msgBuf[MAX_GAME_MESSAGE_SIZE];
assert( (gameLocal.isServer || gameLocal.isRepeater) && trackPrevious && IsType( rvCTFGameState::GetClassType() ) );
if( clientNum == -1 && (rvCTFGameState&)(*this) == (rvCTFGameState&)(*previousGameState) ) {
return;
}
outMsg.Init( msgBuf, sizeof( msgBuf ) );
outMsg.WriteByte( GAME_RELIABLE_MESSAGE_GAMESTATE );
WriteState( outMsg );
sender.Send( outMsg );
// don't update the state if we are working for a single client
if ( clientNum == -1 ) {
outMsg.ReadByte(); // pop off the msg ID
ReceiveState( outMsg );
}
}
/*
===============
rvCTFGameState::WriteState
===============
*/
void rvCTFGameState::WriteState( idBitMsg &msg ) {
// send off base info
rvGameState::PackState( msg );
// add CTF info
PackState( msg );
}
/*
================
rvCTFGameState::ReceiveState
================
*/
void rvCTFGameState::ReceiveState( const idBitMsg& msg ) {
assert( IsType( rvCTFGameState::GetClassType() ) );
if ( !rvGameState::BaseUnpackState( msg ) ) {
return;
}
UnpackState( msg );
if ( gameLocal.localClientNum >= 0 ) {
GameStateChanged();
}
(rvCTFGameState&)(*previousGameState) = (rvCTFGameState&)(*this);
}
/*
================
rvCTFGameState::PackState
================
*/
void rvCTFGameState::PackState( idBitMsg& outMsg ) {
// use indexing to pack in info
int index = 0;
for( int i = 0; i < TEAM_MAX; i++ ) {
if( flagStatus[ i ] != ((rvCTFGameState*)previousGameState)->flagStatus[ i ] ) {
outMsg.WriteByte( index );
outMsg.WriteByte( flagStatus[ i ].state );
outMsg.WriteByte( flagStatus[ i ].clientNum );
}
index++;
}
for( int i = 0; i < MAX_AP; i++ ) {
if( apState[ i ] != ((rvCTFGameState*)previousGameState)->apState[ i ] ) {
outMsg.WriteByte( index );
outMsg.WriteByte( apState[ i ] );
}
index++;
}
}
/*
================
rvCTFGameState::UnpackState
================
*/
void rvCTFGameState::UnpackState( const idBitMsg& inMsg ) {
while( inMsg.GetRemainingData() ) {
int index = inMsg.ReadByte();
if( index >= 0 && index < TEAM_MAX ) {
flagStatus[ index ].state = (flagState_t)inMsg.ReadByte();
flagStatus[ index ].clientNum = inMsg.ReadByte();
} else if( index >= TEAM_MAX && index < ( TEAM_MAX + MAX_AP ) ) {
apState[ index - TEAM_MAX ] = (apState_t)inMsg.ReadByte();
} else {
gameLocal.Error( "rvCTFGameState::UnpackState() - Unknown data identifier '%d'\n", index );
}
}
}
/*
================
rvCTFGameState::SendInitialState
================
*/
void rvCTFGameState::SendInitialState( const idMessageSender &sender, int clientNum ) {
assert( IsType( rvCTFGameState::GetClassType() ) );
rvCTFGameState* previousState = (rvCTFGameState*)previousGameState;
rvCTFGameState invalidState;
previousGameState = &invalidState;
SendState( sender, clientNum );
previousGameState = previousState;
}
/*
================
rvCTFGameState::GameStateChanged
================
*/
void rvCTFGameState::GameStateChanged( void ) {
// detect any base state changes
rvGameState::GameStateChanged();
// CTF specific stuff
idPlayer* player = gameLocal.GetLocalPlayer();
if ( !player ) {
if ( gameLocal.IsServerDemoPlaying() && gameLocal.GetDemoFollowClient() >= 0 ) {
player = static_cast< idPlayer * >( gameLocal.entities[ gameLocal.GetDemoFollowClient() ] );
}
}
if ( !player ) {
gameLocal.Warning( "rvCTFGameState::GameStateChanged() - NULL local player\n" ) ;
return;
}
bool noSounds = false;
for( int i = 0; i < TEAM_MAX; i++ ) {
if( flagStatus[ i ] == ((rvCTFGameState*)previousGameState)->flagStatus[ i ] ) {
continue;
}
// don't play flag messages when flag state changes as a result of the gamestate changing
if( currentState != ((rvCTFGameState*)previousGameState)->currentState && ((rvCTFGameState*)previousGameState)->currentState != INACTIVE ) {
continue;
}
if ( ((rvCTFGameState*)previousGameState)->currentState == INACTIVE ) {
noSounds = true;
}
// flagTeam - used to tell the HUD which flag to update
int flagTeam = i;
// in one flag CTF flagTeam is set to whichever team has the flag
if( gameLocal.gameType == GAME_1F_CTF || gameLocal.gameType == GAME_ARENA_1F_CTF ) {
if( flagStatus[ i ].state == FS_TAKEN_MARINE ) {
flagTeam = TEAM_MARINE;
} else if( flagStatus[ i ].state == FS_TAKEN_STROGG ) {
flagTeam = TEAM_STROGG;
}
}
if( flagStatus[ i ].state == FS_DROPPED && !noSounds ) {
if ( !player->spectating ) {
if( flagTeam == player->team ) {
// our flag was dropped, so the enemy dropped it
gameLocal.mpGame.ScheduleAnnouncerSound ( AS_CTF_ENEMY_DROPS_FLAG, gameLocal.time, -1, true );
} else {
gameLocal.mpGame.ScheduleAnnouncerSound ( AS_CTF_YOUR_TEAM_DROPS_FLAG, gameLocal.time, -1, true );
}
}
if( player->mphud ) {
player->mphud->SetStateInt( "team", flagTeam );
player->mphud->HandleNamedEvent( "flagDrop" );
if ( !player->spectating ) {
if ( flagTeam == player->team ) {
player->mphud->SetStateString( "main_notice_text", common->GetLocalizedString( "#str_107723" ) );
} else {
player->mphud->SetStateString( "main_notice_text", common->GetLocalizedString( "#str_104420" ) );
}
player->mphud->HandleNamedEvent( "main_notice" );
}
}
} else if( flagStatus[ i ].state == FS_AT_BASE ) {
if( ((rvCTFGameState*)previousGameState)->flagStatus[ i ].state == FS_TAKEN && !noSounds ) {
// team scores
if ( !player->spectating ) {
if( flagTeam == player->team ) {
if( gameLocal.mpGame.CanCapture( gameLocal.mpGame.OpposingTeam( flagTeam ) ) ) {
gameLocal.mpGame.ScheduleAnnouncerSound ( AS_TEAM_ENEMY_SCORES, gameLocal.time );
}
} else {
if( gameLocal.mpGame.CanCapture( gameLocal.mpGame.OpposingTeam( flagTeam ) ) ) {
gameLocal.mpGame.ScheduleAnnouncerSound ( AS_TEAM_YOU_SCORE, gameLocal.time );
}
}
}
} else if( ((rvCTFGameState*)previousGameState)->flagStatus[ i ].state == FS_DROPPED && !noSounds ) {
// flag returned
if ( !player->spectating ) {
if( flagTeam == player->team ) {
gameLocal.mpGame.ScheduleAnnouncerSound ( AS_CTF_YOUR_FLAG_RETURNED, gameLocal.time, -1, true );
} else {
gameLocal.mpGame.ScheduleAnnouncerSound ( AS_CTF_ENEMY_RETURNS_FLAG, gameLocal.time, -1, true );
}
}
}
if( player->mphud ) {
player->mphud->SetStateInt( "team", flagTeam );
player->mphud->HandleNamedEvent( "flagReturn" );
}
} else if( flagStatus[ i ].state == FS_TAKEN || flagStatus[ i ].state == FS_TAKEN_STROGG || flagStatus[ i ].state == FS_TAKEN_MARINE ) {
// flag taken
if( flagTeam == player->team ) {
if ( !player->spectating ) {
if ( !noSounds ) {
gameLocal.mpGame.ScheduleAnnouncerSound ( AS_CTF_ENEMY_HAS_FLAG, gameLocal.time, -1, true );
}
}
if ( !player->spectating ) {
if ( player->mphud ) {
player->mphud->SetStateString( "main_notice_text", common->GetLocalizedString( "#str_107722" ) );
player->mphud->HandleNamedEvent( "main_notice" );
}
}
} else {
if ( flagStatus[ i ].clientNum == gameLocal.localClientNum ) {
if ( !noSounds ) {
gameLocal.mpGame.ScheduleAnnouncerSound ( AS_CTF_YOU_HAVE_FLAG, gameLocal.time, -1, true );
}
if ( !player->spectating ) {
// shouchard: inform the GUI that you've taken the flag
player->mphud->SetStateString( "main_notice_text", common->GetLocalizedString( "#str_104419" ) );
player->mphud->HandleNamedEvent( "main_notice" );
}
} else if ( !noSounds ) {
if ( !player->spectating ) {
gameLocal.mpGame.ScheduleAnnouncerSound ( AS_CTF_YOUR_TEAM_HAS_FLAG, gameLocal.time, -1, true );
}
}
}
if( player->mphud ) {
player->mphud->SetStateInt( "team", flagTeam );
player->mphud->HandleNamedEvent ( "flagTaken" );
}
}
}
}
/*
================
rvCTFGameState::Run
================
*/
void rvCTFGameState::Run( void ) {
// run common stuff
rvGameState::Run();
switch( currentState ) {
case GAMEON: {
int team = ( ( gameLocal.mpGame.GetScoreForTeam( TEAM_MARINE ) >= gameLocal.serverInfo.GetInt( "si_captureLimit" ) ) ? TEAM_MARINE : ( ( gameLocal.mpGame.GetScoreForTeam( TEAM_STROGG ) >= gameLocal.serverInfo.GetInt( "si_captureLimit" ) ) ? TEAM_STROGG : -1 ) );
if( gameLocal.serverInfo.GetInt( "si_captureLimit" ) <= 0 ) {
// no capture limit games
team = -1;
}
bool tiedForFirst = gameLocal.mpGame.GetScoreForTeam( TEAM_MARINE ) == gameLocal.mpGame.GetScoreForTeam( TEAM_STROGG );
if ( team >= 0 && !tiedForFirst ) {
if ( !fragLimitTimeout ) {
common->DPrintf( "enter capture limit timeout, team %d is leader\n", team );
fragLimitTimeout = gameLocal.time + CAPTURELIMIT_DELAY;
}
if ( gameLocal.time > fragLimitTimeout ) {
NewState( GAMEREVIEW );
gameLocal.mpGame.PrintMessageEvent( -1, MSG_CAPTURELIMIT, team );
}
} 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
// OR the winner lost a point in the frag delay, and there's no tie, so no one wins, game on.
if( tiedForFirst && ( gameLocal.mpGame.GetScoreForTeam( TEAM_MARINE ) >= gameLocal.serverInfo.GetInt( "si_captureLimit" ) )) {
NewState( SUDDENDEATH );
}
fragLimitTimeout = 0;
} else if ( gameLocal.mpGame.TimeLimitHit() ) {
gameLocal.mpGame.PrintMessageEvent( -1, MSG_TIMELIMIT );
if( tiedForFirst ) {
// if tied at timelimit hit, goto sudden death
fragLimitTimeout = 0;
NewState( SUDDENDEATH );
} else {
// or just end the game
NewState( GAMEREVIEW );
}
} else if( tiedForFirst && team >= 0 ) {
// check for the rare case that two teams both hit the fraglimit the same frame
// two people tied at fraglimit, advance to sudden death after a delay
fragLimitTimeout = gameLocal.time + CAPTURELIMIT_DELAY;
}
break;
}
case SUDDENDEATH: {
int team = gameLocal.mpGame.GetScoreForTeam( TEAM_MARINE ) > gameLocal.mpGame.GetScoreForTeam( TEAM_STROGG ) ? TEAM_MARINE : TEAM_STROGG;
bool tiedForFirst = false;
if( gameLocal.mpGame.GetScoreForTeam( TEAM_MARINE ) == gameLocal.mpGame.GetScoreForTeam( TEAM_STROGG ) ) {
team = -1;
tiedForFirst = true;
}
if ( team >= 0 && !tiedForFirst ) {
if ( !fragLimitTimeout ) {
common->DPrintf( "enter sudden death FragLeader timeout, team %d is leader\n", team );
fragLimitTimeout = gameLocal.time + CAPTURELIMIT_DELAY;
}
if ( gameLocal.time > fragLimitTimeout ) {
NewState( GAMEREVIEW );
gameLocal.mpGame.PrintMessageEvent( -1, MSG_CAPTURELIMIT, team );
}
} else if ( fragLimitTimeout ) {
gameLocal.mpGame.PrintMessageEvent( -1, MSG_HOLYSHIT );
fragLimitTimeout = 0;
}
break;
}
}
}
/*
================
rvCTFGameState::SetFlagState
================
*/
void rvCTFGameState::SetFlagState( int flag, flagState_t newState ) {
if ( !gameLocal.isServer ) {
return;
}
assert( gameLocal.isServer && ( flag >= 0 && flag < TEAM_MAX ) && IsType( rvCTFGameState::GetClassType() ) );
flagStatus[ flag ].state = newState;
}
/*
================
rvCTFGameState::SetFlagCarrier
================
*/
void rvCTFGameState::SetFlagCarrier( int flag, int clientNum ) {
assert( gameLocal.isServer && ( flag >= 0 && flag < TEAM_MAX ) && (clientNum >= 0 && clientNum < MAX_CLIENTS) && IsType( rvCTFGameState::GetClassType() ) );
flagStatus[ flag ].clientNum = clientNum;
}
/*
================
rvCTFGameState::operator==
================
*/
bool rvCTFGameState::operator==( const rvCTFGameState& rhs ) const {
if( (rvGameState&)(*this) != (rvGameState&)rhs ) {
return false;
}
for( int i = 0; i < TEAM_MAX; i++ ) {
if( flagStatus[ i ] != rhs.flagStatus[ i ] ) {
return false;
}
}
for( int i = 0; i < MAX_AP; i++ ) {
if( apState[ i ] != rhs.apState[ i ] ) {
return false;
}
}
return true;
}
/*
================
rvCTFGameState::operator=
================
*/
rvCTFGameState& rvCTFGameState::operator=( const rvCTFGameState& rhs ) {
(rvGameState&)(*this) = (rvGameState&)rhs;
for( int i = 0; i < TEAM_MAX; i++ ) {
flagStatus[ i ] = rhs.flagStatus[ i ];
}
for( int i = 0; i < MAX_AP; i++ ) {
apState[ i ] = rhs.apState[ i ];
}
return (*this);
}
/*
===============================================================================
rvTourneyGameState
Game state info for tourney
===============================================================================
*/
/*
================
rvTourneyGameState::rvTourneyGameState
================
*/
rvTourneyGameState::rvTourneyGameState( bool allocPrevious ) : rvGameState( false ) {
Clear();
if( allocPrevious ) {
previousGameState = new rvTourneyGameState( false );
} else {
previousGameState = NULL;
}
packTourneyHistory = false;
trackPrevious = allocPrevious;
}
/*
================
rvTourneyGameState::Clear
================
*/
void rvTourneyGameState::Clear( void ) {
assert( IsType( rvTourneyGameState::GetClassType() ) );
// mekberg: clear previous game state.
if ( previousGameState ) {
previousGameState->Clear();
}
rvGameState::Clear();
tourneyState = TS_INVALID;
for( int i = 0; i < MAX_ARENAS; i++ ) {
arenas[ i ].Clear( false );
arenas[ i ].SetArenaID( i );
}
for( int i = 0; i < MAX_ROUNDS; i++ ) {
for( int j = 0; j < MAX_ARENAS; j++ ) {
tourneyHistory[ i ][ j ].playerOne[ 0 ] = '\0';
tourneyHistory[ i ][ j ].playerTwo[ 0 ] = '\0';
tourneyHistory[ i ][ j ].playerOneNum = -1;
tourneyHistory[ i ][ j ].playerTwoNum = -1;
tourneyHistory[ i ][ j ].playerOneScore = -1;
tourneyHistory[ i ][ j ].playerTwoScore = -1;
}
}
startingRound = 0;
maxRound = 0;
round = -1;
byeArena = -1;
tourneyCount = 0;
roundTimeout = 0;
}
/*
================
rvTourneyGameState::Reset
================
*/
void rvTourneyGameState::Reset( void ) {
for( int i = 0; i < MAX_ARENAS; i++ ) {
arenas[ i ].Clear( false );
arenas[ i ].SetArenaID( i );
}
for( int i = 0; i < MAX_ROUNDS; i++ ) {
for( int j = 0; j < MAX_ARENAS; j++ ) {
tourneyHistory[ i ][ j ].playerOne[ 0 ] = '\0';
tourneyHistory[ i ][ j ].playerTwo[ 0 ] = '\0';
tourneyHistory[ i ][ j ].playerOneNum = -1;
tourneyHistory[ i ][ j ].playerTwoNum = -1;
tourneyHistory[ i ][ j ].playerOneScore = -1;
tourneyHistory[ i ][ j ].playerTwoScore = -1;
}
}
startingRound = 0;
maxRound = 0;
round = -1;
byeArena = -1;
roundTimeout = 0;
}
/*
================
rvTourneyGameState::Run
================
*/
void rvTourneyGameState::Run( void ) {
assert( IsType( rvTourneyGameState::GetClassType() ) );
rvGameState::Run();
switch( currentState ) {
case GAMEON: {
// check to see if we need to advance to the next round
if( nextState == GAMEREVIEW ) {
return;
}
bool roundDone = true;
for( int i = 0; i < MAX_ARENAS; i++ ) {
if( arenas[ i ].GetState() == AS_INACTIVE ) {
continue;
}
arenas[ i ].UpdateState();
if( arenas[ i ].GetState() != AS_DONE ) {
// check to see if we're done
roundDone = false;
}
}
if( roundDone && !roundTimeout ) {
roundTimeout = gameLocal.time + FRAGLIMIT_DELAY;
}
if( roundDone && gameLocal.time > roundTimeout ) {
int pastRound = round - 1; //rounds are 1 indexed
idList<rvPair<idPlayer*, int> > advancingPlayers;
round++;
roundTimeout = 0;
assert( round >= 2 );
// copy over tourney history for the previous round
UpdateTourneyHistory( pastRound );
// transmit history
forceTourneyHistory = true;
// add who will advance to the next round
for( int i = 0; i < MAX_ARENAS; i++ ) {
if( arenas[ i ].GetState() == AS_DONE && arenas[ i ].GetWinner() ) {
advancingPlayers.Append( rvPair<idPlayer*, int>( arenas[ i ].GetWinner(), i ) );
gameLocal.mpGame.AddPlayerWin( arenas[ i ].GetWinner(), 1 );
}
}
// when only one player advances, that player has won the tournament
if( advancingPlayers.Num() == 1 ) {
idPlayer* winner = advancingPlayers[ 0 ].First();
assert( winner );
for( int i = 0; i < MAX_ARENAS; i++ ) {
arenas[ i ].Clear();
}
gameLocal.Printf( "Round %d: player %d (%s) has won the tourney!\n", round - 1, winner->entityNumber, gameLocal.userInfo[ winner->entityNumber ].GetString( "ui_name" ) );
// award 5 wins for winning the entire tournament
gameLocal.mpGame.AddPlayerWin( winner, 5 );
nextState = GAMEREVIEW;
nextStateTime = gameLocal.time + 5000;
return;
}
// see if we have to swap a player from the byeArena this round
bool swapBye = false;
int thisByeArena = -1;
if( byeArena >= 0 ) {
// look at the bye arena from the round that just ended
thisByeArena = byeArena / (round - 1);
// if the bye arena is playing in the next round, there's no need to switch players
if( !(thisByeArena % 2) ) {
if( arenas[ thisByeArena ].GetState() == AS_DONE && arenas[ thisByeArena + 1 ].GetState() == AS_DONE ) {
assert( arenas[ thisByeArena ].GetWinner() && arenas[ thisByeArena + 1 ].GetWinner() );
swapBye = false;
} else {
swapBye = true;
}
} else {
if( arenas[ thisByeArena ].GetState() == AS_DONE && arenas[ thisByeArena - 1 ].GetState() == AS_DONE ) {
assert( arenas[ thisByeArena ].GetWinner() && arenas[ thisByeArena - 1 ].GetWinner() );
swapBye = false;
} else {
swapBye = true;
}
}
}
idPlayer* oldByePlayer = NULL;
idPlayer* newByePlayer = NULL;
if( swapBye ) {
oldByePlayer = arenas[ thisByeArena ].GetWinner();
// pick a new bye player only from players who will be fighting next round
// i.e., don't swap the bye player with a player who will have a disconnect bye this upcoming round
idList<rvPair<idPlayer*, int> > nextRoundPlayers;
for ( int i = 0; i < MAX_ARENAS; i += 2 ) {
if( arenas[ i ].GetState() == AS_DONE && arenas[ i ].GetWinner() && arenas[ i + 1 ].GetState() == AS_DONE && arenas[ i + 1 ].GetWinner() ) {
nextRoundPlayers.Append( rvPair<idPlayer*, int>( arenas[ i ].GetWinner(), i ) );
nextRoundPlayers.Append( rvPair<idPlayer*, int>( arenas[ i + 1 ].GetWinner(), i + 1 ) );
}
}
if ( nextRoundPlayers.Num() ) {
newByePlayer = nextRoundPlayers[ gameLocal.random.RandomInt( nextRoundPlayers.Num() ) ].First();
}
}
// assign arenas for the next round
for ( int i = 0; i < MAX_ARENAS; i += 2 ) {
idPlayer* advanceOne = arenas[ i ].GetWinner();
idPlayer* advanceTwo = arenas[ i + 1 ].GetWinner();
// #13266 - bug: bystander from prev round is spectator in his match arena after round change
// we call rvTourneyInstance::Clear when setting up rounds (below), which sets it's players as spectator
// if the former bystander is placed in one of the new arenas and is still referenced in the bye arena then he'll get set spectating
// so just clear him up once identified
// #13631 - we used to call RemovePlayer before entering that loop, but that was messing up the selection of the next bystander
// so have to do it inside the loop now, just before it happens
if ( ( i == thisByeArena || i + 1 == thisByeArena ) && arenas[ thisByeArena ].HasPlayer( oldByePlayer ) ) {
arenas[ thisByeArena ].RemovePlayer( oldByePlayer );
}
arenas[ i ].Clear();
arenas[ i + 1 ].Clear();
// assign new arenas, swapping oldByePlayer with newByePlayer
if ( newByePlayer && oldByePlayer ) {
if ( advanceOne == newByePlayer ) {
advanceOne = oldByePlayer;
} else if ( advanceTwo == newByePlayer ) {
advanceTwo = oldByePlayer;
} else if ( advanceOne == oldByePlayer ) {
advanceOne = newByePlayer;
} else if ( advanceTwo == oldByePlayer ) {
advanceTwo = newByePlayer;
}
}
if ( advanceOne || advanceTwo ) {
arenas[ (i / 2) ].AddPlayers( advanceOne, advanceTwo );
if ( advanceOne ) {
advanceOne->JoinInstance( (i / 2) );
advanceOne->ServerSpectate( false );
}
if ( advanceTwo ) {
advanceTwo->JoinInstance( (i / 2) );
advanceTwo->ServerSpectate( false );
}
gameLocal.Printf( "Round %d: Arena %d is starting play with players %d (%s) and %d (%s)\n", round, (i / 2), advanceOne ? advanceOne->entityNumber : -1, advanceOne ? advanceOne->GetUserInfo()->GetString( "ui_name" ) : "NULL", advanceTwo ? advanceTwo->entityNumber : -1, advanceTwo ? advanceTwo->GetUserInfo()->GetString( "ui_name" ) : "NULL" );
arenas[ (i / 2) ].Ready();
}
}
// once the new round is setup, go through and make sure all the spectators are in valid arena
for( int i = 0; i < gameLocal.numClients; i++ ) {
idPlayer* p = (idPlayer*)gameLocal.entities[ i ];
// re-select our arena since round changed
if( p && p->spectating ) {
rvTourneyArena& thisArena = arenas[ p->GetArena() ];
if( thisArena.GetState() != AS_WARMUP ) {
p->JoinInstance( GetNextActiveArena( p->GetArena() ) );
}
}
}
}
break;
}
}
}
/*
================
rvTourneyGameState::NewState
================
*/
void rvTourneyGameState::NewState( mpGameState_t newState ) {
assert( (newState != currentState) && gameLocal.isServer );
switch( newState ) {
case WARMUP: {
Reset();
// force everyone to spectate
for( int i = 0; i < MAX_CLIENTS; i++ ) {
if( gameLocal.entities[ i ] ) {
((idPlayer*)gameLocal.entities[ i ])->ServerSpectate( true );
}
}
break;
}
case GAMEON: {
tourneyCount++;
SetupInitialBrackets();
break;
}
}
rvGameState::NewState( newState );
}
/*
================
rvTourneyGameState::GameStateChanged
================
*/
void rvTourneyGameState::GameStateChanged( void ) {
assert( IsType( rvTourneyGameState::GetClassType() ) );
rvGameState::GameStateChanged();
idPlayer* localPlayer = gameLocal.GetLocalPlayer();
int oldRound = ((rvTourneyGameState*)previousGameState)->round;
if( round != oldRound ) {
if ( round > maxRound && gameLocal.GetLocalPlayer() ) {
gameLocal.GetLocalPlayer()->ForceScoreboard( true, gameLocal.time + 5000 );
gameLocal.mpGame.ScheduleAnnouncerSound( AS_TOURNEY_DONE, gameLocal.time );
}
// we're starting a new round
gameLocal.mpGame.tourneyGUI.RoundStart();
// skip announce if the round number doesn't make sense
if ( round >= 1 && round <= MAX_ROUNDS ) {
// play the sound a bit after round restart to let spawn sounds settle
gameLocal.mpGame.ScheduleAnnouncerSound( (announcerSound_t)( AS_TOURNEY_PRELIMS + round - 1 ), gameLocal.time + 1500);
}
}
for( int i = 0; i < MAX_ARENAS; i++ ) {
rvTourneyArena& thisArena = arenas[ i ];
rvTourneyArena& previousArena = ((rvTourneyGameState*)previousGameState)->arenas[ i ];
if( ((thisArena.GetPlayers()[ 0 ] != previousArena.GetPlayers()[ 0 ]) ||
(thisArena.GetPlayers()[ 1 ] != previousArena.GetPlayers()[ 1 ]) ||
(round != ((rvTourneyGameState*)previousGameState)->round )) /*&&
!((thisArena.GetPlayers()[ 0 ] == NULL && thisArena.GetPlayers()[ 1 ] != NULL) ||
(thisArena.GetPlayers()[ 0 ] != NULL && thisArena.GetPlayers()[ 1 ] == NULL) ) */) {
// don't re-init arenas that have changed and been done for a while
if( thisArena.GetState() != AS_DONE || previousArena.GetState() != AS_DONE ) {
gameLocal.mpGame.tourneyGUI.ArenaStart( i );
}
if( localPlayer && thisArena.GetPlayers()[ 0 ] == localPlayer ) {
gameLocal.mpGame.tourneyGUI.ArenaSelect( i, TGH_PLAYER_ONE );
}
if( localPlayer && thisArena.GetPlayers()[ 1 ] == localPlayer ) {
gameLocal.mpGame.tourneyGUI.ArenaSelect( i, TGH_PLAYER_TWO );
}
}
if( localPlayer->GetArena() == thisArena.GetArenaID() && thisArena.GetState() == AS_SUDDEN_DEATH && thisArena.GetState() != previousArena.GetState() ) {
gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_SUDDEN_DEATH, gameLocal.time, thisArena.GetArenaID() );
gameLocal.GetLocalPlayer()->GUIMainNotice( common->GetLocalizedString( "#str_104287" ) );
}
if( thisArena.GetState() == AS_DONE && thisArena.GetState() != previousArena.GetState() ) {
if( thisArena.GetWinner() != previousArena.GetWinner() && thisArena.GetWinner() == gameLocal.GetLocalPlayer() ) {
if( round >= maxRound ) {
gameLocal.mpGame.ScheduleAnnouncerSound( AS_TOURNEY_WON, gameLocal.time );
} else {
gameLocal.mpGame.ScheduleAnnouncerSound( AS_TOURNEY_ADVANCE, gameLocal.time );
}
} else if( thisArena.GetLoser() != previousArena.GetLoser() && thisArena.GetLoser() == gameLocal.GetLocalPlayer() ) {
gameLocal.mpGame.ScheduleAnnouncerSound( AS_TOURNEY_ELIMINATED, gameLocal.time );
}
if( previousArena.GetWinner() == NULL && thisArena.GetWinner() != NULL ) {
gameLocal.mpGame.tourneyGUI.ArenaDone( i );
}
// on the client, add this result to our tourneyHistory
//if( gameLocal.isClient && thisArena.GetPlayers()[ 0 ] && thisArena.GetPlayers()[ 1 ] && (round - 1 ) >= 0 && (round - 1) < MAX_ROUNDS ) {
// tourneyHistory[ round - 1 ][ i ].playerOneScore = gameLocal.mpGame.GetTeamScore( thisArena.GetPlayers()[ 0 ] );
// tourneyHistory[ round - 1 ][ i ].playerTwoScore = gameLocal.mpGame.GetTeamScore( thisArena.GetPlayers()[ 1 ] );
//}
}
if( localPlayer && (thisArena.GetState() == AS_WARMUP) && (thisArena.GetState() != previousArena.GetState()) && localPlayer->GetArena() == thisArena.GetArenaID() ) {
gameLocal.mpGame.RemoveAnnouncerSound( AS_GENERAL_PREPARE_TO_FIGHT );
gameLocal.mpGame.RemoveAnnouncerSound( AS_GENERAL_THREE );
gameLocal.mpGame.RemoveAnnouncerSound( AS_GENERAL_TWO );
gameLocal.mpGame.RemoveAnnouncerSound( AS_GENERAL_ONE );
int warmupEndTime = gameLocal.time + ( gameLocal.serverInfo.GetInt( "si_countdown" ) * 1000 ) + 5000;
gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_PREPARE_TO_FIGHT, gameLocal.time + 5000 );
gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_THREE, warmupEndTime - 3000 );
gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_TWO, warmupEndTime - 2000 );
gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_ONE, warmupEndTime - 1000 );
localPlayer->vsMsgState = true;
localPlayer->GUIMainNotice( va( "%s^0 vs. %s^0", thisArena.GetPlayerName( 0 ), thisArena.GetPlayerName( 1 ) ), true );
}
if( (thisArena.GetState() == AS_ROUND) && (thisArena.GetState() != previousArena.GetState()) ) {
if( localPlayer && localPlayer->GetArena() == thisArena.GetArenaID() ) {
gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_FIGHT, gameLocal.time );
gameLocal.mpGame.ScheduleTimeAnnouncements();
}
}
}
if( ((rvTourneyGameState*)previousGameState)->currentState != currentState ) {
if( currentState == WARMUP ) {
gameLocal.mpGame.tourneyGUI.PreTourney();
} else if( currentState == COUNTDOWN ) {
if( currentState == COUNTDOWN && ((rvTourneyGameState*)previousGameState)->currentState != INACTIVE ) {
gameLocal.mpGame.ScheduleAnnouncerSound( AS_TOURNEY_START, gameLocal.time);
}
} else if( currentState == GAMEON ) {
gameLocal.mpGame.tourneyGUI.TourneyStart();
}
}
if( packTourneyHistory ) {
// apply history
gameLocal.mpGame.tourneyGUI.SetupTourneyHistory( startingRound, round - 1, tourneyHistory );
packTourneyHistory = false;
}
if( localPlayer && localPlayer->spectating ) {
rvTourneyArena& thisArena = arenas[ localPlayer->GetArena() ];
if( thisArena.GetPlayers()[ 0 ] == NULL || thisArena.GetPlayers()[ 1 ] == NULL || (localPlayer->spectating && localPlayer->spectator != thisArena.GetPlayers()[ 0 ]->entityNumber && localPlayer->spectator != thisArena.GetPlayers()[ 1 ]->entityNumber) ) {
gameLocal.mpGame.tourneyGUI.ArenaSelect( localPlayer->GetArena(), TGH_BRACKET );
} else if ( thisArena.GetPlayers()[ 0 ] == localPlayer || (localPlayer->spectating && thisArena.GetPlayers()[ 0 ]->entityNumber == localPlayer->spectator) ) {
gameLocal.mpGame.tourneyGUI.ArenaSelect( localPlayer->GetArena(), TGH_PLAYER_ONE );
} else if ( thisArena.GetPlayers()[ 1 ] == localPlayer || (localPlayer->spectating && thisArena.GetPlayers()[ 1 ]->entityNumber == localPlayer->spectator) ) {
gameLocal.mpGame.tourneyGUI.ArenaSelect( localPlayer->GetArena(), TGH_PLAYER_TWO );
}
}
}
/*
================
msgTourney_t
Identifiers for transmitted state
================
*/
typedef enum {
MSG_TOURNEY_TOURNEYSTATE,
MSG_TOURNEY_STARTINGROUND,
MSG_TOURNEY_ROUND,
MSG_TOURNEY_MAXROUND,
MSG_TOURNEY_HISTORY,
MSG_TOURNEY_TOURNEYCOUNT,
MSG_TOURNEY_ARENAINFO
} msgTourney_t;
/*
================
rvTourneyGameState::PackState
================
*/
void rvTourneyGameState::PackState( idBitMsg& outMsg ) {
assert( IsType( rvTourneyGameState::GetClassType() ) );
if ( tourneyState != ((rvTourneyGameState*)previousGameState)->tourneyState ) {
outMsg.WriteByte( MSG_TOURNEY_TOURNEYSTATE );
outMsg.WriteByte( tourneyState );
}
if ( startingRound != ((rvTourneyGameState*)previousGameState)->startingRound ) {
outMsg.WriteByte( MSG_TOURNEY_STARTINGROUND );
outMsg.WriteByte( startingRound );
}
if( round != ((rvTourneyGameState*)previousGameState)->round ) {
outMsg.WriteByte( MSG_TOURNEY_ROUND );
outMsg.WriteByte( round );
}
if( maxRound != ((rvTourneyGameState*)previousGameState)->maxRound ) {
outMsg.WriteByte( MSG_TOURNEY_MAXROUND );
outMsg.WriteByte( maxRound );
}
for ( int i = 0; i < MAX_ARENAS; i++ ) {
if ( arenas[ i ] != ((rvTourneyGameState*)previousGameState)->arenas[ i ] ) {
outMsg.WriteByte( MSG_TOURNEY_ARENAINFO + i );
arenas[ i ].PackState( outMsg );
}
}
if ( packTourneyHistory || forceTourneyHistory ) {
if ( forceTourneyHistory ) {
common->Warning( "forcing down tourney history\n" );
}
// don't write out uninitialized tourney history
if ( startingRound > 0 && round > 1 ) {
outMsg.WriteByte( MSG_TOURNEY_HISTORY );
// client might not yet have startingRound or round
outMsg.WriteByte( startingRound );
outMsg.WriteChar( round );
outMsg.WriteByte( maxRound );
for( int i = startingRound - 1; i <= Min( (round - 1), (maxRound - 1) ); i++ ) {
for( int j = 0; j < MAX_ARENAS / (i + 1); j++ ) {
outMsg.WriteString( tourneyHistory[ i ][ j ].playerOne, MAX_TOURNEY_HISTORY_NAME_LEN );
outMsg.WriteChar( idMath::ClampChar( tourneyHistory[ i ][ j ].playerOneScore ) );
outMsg.WriteString( tourneyHistory[ i ][ j ].playerTwo, MAX_TOURNEY_HISTORY_NAME_LEN );
outMsg.WriteChar( idMath::ClampChar( tourneyHistory[ i ][ j ].playerTwoScore ) );
outMsg.WriteChar( tourneyHistory[ i ][ j ].playerOneNum );
outMsg.WriteChar( tourneyHistory[ i ][ j ].playerTwoNum );
}
}
packTourneyHistory = false;
}
}
if ( tourneyCount != ((rvTourneyGameState*)previousGameState)->tourneyCount ) {
outMsg.WriteByte( MSG_TOURNEY_TOURNEYCOUNT );
outMsg.WriteByte( tourneyCount );
}
}
/*
================
rvTourneyGameState::UnpackState
================
*/
void rvTourneyGameState::UnpackState( const idBitMsg& inMsg ) {
assert( IsType( rvTourneyGameState::GetClassType() ) );
while( inMsg.GetRemainingData() ) {
int index = inMsg.ReadByte();
switch( index ) {
case MSG_TOURNEY_TOURNEYSTATE: {
tourneyState = (tourneyState_t)inMsg.ReadByte();
break;
}
case MSG_TOURNEY_STARTINGROUND: {
startingRound = inMsg.ReadByte();
break;
}
case MSG_TOURNEY_ROUND: {
round = inMsg.ReadByte();
// not a great way of doing this, should clamp the values
if ( round == 255 ) {
round = -1;
}
break;
}
case MSG_TOURNEY_MAXROUND: {
maxRound = inMsg.ReadByte();
if ( maxRound == 255 ) {
maxRound = -1;
}
break;
}
case MSG_TOURNEY_HISTORY: {
int startRound = inMsg.ReadByte();
int rnd = inMsg.ReadChar();
int maxr = inMsg.ReadByte();
assert( rnd >= 1 ); // something is uninitialized
for( int i = startRound - 1; i <= Min( (rnd - 1), (maxr - 1) ); i++ ) {
for( int j = 0; j < MAX_ARENAS / (i + 1); j++ ) {
inMsg.ReadString( tourneyHistory[ i ][ j ].playerOne, MAX_TOURNEY_HISTORY_NAME_LEN );
tourneyHistory[ i ][ j ].playerOneScore = inMsg.ReadChar();
inMsg.ReadString( tourneyHistory[ i ][ j ].playerTwo, MAX_TOURNEY_HISTORY_NAME_LEN );
tourneyHistory[ i ][ j ].playerTwoScore = inMsg.ReadChar();
tourneyHistory[ i ][ j ].playerOneNum = inMsg.ReadChar();
tourneyHistory[ i ][ j ].playerTwoNum = inMsg.ReadChar();
if( tourneyHistory[ i ][ j ].playerOneNum < 0 || tourneyHistory[ i ][ j ].playerOneNum >= MAX_CLIENTS ) {
tourneyHistory[ i ][ j ].playerOneNum = -1;
}
if( tourneyHistory[ i ][ j ].playerTwoNum < 0 || tourneyHistory[ i ][ j ].playerTwoNum >= MAX_CLIENTS ) {
tourneyHistory[ i ][ j ].playerTwoNum = -1;
}
}
}
// client side (and listen server) no reason to check for history change all the time
packTourneyHistory = true;
break;
}
case MSG_TOURNEY_TOURNEYCOUNT: {
tourneyCount = inMsg.ReadByte();
break;
}
default: {
if ( index >= MSG_TOURNEY_ARENAINFO && index < MSG_TOURNEY_ARENAINFO + MAX_ARENAS ) {
arenas[ index - MSG_TOURNEY_ARENAINFO ].UnpackState( inMsg );
} else {
gameLocal.Error( "rvTourneyGameState::UnpackState() - Unknown data identifier '%d'\n", index );
}
break;
}
}
}
}
/*
================
rvTourneyGameState::SendState
================
*/
void rvTourneyGameState::SendState( const idMessageSender &sender, int clientNum ) {
idBitMsg outMsg;
byte msgBuf[MAX_GAME_MESSAGE_SIZE];
assert( (gameLocal.isServer || gameLocal.isRepeater) && trackPrevious && IsType( rvTourneyGameState::GetClassType() ) );
if ( clientNum == -1 && (rvTourneyGameState&)(*this) == (rvTourneyGameState&)(*previousGameState) ) {
return;
}
outMsg.Init( msgBuf, sizeof( msgBuf ) );
outMsg.WriteByte( GAME_RELIABLE_MESSAGE_GAMESTATE );
WriteState( outMsg );
if( clientNum == -1 && forceTourneyHistory ) {
forceTourneyHistory = false;
}
sender.Send( outMsg );
// don't update the state if we are working for a single client
if ( clientNum == -1 ) {
outMsg.ReadByte(); // pop off the msg ID
ReceiveState( outMsg );
}
}
/*
===============
rvTourneyGameState::WriteState
===============
*/
void rvTourneyGameState::WriteState( idBitMsg &msg ) {
// send off base info
rvGameState::PackState( msg );
// add Tourney info
PackState( msg );
}
/*
================
rvTourneyGameState::ReceiveState
================
*/
void rvTourneyGameState::ReceiveState( const idBitMsg& msg ) {
assert( IsType( rvTourneyGameState::GetClassType() ) );
if ( !rvGameState::BaseUnpackState( msg ) ) {
return;
}
UnpackState( msg );
if ( gameLocal.localClientNum >= 0 ) {
GameStateChanged();
}
(rvTourneyGameState&)(*previousGameState) = (rvTourneyGameState&)(*this);
}
/*
================
rvTourneyGameState::SendInitialState
================
*/
void rvTourneyGameState::SendInitialState( const idMessageSender &sender, int clientNum ) {
assert( IsType( rvTourneyGameState::GetClassType() ) );
rvTourneyGameState* previousState = (rvTourneyGameState*)previousGameState;
rvTourneyGameState invalidState;
previousGameState = &invalidState;
// if the tourney has been going on, transmit the tourney history
if( round > 0 ) {
// comparing the tourney history for all state changes is wasteful when we really just want to send it to new clients
packTourneyHistory = true;
}
SendState( sender, clientNum );
previousGameState = previousState;
}
/*
================
rvTourneyGameState::operator==
================
*/
bool rvTourneyGameState::operator==( const rvTourneyGameState& rhs ) const {
assert( IsType( rvTourneyGameState::GetClassType() ) && rhs.IsType( rvTourneyGameState::GetClassType() ) );
if( (rvGameState&)(*this) != (rvGameState&)rhs ) {
return false;
}
if( round != rhs.round || startingRound != rhs.startingRound || maxRound != rhs.maxRound || tourneyState != rhs.tourneyState ) {
return false;
}
for( int i = 0; i < MAX_ARENAS; i++ ) {
if( arenas[ i ] != rhs.arenas[ i ] ) {
return false;
}
}
return true;
}
/*
================
rvTourneyGameState::operator=
================
*/
rvTourneyGameState& rvTourneyGameState::operator=( const rvTourneyGameState& rhs ) {
assert( IsType( rvTourneyGameState::GetClassType() ) && rhs.IsType( rvTourneyGameState::GetClassType() ) );
(rvGameState&)(*this) = (rvGameState&)rhs;
round = rhs.round;
startingRound = rhs.startingRound;
maxRound = rhs.maxRound;
tourneyState = rhs.tourneyState;
for( int i = 0; i < MAX_ARENAS; i++ ) {
arenas[ i ] = rhs.arenas[ i ];
}
return (*this);
}
/*
================
rvTourneyGameState::GetNumArenas
Returns number of active arenas
================
*/
int rvTourneyGameState::GetNumArenas( void ) const {
assert( IsType( rvTourneyGameState::GetClassType() ) );
int num = 0;
for( int i = 0; i < MAX_ARENAS; i++ ) {
if( arenas[ i ].GetState() != AS_INACTIVE && arenas[ i ].GetState() != AS_DONE ) {
num++;
}
}
return num;
}
/*
================
rvTourneyGameState::SetupInitialBrackets
Sets up the brackets for a new match. fragCount in playerstate is the player's
persistant frag count over an entire level. In teamFragCount we store this
rounds score.
Initial bracket seeds are random
================
*/
void rvTourneyGameState::SetupInitialBrackets( void ) {
assert( IsType( rvTourneyGameState::GetClassType() ) );
idList<idPlayer*> unseededPlayers;
int numRankedPlayers = gameLocal.mpGame.GetNumRankedPlayers();
// all this crazy math does is figure out:
// 8 arenas to 4 rounds ( round 1: 8 arenas, round 2: 4 arenas, round 3: 2 arenas, round 4: 1 arena )
// 16 arenas to 5 rounds ( round 1: 16 arenas, round 2: 8 arenas, round 3: 4 arenas, round 4: 2 arenas, round 5: 1 arena )
// etc
int newMaxRound = idMath::ILog2( Max( idMath::CeilPowerOfTwo( MAX_ARENAS * 2 ), 2 ) );
// we start at a round appropriate for our # of people
// If you have 1-2 players, start in maxRound, if you have 3-4 players start in maxRound - 1, if you have 5-8 players
// start in maxRound - 2, if you have 9 - 16 players start in maxRound - 3, etc.
int newRound = newMaxRound - idMath::ILog2( Max( idMath::CeilPowerOfTwo( gameLocal.mpGame.GetNumRankedPlayers() ), 2 ) ) + 1;
round = newRound;
maxRound = newMaxRound;
startingRound = round;
for( int i = 0; i < numRankedPlayers; i++ ) {
unseededPlayers.Append( gameLocal.mpGame.GetRankedPlayer( i ) );
}
int numArenas = 0;
while ( unseededPlayers.Num() > 1 ) {
idPlayer* playerOne = unseededPlayers[ gameLocal.random.RandomInt( unseededPlayers.Num() ) ];
unseededPlayers.Remove( playerOne );
idPlayer* playerTwo = unseededPlayers[ gameLocal.random.RandomInt( unseededPlayers.Num() ) ];
unseededPlayers.Remove( playerTwo );
rvTourneyArena& arena = arenas[ numArenas ];
arena.Clear();
arena.AddPlayers( playerOne, playerTwo );
// place the players in the correct instance
playerOne->JoinInstance( numArenas );
playerTwo->JoinInstance( numArenas );
playerOne->ServerSpectate( false );
playerTwo->ServerSpectate( false );
gameLocal.mpGame.SetPlayerTeamScore( playerOne, 0 );
gameLocal.mpGame.SetPlayerTeamScore( playerTwo, 0 );
gameLocal.Printf( "rvTourneyGameState::SetupInitialBrackets() - %s will face %s in arena %d\n", playerOne->GetUserInfo()->GetString( "ui_name" ), playerTwo->GetUserInfo()->GetString( "ui_name" ), numArenas );
// this arena is ready to play
arena.Ready();
numArenas++;
}
assert( unseededPlayers.Num() == 0 || unseededPlayers.Num() == 1 );
if( unseededPlayers.Num() ) {
assert( unseededPlayers[ 0 ] );
// the mid player gets a bye
unseededPlayers[ 0 ]->SetTourneyStatus( PTS_UNKNOWN );
unseededPlayers[ 0 ]->ServerSpectate( true );
arenas[ numArenas ].AddPlayers( unseededPlayers[ 0 ], NULL );
arenas[ numArenas ].Ready();
// mark the ancestor arena of the bye player
byeArena = numArenas * startingRound;
} else {
byeArena = -1;
}
}
/*
================
rvTourneyGameState::ClientDisconnect
A player has disconnected from the server
================
*/
void rvTourneyGameState::ClientDisconnect( idPlayer* player ) {
assert( IsType( rvTourneyGameState::GetClassType() ) );
if( gameLocal.isClient ) {
return;
}
// go through the tourney history and copy over the disconnecting player's name
if( startingRound > 0 && round <= MAX_ROUNDS ) {
for( int i = startingRound - 1; i < round - 1; i++ ) {
for( int j = 0; j < MAX_ARENAS / (i + 1); j++ ) {
if( tourneyHistory[ i ][ j ].playerOneNum == player->entityNumber ) {
idStr::Copynz( tourneyHistory[ i ][ j ].playerOne, player->GetUserInfo()->GetString( "ui_name" ), MAX_TOURNEY_HISTORY_NAME_LEN );
tourneyHistory[ i ][ j ].playerOneNum = -1;
} else if( tourneyHistory[ i ][ j ].playerTwoNum == player->entityNumber ) {
idStr::Copynz( tourneyHistory[ i ][ j ].playerTwo, player->GetUserInfo()->GetString( "ui_name" ), MAX_TOURNEY_HISTORY_NAME_LEN );
tourneyHistory[ i ][ j ].playerTwoNum = -1;
}
}
}
// copy over the current rounds info if the player is playing
for( int i = 0; i < MAX_ARENAS; i++ ) {
if( arenas[ i ].GetPlayers()[ 0 ] == player ) {
tourneyHistory[ round - 1 ][ i ].playerOneNum = -1;
idStr::Copynz( tourneyHistory[ round - 1 ][ i ].playerOne, player->GetUserInfo()->GetString( "ui_name" ), MAX_TOURNEY_HISTORY_NAME_LEN );
tourneyHistory[ round - 1 ][ i ].playerOneScore = gameLocal.mpGame.GetTeamScore( player );
} else if( arenas[ i ].GetPlayers()[ 1 ] == player ) {
tourneyHistory[ round - 1 ][ i ].playerTwoNum = -1;
idStr::Copynz( tourneyHistory[ round - 1 ][ i ].playerTwo, player->GetUserInfo()->GetString( "ui_name" ), MAX_TOURNEY_HISTORY_NAME_LEN );
tourneyHistory[ round - 1 ][ i ].playerTwoScore = gameLocal.mpGame.GetTeamScore( player );
}
}
// retransmit tourney history to everyone
packTourneyHistory = true;
}
RemovePlayer( player );
// give RemovePlayer() a chance to update tourney history if needed
if( packTourneyHistory ) {
for( int i = 0; i < gameLocal.numClients; i++ ) {
if( i == player->entityNumber ) {
continue;
}
if( gameLocal.entities[ i ] ) {
packTourneyHistory = true;
SendState( serverReliableSender.To( i, true ), i );
}
}
if ( gameLocal.isRepeater ) {
packTourneyHistory = true;
SendState( repeaterReliableSender.To( -1 ), ENTITYNUM_NONE );
}
packTourneyHistory = false;
}
}
/*
================
rvTourneyGameState::Spectate
================
*/
void rvTourneyGameState::Spectate( idPlayer* player ) {
assert( IsType( rvTourneyGameState::GetClassType() ) );
assert( gameLocal.isServer || player->IsFakeClient() );
if( player->spectating && player->wantSpectate ) {
RemovePlayer( player );
}
}
/*
================
rvTourneyGameState::RemovePlayer
Removes the specified player from the arena
================
*/
void rvTourneyGameState::RemovePlayer( idPlayer* player ) {
assert( IsType( rvTourneyGameState::GetClassType() ) );
if( round < 1 || round > maxRound ) {
return;
}
for( int i = 0; i < MAX_ARENAS; i++ ) {
idPlayer** players = GetArenaPlayers( i );
if( players[ 0 ] == player || players[ 1 ] == player ) {
rvTourneyArena& arena = arenas[ i ];
idPlayer* remainingPlayer = players[ 0 ] == player ? players[ 1 ] : players[ 0 ];
bool arenaInProgress = arena.GetState() == AS_ROUND || arena.GetState() == AS_WARMUP || arena.GetState() == AS_SUDDEN_DEATH;
bool remainingIsWinner = (remainingPlayer == arena.GetWinner());
int remainingIndex = (remainingPlayer == arena.GetPlayers()[ 0 ]) ? 0 : 1;
arena.RemovePlayer( player );
// both players have disconnected from this arena, or the winner has disconnected, just return
if( (!arena.GetPlayers()[ 0 ] && !arena.GetPlayers()[ 1 ]) || (!arenaInProgress && remainingPlayer && !remainingIsWinner) ) {
arena.Clear();
tourneyHistory[ round - 1 ][ i ].playerOneNum = -1;
tourneyHistory[ round - 1 ][ i ].playerOne[ 0 ] = '\0';
tourneyHistory[ round - 1 ][ i ].playerTwoNum = -1;
tourneyHistory[ round - 1 ][ i ].playerTwo[ 0 ] = '\0';
return;
}
assert( remainingPlayer );
// if this arena is in progress, try restarting
// ATVI DevTrack #13196 - do not put the bye player into a game with the abandonned player anymore
// if ( arenaInProgress && byeArena >= 0 && arenas[ byeArena / round ].GetWinner() ) {
if ( 0 ) {
idPlayer* byePlayer = arenas[ byeArena / round ].GetWinner();
// reset history for this new round
tourneyHistory[ round - 1 ][ i ].playerOneNum = -1;
tourneyHistory[ round - 1 ][ i ].playerOne[ 0 ] = '\0';
tourneyHistory[ round - 1 ][ i ].playerTwoNum = -1;
tourneyHistory[ round - 1 ][ i ].playerTwo[ 0 ] = '\0';
arena.Clear();
arenas[ byeArena / round ].Clear();
arena.AddPlayers( remainingPlayer, byePlayer );
// place the players in the correct instance
remainingPlayer->JoinInstance( i );
byePlayer->JoinInstance( i );
remainingPlayer->ServerSpectate( false );
byePlayer->ServerSpectate( false );
gameLocal.mpGame.SetPlayerTeamScore( remainingPlayer, 0 );
gameLocal.mpGame.SetPlayerTeamScore( byePlayer, 0 );
gameLocal.Printf( "rvTourneyManager::RemovePlayer() - %s will face %s in arena %d\n", remainingPlayer->GetUserInfo()->GetString( "ui_name" ), byePlayer->GetUserInfo()->GetString( "ui_name" ), i );
byeArena = -1;
// this arena is ready to play
arena.Ready();
} else {
// if the arena was in progress and didn't get a bye player to step in, this becomes a bye
// arena - clear the tourney history
if( arenaInProgress ) {
tourneyHistory[ round - 1 ][ i ].playerOneNum = -1;
tourneyHistory[ round - 1 ][ i ].playerOne[ 0 ] = '\0';
tourneyHistory[ round - 1 ][ i ].playerOneScore = -1;
tourneyHistory[ round - 1 ][ i ].playerTwoNum = -1;
tourneyHistory[ round - 1 ][ i ].playerTwo[ 0 ] = '\0';
tourneyHistory[ round - 1 ][ i ].playerTwoScore = -1;
} else {
// since the player is disconnecting, write the history for this round
if( remainingIndex == 0 ) {
tourneyHistory[ round - 1 ][ i ].playerOneNum = remainingPlayer->entityNumber;
tourneyHistory[ round - 1 ][ i ].playerOne[ 0 ] = '\0';
tourneyHistory[ round - 1 ][ i ].playerOneScore = gameLocal.mpGame.GetTeamScore( remainingPlayer );
} else {
tourneyHistory[ round - 1 ][ i ].playerTwoNum = remainingPlayer->entityNumber;
tourneyHistory[ round - 1 ][ i ].playerTwo[ 0 ] = '\0';
tourneyHistory[ round - 1 ][ i ].playerTwoScore = gameLocal.mpGame.GetTeamScore( remainingPlayer );
}
}
}
return;
}
}
}
int rvTourneyGameState::GetNextActiveArena( int arena ) {
assert( IsType( rvTourneyGameState::GetClassType() ) );
for( int i = arena + 1; i < MAX_ARENAS; i++ ) {
if( arenas[ i ].GetState() != AS_INACTIVE && arenas[ i ].GetState() != AS_DONE ) {
return i;
}
}
for( int i = 0; i < arena; i++ ) {
if( arenas[ i ].GetState() != AS_INACTIVE && arenas[ i ].GetState() != AS_DONE ) {
return i;
}
}
return arena;
}
int rvTourneyGameState::GetPrevActiveArena( int arena ) {
assert( IsType( rvTourneyGameState::GetClassType() ) );
for( int i = arena - 1; i >= 0; i-- ) {
if( arenas[ i ].GetState() != AS_INACTIVE && arenas[ i ].GetState() != AS_DONE ) {
return i;
}
}
for( int i = MAX_ARENAS - 1; i > arena; i-- ) {
if( arenas[ i ].GetState() != AS_INACTIVE && arenas[ i ].GetState() != AS_DONE ) {
return i;
}
}
return arena;
}
void rvTourneyGameState::SpectateCycleNext( idPlayer* player ) {
assert( IsType( rvTourneyGameState::GetClassType() ) );
assert( gameLocal.isServer || player->IsFakeClient() );
rvTourneyArena& spectatingArena = arenas[ player->GetArena() ];
idPlayer** players = spectatingArena.GetPlayers();
if( !players[ 0 ] || !players[ 1 ] || players[ 0 ]->spectating || players[ 1 ]->spectating ) {
// setting the spectated client to ourselves will unlock us
player->spectator = player->entityNumber;
return;
}
if( player->spectator != players[ 0 ]->entityNumber && player->spectator != players[ 1 ]->entityNumber ) {
player->spectator = players[ 0 ]->entityNumber;
} else if( player->spectator == players[ 0 ]->entityNumber ) {
player->spectator = players[ 1 ]->entityNumber;
} else if( player->spectator == players[ 1 ]->entityNumber ) {
if( gameLocal.time > player->lastArenaChange ) {
if ( GetNumArenas() <= 0 ) {
player->JoinInstance( 0 );
} else {
player->JoinInstance( GetNextActiveArena( player->GetArena() ) );
}
player->lastArenaChange = gameLocal.time + 2000;
player->spectator = player->entityNumber;
}
}
// this is where the listen server updates it's gui spectating elements
if ( gameLocal.GetLocalPlayer() == player ) {
rvTourneyArena& arena = arenas[ player->GetArena() ];
if( arena.GetPlayers()[ 0 ] == NULL || arena.GetPlayers()[ 1 ] == NULL || (player->spectating && player->spectator != arena.GetPlayers()[ 0 ]->entityNumber && player->spectator != arena.GetPlayers()[ 1 ]->entityNumber) ) {
gameLocal.mpGame.tourneyGUI.ArenaSelect( player->GetArena(), TGH_BRACKET );
} else if( arena.GetPlayers()[ 0 ] == player || player->spectator == arena.GetPlayers()[ 0 ]->entityNumber ) {
gameLocal.mpGame.tourneyGUI.ArenaSelect( player->GetArena(), TGH_PLAYER_ONE );
} else if( arena.GetPlayers()[ 1 ] == player || player->spectator == arena.GetPlayers()[ 1 ]->entityNumber ) {
gameLocal.mpGame.tourneyGUI.ArenaSelect( player->GetArena(), TGH_PLAYER_TWO );
}
gameLocal.mpGame.tourneyGUI.UpdateScores();
}
}
void rvTourneyGameState::SpectateCyclePrev( idPlayer* player ) {
assert( IsType( rvTourneyGameState::GetClassType() ) );
assert( gameLocal.isServer || player->IsFakeClient() );
rvTourneyArena& spectatingArena = arenas[ player->GetArena() ];
idPlayer** players = spectatingArena.GetPlayers();
if( !players[ 0 ] || !players[ 1 ] || players[ 0 ]->spectating || players[ 1 ]->spectating ) {
// setting the spectated client to ourselves will unlock us
player->spectator = player->entityNumber;
return;
}
if( player->spectator != players[ 0 ]->entityNumber && player->spectator != players[ 1 ]->entityNumber ) {
if( gameLocal.time > player->lastArenaChange ) {
if ( GetNumArenas() <= 0 ) {
player->JoinInstance( 0 );
} else {
player->JoinInstance( GetPrevActiveArena( player->GetArena() ) );
}
player->lastArenaChange = gameLocal.time + 2000;
rvTourneyArena& newSpectatingArena = arenas[ player->GetArena() ];
idPlayer** newPlayers = newSpectatingArena.GetPlayers();
if( !newPlayers[ 0 ] || !newPlayers[ 1 ] || newPlayers[ 0 ]->spectating || newPlayers[ 1 ]->spectating ) {
// setting the spectated client to ourselves will unlock us
player->spectator = player->entityNumber;
return;
}
player->spectator = newPlayers[ 1 ]->entityNumber;
}
} else if( player->spectator == players[ 0 ]->entityNumber ) {
player->spectator = player->entityNumber;
} else if( player->spectator == players[ 1 ]->entityNumber ) {
player->spectator = players[ 0 ]->entityNumber;
}
// this is where the listen server updates it gui spectating elements
if( gameLocal.GetLocalPlayer() == player ) {
rvTourneyArena& arena = arenas[ player->GetArena() ];
if( arena.GetPlayers()[ 0 ] == NULL || arena.GetPlayers()[ 1 ] == NULL || (player->spectating && player->spectator != arena.GetPlayers()[ 0 ]->entityNumber && player->spectator != arena.GetPlayers()[ 1 ]->entityNumber) ) {
gameLocal.mpGame.tourneyGUI.ArenaSelect( player->GetArena(), TGH_BRACKET );
} else if( arena.GetPlayers()[ 0 ] == player || player->spectator == arena.GetPlayers()[ 0 ]->entityNumber ) {
gameLocal.mpGame.tourneyGUI.ArenaSelect( player->GetArena(), TGH_PLAYER_ONE );
} else if( arena.GetPlayers()[ 1 ] == player || player->spectator == arena.GetPlayers()[ 1 ]->entityNumber ) {
gameLocal.mpGame.tourneyGUI.ArenaSelect( player->GetArena(), TGH_PLAYER_TWO );
}
gameLocal.mpGame.tourneyGUI.UpdateScores();
}
}
void rvTourneyGameState::UpdateTourneyBrackets( void ) {
assert( IsType( rvTourneyGameState::GetClassType() ) );
gameLocal.mpGame.tourneyGUI.SetupTourneyHistory( startingRound, round - 1, tourneyHistory );
}
void rvTourneyGameState::UpdateTourneyHistory( int round ) {
assert( IsType( rvTourneyGameState::GetClassType() ) );
if ( round >= MAX_ROUNDS ) {
assert( false );
gameLocal.Error( "rvTourneyGameState::UpdateTourneyHistory: MAX_ROUNDS exceeded" );
return;
}
for( int i = 0; i < MAX_ARENAS; i++ ) {
// sometimes tourney history might have been updated for the current
// round, so don't clobber any data
if( (tourneyHistory[ round ][ i ].playerOne[ 0 ] != '\0' || tourneyHistory[ round ][ i ].playerOneNum != -1) ||
(tourneyHistory[ round ][ i ].playerTwo[ 0 ] != '\0' || tourneyHistory[ round ][ i ].playerTwoNum != -1) ) {
continue;
}
tourneyHistory[ round ][ i ].playerOne[ 0 ] = '\0';
tourneyHistory[ round ][ i ].playerOneNum = -1;
tourneyHistory[ round ][ i ].playerOneScore = 0;
tourneyHistory[ round ][ i ].playerTwo[ 0 ] = '\0';
tourneyHistory[ round ][ i ].playerTwoNum = -1;
tourneyHistory[ round ][ i ].playerTwoScore = 0;
if( arenas[ i ].GetState() == AS_DONE ) {
idPlayer* playerOne = arenas[ i ].GetPlayers()[ 0 ];
idPlayer* playerTwo = arenas[ i ].GetPlayers()[ 1 ];
if( playerOne ) {
tourneyHistory[ round ][ i ].playerOneNum = playerOne->entityNumber;
tourneyHistory[ round ][ i ].playerOne[ MAX_TOURNEY_HISTORY_NAME_LEN - 1 ] = '\0';
tourneyHistory[ round ][ i ].playerOneScore = gameLocal.mpGame.GetTeamScore( playerOne );
}
if( playerTwo ) {
tourneyHistory[ round ][ i ].playerTwoNum = playerTwo->entityNumber;
tourneyHistory[ round ][ i ].playerTwo[ MAX_TOURNEY_HISTORY_NAME_LEN - 1 ] = '\0';
tourneyHistory[ round ][ i ].playerTwoScore = gameLocal.mpGame.GetTeamScore( playerTwo );
}
}
}
}
/*
================
rvTourneyGameState::FirstAvailableArena
Returns the first non-WARMUP arena available for use in the next round
================
*/
int rvTourneyGameState::FirstAvailableArena( void ) {
assert( IsType( rvTourneyGameState::GetClassType() ) );
for( int i = 0; i < MAX_ARENAS; i++ ) {
if( arenas[ i ].GetState() != AS_WARMUP ) {
return i;
}
}
// no available arenas
assert( 0 );
return 0;
}
arenaOutcome_t* rvTourneyGameState::GetArenaOutcome( int arena ) {
assert( IsType( rvTourneyGameState::GetClassType() ) );
if( arenas[ arena ].GetState() != AS_DONE || (arenas[ arena ].GetPlayers()[ 0 ] && arenas[ arena ].GetPlayers()[ 1 ]) ) {
return NULL;
}
return &(tourneyHistory[ round - 1 ][ arena ]);
}
bool rvTourneyGameState::HasBye( idPlayer* player ) {
assert( IsType( rvTourneyGameState::GetClassType() ) );
if( player == NULL || player->GetArena() < 0 || player->GetArena() >= MAX_ARENAS ) {
return false;
}
// if we're one of the players in the arena we're in, and the other player is NULL, we have a bye
if( (arenas[ player->GetArena() ].GetPlayers()[ 0 ] == player || arenas[ player->GetArena() ].GetPlayers()[ 1 ] == player) &&
(arenas[ player->GetArena() ].GetPlayers()[ 0 ] == NULL || arenas[ player->GetArena() ].GetPlayers()[ 1 ] == NULL) ) {
return true;
}
return false;
}
// simple RTTI
gameStateType_t rvGameState::type = GS_BASE;
gameStateType_t rvDMGameState::type = GS_DM;
gameStateType_t rvTeamDMGameState::type = GS_TEAMDM;
gameStateType_t rvCTFGameState::type = GS_CTF;
gameStateType_t rvTourneyGameState::type = GS_TOURNEY;
bool rvGameState::IsType( gameStateType_t type ) const {
return ( type == rvGameState::type );
}
bool rvDMGameState::IsType( gameStateType_t type ) const {
return ( type == rvDMGameState::type );
}
bool rvTeamDMGameState::IsType( gameStateType_t type ) const {
return ( type == rvTeamDMGameState::type );
}
bool rvCTFGameState::IsType( gameStateType_t type ) const {
return ( type == rvCTFGameState::type );
}
bool rvTourneyGameState::IsType( gameStateType_t type ) const {
return ( type == rvTourneyGameState::type );
}
gameStateType_t rvGameState::GetClassType( void ) {
return rvGameState::type;
}
gameStateType_t rvDMGameState::GetClassType( void ) {
return rvDMGameState::type;
}
gameStateType_t rvTeamDMGameState::GetClassType( void ) {
return rvTeamDMGameState::type;
}
gameStateType_t rvCTFGameState::GetClassType( void ) {
return rvCTFGameState::type;
}
gameStateType_t rvTourneyGameState::GetClassType( void ) {
return rvTourneyGameState::type;
}
/*
===============================================================================
riDZGameState
Game state info for Dead Zone
===============================================================================
*/
/*
================
riDZGameState::riDZGameState
================
*/
riDZGameState::riDZGameState( bool allocPrevious ) : rvGameState( false ) {
Clear();
if( allocPrevious ) {
previousGameState = new riDZGameState( false );
} else {
previousGameState = NULL;
}
trackPrevious = allocPrevious;
type = GS_DZ;
}
/*
================
riDZGameState::~riDZGameState
================
*/
riDZGameState::~riDZGameState( void ) {
Clear();
delete previousGameState;
previousGameState = NULL;
}
/*
================
riDZGameState::Clear
================
*/
void riDZGameState::Clear( void ) {
rvGameState::Clear();
if ( previousGameState ) {
riDZGameState* prevState = (riDZGameState*)previousGameState;
prevState->Clear( );
}
for( int i = 0; i < TEAM_MAX; i++ ) {
dzStatus[ i ].state = DZ_NONE;
dzStatus[ i ].clientNum = -1;
}
dzTriggerEnt = -1;
}
/*
================
riDZGameState::SendState
================
*/
void riDZGameState::SendState( const idMessageSender &sender, int clientNum ) {
idBitMsg outMsg;
byte msgBuf[MAX_GAME_MESSAGE_SIZE];
assert( (gameLocal.isServer || gameLocal.isRepeater) && trackPrevious && type == GS_DZ );
if( clientNum == -1 && (riDZGameState&)(*this) == (riDZGameState&)(*previousGameState) ) {
return;
}
outMsg.Init( msgBuf, sizeof( msgBuf ) );
outMsg.WriteByte( GAME_RELIABLE_MESSAGE_GAMESTATE );
WriteState( outMsg );
sender.Send( outMsg );
// don't update the state if we are working for a single client
if ( clientNum == -1 ) {
outMsg.ReadByte(); // pop off the msg ID
ReceiveState( outMsg );
}
}
/*
===============
riDZGameState::WriteState
===============
*/
void riDZGameState::WriteState( idBitMsg &msg ) {
// send off base info
rvGameState::PackState( msg );
// add DZ info
PackState( msg );
}
/*
================
riDZGameState::ReceiveState
================
*/
void riDZGameState::ReceiveState( const idBitMsg& msg ) {
assert( type == GS_DZ );
if ( !rvGameState::BaseUnpackState( msg ) ) {
return;
}
UnpackState( msg );
if ( gameLocal.localClientNum >= 0 ) {
GameStateChanged();
}
(riDZGameState&)(*previousGameState) = (riDZGameState&)(*this);
}
/*
================
riDZGameState::PackState
================
*/
void riDZGameState::PackState( idBitMsg& outMsg ) {
int i;
for( i = 0; i < TEAM_MAX; i++ ) {
outMsg.WriteByte( dzStatus[ i ].state );
outMsg.WriteBits( dzStatus[ i ].clientNum, -( idMath::BitsForInteger( MAX_CLIENTS ) + 1 ) );
}
outMsg.WriteLong(dzTriggerEnt);
outMsg.WriteLong(dzShaderParm);
}
/*
================
riDZGameState::UnpackState
================
*/
void riDZGameState::UnpackState( const idBitMsg& inMsg ) {
int i;
for( i = 0; i < TEAM_MAX; i++ ) {
dzStatus[ i ].state = (dzState_t)inMsg.ReadByte();
dzStatus[ i ].clientNum = inMsg.ReadBits( -( idMath::BitsForInteger( MAX_CLIENTS ) + 1 ) );
}
dzTriggerEnt = inMsg.ReadLong();
dzShaderParm = inMsg.ReadLong();
}
/*
================
riDZGameState::SendInitialState
================
*/
void riDZGameState::SendInitialState( const idMessageSender &sender, int clientNum ) {
assert( type == GS_DZ );
riDZGameState* previousState = (riDZGameState*)previousGameState;
riDZGameState invalidState;
previousGameState = &invalidState;
SendState( sender, clientNum );
previousGameState = previousState;
}
/*
================
riDZGameState::ControlZoneStateChanged
================
*/
void riDZGameState::ControlZoneStateChanged( int team ) {
if( !gameLocal.isClient && !gameLocal.isListenServer ) {
return;
}
idPlayer* player = gameLocal.GetLocalPlayer();
if( player == NULL ) {
return;
}
if ( dzTriggerEnt < 0 )
return;
idEntity* ent = gameLocal.FindEntity(dzTriggerEnt);
if ( ent ) {
ent->SetShaderParm(7, dzShaderParm);
}
dzTriggerEnt = -1;
}
/*
================
riDZGameState::GameStateChanged
================
*/
void riDZGameState::GameStateChanged( void ) {
// detect any base state changes
rvGameState::GameStateChanged();
// DZ specific stuff
idPlayer* player = gameLocal.GetLocalPlayer();
if( player == NULL ) {
//gameLocal.Error( "riDZGameState::GameStateChanged() - NULL local player\n" );
return;
}
if( currentState == GAMEREVIEW )
{
// Need to clear the deadzone state at this point.
((riDZGameState*)previousGameState)->Clear();
for( int i = 0; i < TEAM_MAX; i++ ) {
dzStatus[ i ].state = DZ_NONE;
dzStatus[ i ].clientNum = -1;
}
}
for( int i = 0; i < TEAM_MAX; i++ ) {
if( dzStatus[ i ] == ((riDZGameState*)previousGameState)->dzStatus[ i ] ) {
continue;
}
ControlZoneStateChanged(i);
}
}
/*
================
riDZGameState::Run
================
*/
void riDZGameState::Run( void ) {
// run common stuff
rvGameState::Run();
switch( currentState ) {
case GAMEON:
{
/// Check if we're frozen (in buy mode, etc.)
if( gameLocal.GetIsFrozen() )
{
/// Check if it's time for freeze to wear off
int unFreezeTime = gameLocal.GetUnFreezeTime();
if( gameLocal.time >= unFreezeTime )
{
gameLocal.SetIsFrozen( false );
// FIXME: say "fight"
}
}
else
{
/// Check if time limit has been reached
if ( gameLocal.mpGame.TimeLimitHit() )
{
int marineTeamScore = gameLocal.mpGame.GetScoreForTeam( TEAM_MARINE );
int stroggTeamScore = gameLocal.mpGame.GetScoreForTeam( TEAM_STROGG );
gameLocal.mpGame.PrintMessageEvent( -1, MSG_TIMELIMIT );
if( marineTeamScore > stroggTeamScore )
{
/// Marines win!
gameLocal.mpGame.OnDeadZoneTeamVictory( TEAM_MARINE );
}
else if( marineTeamScore < stroggTeamScore )
{
/// Strogg win!
gameLocal.mpGame.OnDeadZoneTeamVictory( TEAM_STROGG );
}
else
{
/// Teams are tied and time limit was hit - go to sudden death!
fragLimitTimeout = 0;
NewState( SUDDENDEATH );
}
}
}
break;
}
case SUDDENDEATH:
{
int marineTeamScore = gameLocal.mpGame.GetScoreForTeam( TEAM_MARINE );
int stroggTeamScore = gameLocal.mpGame.GetScoreForTeam( TEAM_STROGG );
if( marineTeamScore > stroggTeamScore )
{
/// Marines win!
gameLocal.mpGame.OnDeadZoneTeamVictory( TEAM_MARINE );
}
else if( marineTeamScore < stroggTeamScore )
{
/// Strogg win!
gameLocal.mpGame.OnDeadZoneTeamVictory( TEAM_STROGG );
}
break;
}
}
}
/*
================
riDZGameState::SetDZState
================
*/
void riDZGameState::SetDZState( int dz, dzState_t newState ) {
assert( gameLocal.isServer && ( dz >= 0 && dz < TEAM_MAX ) && type == GS_DZ );
dzStatus[ dz ].state = newState;
}
/*
================
riDZGameState::operator==
================
*/
bool riDZGameState::operator==( const riDZGameState& rhs ) const {
if( (rvGameState&)(*this) != (rvGameState&)rhs ) {
return false;
}
for( int i = 0; i < TEAM_MAX; i++ ) {
if( dzStatus[ i ] != rhs.dzStatus[ i ] ) {
return false;
}
}
return true;
}
/*
================
riDZGameState::operator=
================
*/
riDZGameState& riDZGameState::operator=( const riDZGameState& rhs ) {
(rvGameState&)(*this) = (rvGameState&)rhs;
for( int i = 0; i < TEAM_MAX; i++ ) {
dzStatus[ i ] = rhs.dzStatus[ i ];
}
return (*this);
}