//---------------------------------------------------------------- // 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(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( 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; iInit(); 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( 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(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(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( 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; iIsType( idPlayer::GetClassType() ) ) { // RAVEN END continue; } if ( static_cast< idPlayer *>( ent )->IsLeader() ) { static_cast(ent)->ServerSpectate( false ); continue; } static_cast< idPlayer *>( ent )->forcedReady = false; static_cast(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 > 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( 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 > 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( arenas[ i ].GetWinner(), i ) ); nextRoundPlayers.Append( rvPair( 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 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); }