// Copyright (C) 2007 Id Software, Inc. // #include "../precompiled.h" #pragma hdrstop #if defined( _DEBUG ) && !defined( ID_REDIRECT_NEWDELETE ) #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif #include "GameRules.h" #include "GameRules_Campaign.h" #include "../structures/TeamManager.h" #include "../Player.h" #include "../roles/FireTeams.h" #include "../script/Script_Helper.h" #include "../script/Script_ScriptObject.h" #include "../guis/UserInterfaceLocal.h" #include "../guis/UserInterfaceTypes.h" #include "../guis/UIWindow.h" #include "../guis/UIList.h" #include "../guis/UserInterfaceManager.h" #include "../proficiency/StatsTracker.h" #include "../botai/BotThreadData.h" #include "../botai/Bot.h" //mal: needed for the bots. #include "../Waypoints/LocationMarker.h" #include "../demos/DemoManager.h" #include "VoteManager.h" #include "AdminSystem.h" idCVar sdGameRules::g_chatDefaultColor( "g_chatDefaultColor", "1 1 1 1", CVAR_GAME | CVAR_NOCHEAT | CVAR_PROFILE, "RGBA value for normal chat prints" ); idCVar sdGameRules::g_chatTeamColor( "g_chatTeamColor", "1 1 0 1", CVAR_GAME | CVAR_NOCHEAT | CVAR_PROFILE, "RGBA value for team chat prints" ); idCVar sdGameRules::g_chatFireTeamColor( "g_chatFireTeamColor", "0.8 0.8 0.8 1", CVAR_GAME | CVAR_NOCHEAT | CVAR_PROFILE, "RGBA value for fire team chat prints" ); idCVar sdGameRules::g_chatLineTimeout( "g_chatLineTimeout", "5", CVAR_GAME | CVAR_FLOAT | CVAR_NOCHEAT | CVAR_PROFILE, "number of seconds that each chat line stays in the history" ); /* ================ sdGameRulesNetworkState::MakeDefault ================ */ void sdGameRulesNetworkState::MakeDefault( void ) { for ( int i = 0; i < MAX_CLIENTS; i++ ) { pings[ i ] = 0; teams[ i ] = NULL; userGroups[ i ] = -1; } state = 0; matchStartedTime = 0; nextStateSwitch = 0; } /* ================ sdGameRulesNetworkState::Write ================ */ void sdGameRulesNetworkState::Write( idFile* file ) const { for ( int i = 0; i < MAX_CLIENTS; i++ ) { file->WriteInt( pings[ i ] ); file->WriteInt( teams[ i ] ? teams[ i ]->GetIndex() : -1 ); file->WriteInt( userGroups[ i ] ); } file->WriteInt( state ); file->WriteInt( matchStartedTime ); file->WriteInt( nextStateSwitch ); } /* ================ sdGameRulesNetworkState::Read ================ */ void sdGameRulesNetworkState::Read( idFile* file ) { for ( int i = 0; i < MAX_CLIENTS; i++ ) { file->ReadInt( pings[ i ] ); int teamIndex; file->ReadInt( teamIndex ); teams[ i ] = teamIndex == -1 ? NULL : &sdTeamManager::GetInstance().GetTeamByIndex( teamIndex ); file->ReadInt( userGroups[ i ] ); } file->ReadInt( state ); file->ReadInt( matchStartedTime ); file->ReadInt( nextStateSwitch ); } /* =============================================================================== sdGameRules =============================================================================== */ extern const idEventDef EV_SendNetworkEvent; const idEventDef EV_Rules_SetEndGameCamera( "setEndGameCamera", '\0', DOC_TEXT( "Sets the camera view to be used at the end of the game, before the stats screen is shown." ), 1, NULL, "E", "camera", "Entity to be used as a camera." ); const idEventDef EV_Rules_EndGame( "endGame", '\0', DOC_TEXT( "Finishes the current game in progress, and marks the current winning team as the winners." ), 0, "Use $event:setWinningTeam$ to set the current winning team." ); const idEventDef EV_Rules_SetWinningTeam( "setWinningTeam", '\0', DOC_TEXT( "Sets the current winning team." ), 1, "If the object passed in is not a valid team, it will be treated as $null$.", "o", "team", "Team to set." ); const idEventDef EV_Rules_GetKeySuffix( "getKeySuffix", 's', DOC_TEXT( "Gets the suffix for rules-dependant keys." ), 0, "Append to key names to get versions of keys for different game rulesets." ); ABSTRACT_DECLARATION( idClass, sdGameRules ) EVENT( EV_SendNetworkEvent, sdGameRules::Event_SendNetworkEvent ) EVENT( EV_Rules_SetEndGameCamera, sdGameRules::Event_SetEndGameCamera ) EVENT( EV_Rules_EndGame, sdGameRules::Event_EndGame ) EVENT( EV_Rules_SetWinningTeam, sdGameRules::Event_SetWinningTeam ) EVENT( EV_Rules_GetKeySuffix, sdGameRules::Event_GetKeySuffix ) END_CLASS /* ================ sdGameRules::sdGameRules ================ */ sdGameRules::sdGameRules( void ) { scriptObject = NULL; commandsAdded = false; for ( int i = 0; i < MAX_CLIENTS; i++ ) { playerState[ i ].fireTeam = NULL; playerState[ i ].userGroup = -1; playerState[ i ].muteStatus = 0; playerState[ i ].numWarnings = 0; playerState[ i ].backupTeam = NULL; playerState[ i ].backupFireTeamState.fireTeamIndex = -1; playerState[ i ].backupFireTeamState.fireTeamLeader = false; playerState[ i ].backupFireTeamState.fireTeamPublic = true; } Clear(); noMission = declHolder.declLocStrType.LocalFind( "guis/game/scoreboard/nomission" ); infinity = declHolder.declLocStrType.LocalFind( "guis/mainmenu/infinity" ); } /* ================ sdGameRules::~sdGameRules ================ */ sdGameRules::~sdGameRules( void ) { callVotes.DeleteContents( true ); if( commandsAdded ) { RemoveConsoleCommands(); uiManager->UnregisterListEnumerationCallback( "chatHistory" ); commandsAdded = false; } } /* ================ sdGameRules::SavePlayerStates ================ */ void sdGameRules::SavePlayerStates( playerStateList_t& states ) { for ( int i = 0; i < MAX_CLIENTS; i++ ) { states[ i ].backupClass = playerState[ i ].backupClass; states[ i ].backupTeam = playerState[ i ].backupTeam; states[ i ].backupFireTeamState.fireTeamIndex = playerState[ i ].backupFireTeamState.fireTeamIndex; states[ i ].backupFireTeamState.fireTeamLeader = playerState[ i ].backupFireTeamState.fireTeamLeader; states[ i ].backupFireTeamState.fireTeamPublic = playerState[ i ].backupFireTeamState.fireTeamPublic; states[ i ].backupFireTeamState.fireTeamName = playerState[ i ].backupFireTeamState.fireTeamName; states[ i ].fireTeam = playerState[ i ].fireTeam; states[ i ].muteStatus = playerState[ i ].muteStatus; states[ i ].numWarnings = playerState[ i ].numWarnings; states[ i ].ping = playerState[ i ].ping; states[ i ].userGroup = playerState[ i ].userGroup; } } /* ================ sdGameRules::RestorePlayerStates ================ */ void sdGameRules::RestorePlayerStates( const playerStateList_t& states ) { for ( int i = 0; i < MAX_CLIENTS; i++ ) { playerState[ i ].backupClass = states[ i ].backupClass; playerState[ i ].backupTeam = states[ i ].backupTeam; playerState[ i ].backupFireTeamState.fireTeamIndex = states[ i ].backupFireTeamState.fireTeamIndex; playerState[ i ].backupFireTeamState.fireTeamLeader = states[ i ].backupFireTeamState.fireTeamLeader; playerState[ i ].backupFireTeamState.fireTeamPublic = states[ i ].backupFireTeamState.fireTeamPublic; playerState[ i ].backupFireTeamState.fireTeamName = states[ i ].backupFireTeamState.fireTeamName; playerState[ i ].fireTeam = states[ i ].fireTeam; playerState[ i ].muteStatus = states[ i ].muteStatus; playerState[ i ].numWarnings = states[ i ].numWarnings; playerState[ i ].ping = states[ i ].ping; playerState[ i ].userGroup = states[ i ].userGroup; } } /* ================ sdGameRules::InitConsoleCommands ================ */ void sdGameRules::InitConsoleCommands( void ) { if( !commandsAdded ) { cmdSystem->AddCommand( "clientMessageMode", sdGameRules::MessageMode_f, CMD_FL_GAME, "ingame gui message mode" ); cmdSystem->AddCommand( "clientQuickChat", sdGameRules::QuickChat_f, CMD_FL_GAME, "plays a quickchat declaration", idArgCompletionGameDecl_f< DECLTYPE_QUICKCHAT > ); cmdSystem->AddCommand( "clientTeam", sdGameRules::SetTeam_f, CMD_FL_GAME, "change your team" ); cmdSystem->AddCommand( "clientClass", sdGameRules::SetClass_f, CMD_FL_GAME, "change your class" ); cmdSystem->AddCommand( "clientDefaultSpawn", sdGameRules::DefaultSpawn_f,CMD_FL_GAME, "revert to default spawn" ); cmdSystem->AddCommand( "clientWantSpawn", sdGameRules::WantSpawn_f, CMD_FL_GAME, "tap out" ); uiManager->RegisterListEnumerationCallback( "chatHistory", CreateChatList ); // input: show expired, CHAT_MODE_ constant, number of items (-1 for all available) InitVotes(); } commandsAdded = true; } /* ================ sdGameRules::InitVotes ================ */ void sdGameRules::InitVotes( void ) { callVotes.Alloc() = new sdCallVoteMapReset(); } /* ================ sdGameRules::RemoveConsoleCommands ================ */ void sdGameRules::RemoveConsoleCommands( void ) { cmdSystem->RemoveCommand( "clientMessageMode" ); cmdSystem->RemoveCommand( "clientQuickChat" ); cmdSystem->RemoveCommand( "clientTeam" ); cmdSystem->RemoveCommand( "clientClass" ); cmdSystem->RemoveCommand( "clientDefaultSpawn" ); cmdSystem->RemoveCommand( "clientWantSpawn" ); } /* ================ sdGameRules::MessageMode_f ================ */ void sdGameRules::MessageMode_f( const idCmdArgs &args ) { gameLocal.rules->MessageMode( args ); } /* ============ sdGameRules::WantSpawn_f ============ */ void sdGameRules::WantSpawn_f( const idCmdArgs &args ) { if ( gameLocal.isClient ) { sdReliableClientMessage msg( GAME_RELIABLE_CMESSAGE_RESPAWN ); msg.Send(); } else { idPlayer* player = gameLocal.GetLocalPlayer(); if ( player ) { if ( !player->IsSpectator() && player->IsDead() ) { player->ServerForceRespawn( false ); } } } } /* ============ sdGameRules::DefaultSpawn_f ============ */ void sdGameRules::DefaultSpawn_f( const idCmdArgs &args ) { if ( gameLocal.isClient ) { sdReliableClientMessage msg( GAME_RELIABLE_CMESSAGE_DEFAULTSPAWN ); msg.Send(); } else { idPlayer* player = gameLocal.GetLocalPlayer(); if ( player ) { player->SetSpawnPoint( NULL ); } } } /* ============ sdGameRules::SetClass_f ============ */ void sdGameRules::SetClass_f( const idCmdArgs &args ) { const char* className = args.Argv( 1 ); const sdDeclPlayerClass* pc = gameLocal.declPlayerClassType[ className ]; if ( !pc ) { gameLocal.Warning( "sdGameRules::SetClass_f Invalid Class '%s'", className ); return; } int classOption = 0; if ( args.Argc() > 2 ) { const char* classOptionStr = args.Argv( 2 ); classOption = atoi( classOptionStr ); } if ( gameLocal.isClient ) { sdReliableClientMessage msg( GAME_RELIABLE_CMESSAGE_CLASSSWITCH ); msg.WriteLong( pc->Index() ); msg.WriteLong( classOption ); msg.Send(); } else { idPlayer* player = gameLocal.GetLocalPlayer(); if ( player != NULL ) { player->ChangeClass( pc, classOption ); } } } /* ============ sdGameRules::MuteStatus ============ */ int sdGameRules::MuteStatus( idPlayer* player ) const { return playerState[ player->entityNumber ].muteStatus; } /* ============ sdGameRules::Mute ============ */ void sdGameRules::Mute( idPlayer* player, int flags ) { playerState[ player->entityNumber ].muteStatus |= flags; } /* ============ sdGameRules::UnMute ============ */ void sdGameRules::UnMute( idPlayer* player, int flags ) { playerState[ player->entityNumber ].muteStatus &= ~flags; } /* ============ sdGameRules::Warn ============ */ void sdGameRules::Warn( idPlayer* player ) { playerState[ player->entityNumber ].numWarnings++; int maxWarnings = g_maxPlayerWarnings.GetInteger(); if ( maxWarnings > 0 ) { if ( playerState[ player->entityNumber ].numWarnings >= maxWarnings ) { networkSystem->ServerKickClient( player->entityNumber, "engine/disconnect/toomanywarnings", true ); return; } int left = maxWarnings - playerState[ player->entityNumber ].numWarnings; idWStrList parms; parms.Append( va( L"%d", left ) ); player->SendLocalisedMessage( declHolder.declLocStrType[ "rules/messages/warning/xleft" ], parms ); return; } player->SendLocalisedMessage( declHolder.declLocStrType[ "rules/messages/warning" ], idWStrList() ); } /* ============ sdGameRules::TeamIndexForName ============ */ int sdGameRules::TeamIndexForName( const char* teamname ) { int index = -1; if ( !idStr::Icmp( teamname, "s" ) || !idStr::Icmp( teamname, "spec" ) || !idStr::Icmp( teamname, "spectator" ) ) { index = 0; } else { sdTeamManagerLocal& manager = sdTeamManager::GetInstance(); for ( int i = 0; i < manager.GetNumTeams(); i++ ) { if ( !idStr::Icmp( teamname, manager.GetTeamByIndex( i ).GetLookupName() ) ) { index = i + 1; } } } return index; } /* ============ sdGameRules::SetTeam_f ============ */ void sdGameRules::SetTeam_f( const idCmdArgs &args ) { if ( args.Argc() < 1 ) { return; } const char* teamName = args.Argv( 1 ); int index; if ( !idStr::Icmp( teamName, "auto" ) ) { // set to the end of the array index = sdTeamManager::GetInstance().GetNumTeams() + 1; } else { index = TeamIndexForName( teamName ); } if ( index == -1 ) { gameLocal.Warning( "Invalid Team '%s'", teamName ); return; } if ( gameLocal.isClient ) { sdReliableClientMessage msg( GAME_RELIABLE_CMESSAGE_TEAMSWITCH ); msg.WriteLong( index ); if ( index == 0 ) { msg.WriteString( "" ); } else if ( index == sdTeamManager::GetInstance().GetNumTeams() + 1 ) { sdTeamInfo& team1 = sdTeamManager::GetInstance().GetTeamByIndex( 0 ); idCVar* passwordCVar = team1.GetPasswordCVar(); msg.WriteString( passwordCVar ? passwordCVar->GetString() : "" ); sdTeamInfo& team2 = sdTeamManager::GetInstance().GetTeamByIndex( 1 ); passwordCVar = team2.GetPasswordCVar(); msg.WriteString( passwordCVar ? passwordCVar->GetString() : "" ); } else { sdTeamInfo& teamInfo = sdTeamManager::GetInstance().GetTeamByIndex( index - 1 ); idCVar* passwordCVar = teamInfo.GetPasswordCVar(); msg.WriteString( passwordCVar ? passwordCVar->GetString() : "" ); } msg.Send(); } else { idPlayer* player = gameLocal.GetLocalPlayer(); if ( player ) { if ( index == sdTeamManager::GetInstance().GetNumTeams() + 1 ) { // auto join team with the least number of players int lowest = -1; int clientTeamIndex = player->GetTeam() != NULL ? player->GetTeam()->GetIndex() : -1; for ( int i = 0; i < sdTeamManager::GetInstance().GetNumTeams(); i++ ) { int num = sdTeamManager::GetInstance().GetTeamByIndex( i ).GetNumPlayers(); if ( clientTeamIndex == i ) { num--; } if ( num < lowest || lowest == -1 ) { lowest = num; index = i + 1; } } } gameLocal.rules->SetClientTeam( player, index, false, "" ); } } } /* ============ sdGameRules::SetClientTeam ============ */ void sdGameRules::SetClientTeam( idPlayer* player, int index, bool force, const char* password ) { idPlayer* botPlayer = NULL; if ( index == 0 ) { if ( si_spectators.GetBool() ) { player->SetWantSpectate( true ); } } else { index--; sdTeamManagerLocal& manager = sdTeamManager::GetInstance(); int numTeams = manager.GetNumTeams(); if ( index >= 0 && index < numTeams ) { sdTeamInfo* team = &manager.GetTeamByIndex( index ); sdTeamInfo* currentTeam = player->GetTeam(); if ( team == currentTeam ) { return; } int currentTeamIndex = currentTeam == NULL ? -1 : currentTeam->GetIndex(); if ( !force ) { const sdDeclLocStr* reason; if ( !team->CanJoin( player, password, reason ) ) { player->SendLocalisedMessage( reason, idWStrList() ); return; } if ( si_teamForceBalance.GetBool() ) { int* teamCounts = ( int* )_alloca( numTeams * sizeof( int ) ); int low = -1; for ( int i = 0; i < numTeams; i++ ) { teamCounts[ i ] = manager.GetTeamByIndex( i ).GetNumPlayers(); if ( i == currentTeamIndex ) { teamCounts[ i ]--; } if ( low == -1 || teamCounts[ i ] < low ) { low = teamCounts[ i ]; } } if ( teamCounts[ index ] != low ) { // Gordon: Try to find a bot for the client to replace for ( int i = 0; i < MAX_CLIENTS; i++ ) { idPlayer* other = gameLocal.GetClient( i ); if ( other == NULL ) { continue; } if ( other->GetGameTeam() != team ) { continue; } if ( !other->userInfo.isBot ) { continue; } botPlayer = other; break; } if ( botPlayer == NULL ) { player->SendLocalisedMessage( declHolder.declLocStrType[ "teams/messages/toomanyplayers" ], idWStrList() ); return; } } } if ( GetState() == GS_GAMEON ) { if ( !si_allowLateJoin.GetBool() ) { player->SendLocalisedMessage( declHolder.declLocStrType[ "teams/messages/nojoin" ], idWStrList() ); return; } } if ( player->GetNextTeamSwitchTime() > gameLocal.time ) { player->SendLocalisedMessage( declHolder.declLocStrType[ "teams/messages/switchwait" ], idWStrList() ); return; } } player->Kill( NULL ); player->SetGameTeam( team ); if ( currentTeam != NULL && team != NULL && player->GetInventory().GetClass() != NULL ) { const sdDeclPlayerClass* remappedClass = currentTeam->GetEquivalentClass( *player->GetInventory().GetClass(), *team ); if( remappedClass != NULL ) { player->GetInventory().SetCachedClass( remappedClass ); } else { gameLocal.Warning( "sdGameRules::SetClientTeam: could not find equivalent class for '%s' on team '%s'", player->GetInventory().GetClass()->GetName(), team->GetLookupName() ); } } player->SetWantSpectate( false ); player->ServerForceRespawn( false ); if ( botPlayer != NULL ) { sdTeamInfo* newBotTeam = FindNeedyTeam( botPlayer ); botPlayer->Kill( NULL ); botPlayer->SetGameTeam( newBotTeam ); botPlayer->SetWantSpectate( false ); botPlayer->ServerForceRespawn( false ); } } } } /* ============ sdGameRules::QuickChat_f ============ */ void sdGameRules::QuickChat_f( const idCmdArgs &args ) { if ( args.Argc() < 2 || args.Argc() > 3 ) { gameLocal.Printf( "Usage: clientQuickChat [quickchatname] [target spawnID]\n" ); return; } idPlayer* localPlayer = gameLocal.GetLocalPlayer(); if ( localPlayer == NULL ) { return; } const char* quickChatName = args.Argv( 1 ); const sdDeclQuickChat* quickChat; if ( localPlayer == NULL || localPlayer->GetInventory().GetClass() == NULL ) { quickChat = gameLocal.declQuickChatType.LocalFind( quickChatName, false ); } else { quickChatName = localPlayer->GetInventory().GetClass()->BuildQuickChatDeclName( args.Argv( 1 ) ); quickChat = gameLocal.declQuickChatType.LocalFind( quickChatName, false ); } if ( quickChat == NULL ) { gameLocal.Warning( "Could not find quickchat: %s (%s)", quickChatName, args.Argv( 1 ) ); return; } int spawnId = -1; if ( args.Argc() == 3 && idStr::Icmp( args.Argv( 2 ), "invalid" ) != 0 ) { spawnId = sdTypeFromString< int >( args.Argv( 2 ) ); } localPlayer->RequestQuickChat( quickChat, spawnId ); } /* ============ sdGameRules::EnterGame ============ */ void sdGameRules::EnterGame( idPlayer* player ) { player->SetInGame( true ); } /* ================ sdGameRules::NewMap ================ */ void sdGameRules::NewMap( bool isUserChange ) { if ( isUserChange ) { Reset(); } pingUpdateTime = 0; } /* ================ sdGameRules::Shutdown ================ */ void sdGameRules::Shutdown( void ) { Clear(); } /* ================ sdGameRules::Reset ================ */ void sdGameRules::Reset( void ) { Clear(); sdProficiencyManager::GetInstance().ClearProficiency(); } /* ================ sdGameRules::BackupPlayerTeams ================ */ void sdGameRules::BackupPlayerTeams( void ) { for ( int i = 0; i < MAX_CLIENTS; i++ ) { idPlayer* player = gameLocal.GetClient( i ); if ( !player ) { continue; } playerState[ i ].backupTeam = player->GetGameTeam(); playerState[ i ].backupClass = *player->GetInventory().GetClassSetup(); playerState[ i ].backupFireTeamState.fireTeamIndex = playerState[ i ].fireTeam != NULL ? playerState[ i ].backupTeam->GetFireTeamIndex( playerState[ i ].fireTeam ) : -1; playerState[ i ].backupFireTeamState.fireTeamName.Clear(); if ( playerState[ i ].backupFireTeamState.fireTeamIndex != -1 ) { playerState[ i ].backupFireTeamState.fireTeamLeader = playerState[ i ].fireTeam->GetCommander() == player ? true : false; playerState[ i ].backupFireTeamState.fireTeamPublic = playerState[ i ].fireTeam->IsPrivate() == false ? true : false; playerState[ i ].backupFireTeamState.fireTeamName = playerState[ i ].fireTeam->GetName(); } } } /* ================ sdGameRules::RestoreFireTeam ================ */ void sdGameRules::RestoreFireTeam( idPlayer* player ) { if ( player == NULL ) { return; } playerState_t& state = playerState[ player->entityNumber ]; sdTeamInfo* team = player->GetGameTeam(); if ( state.backupFireTeamState.fireTeamIndex != -1 && team != NULL ) { if ( !gameLocal.isClient ) { sdFireTeam& fireTeam = team->GetFireTeam( state.backupFireTeamState.fireTeamIndex ); fireTeam.AddMember( player->entityNumber ); if ( state.backupFireTeamState.fireTeamLeader && fireTeam.GetNumMembers() > 1 ) { fireTeam.PromoteMember( player->entityNumber ); } if ( state.backupFireTeamState.fireTeamName.Length() > 0 ) { fireTeam.SetName( state.backupFireTeamState.fireTeamName.c_str() ); } fireTeam.SetPrivate( !state.backupFireTeamState.fireTeamPublic ); } } state.backupFireTeamState.fireTeamName.Clear(); state.backupFireTeamState.fireTeamLeader = false; state.backupFireTeamState.fireTeamIndex = -1; } /* ================ sdGameRules::SpawnPlayer ================ */ void sdGameRules::SpawnPlayer( idPlayer* player ) { playerState_t& state = playerState[ player->entityNumber ]; state.ping = 0; if ( state.backupTeam != NULL ) { player->SetGameTeam( state.backupTeam ); if ( state.backupClass.GetClass() != NULL ) { const idList< int >& options = state.backupClass.GetOptions(); int classOption = options.Num() > 0 ? options[ 0 ] : 0; player->ChangeClass( state.backupClass.GetClass(), classOption ); } RestoreFireTeam( player ); state.backupTeam = NULL; } if ( GetState() == GS_GAMEON ) { player->SetGameStartTime( gameLocal.time ); } OnConnect( player ); } /* ================ sdGameRules::Clear ================ */ void sdGameRules::Clear( void ) { gameState = GS_INACTIVE; nextState = GS_INACTIVE; pingUpdateTime = 0; nextStateSwitch = 0; matchStartedTime = 0; pureReady = false; ClearChatData(); } //idCVar gui_scoreBoardSort( "gui_scoreBoardSort", "0", CVAR_ARCHIVE | CVAR_PROFILE | CVAR_INTEGER, "0 - group by XP, 1 - group by fireteam, then by XP" ); /* ================ sdGameRules::UpdateScoreboard ================ */ void sdGameRules::UpdateScoreboard( sdUIList* list, const char* teamName ) { sdUIList::ClearItems( list ); idPlayer* localPlayer = gameLocal.GetLocalPlayer(); sdTeamManagerLocal& manager = sdTeamManager::GetInstance(); int index = TeamIndexForName( teamName ); // spectators if( index == 0 ) { list->GetUI()->PushScriptVar( 0.0f ); list->GetUI()->PushScriptVar( 0.0f ); list->GetUI()->PushScriptVar( 0.0f ); return; } sdTeamInfo& teamInfo = manager.GetTeamByIndex( index - 1 ); sdTeamInfo* localTeam = localPlayer == NULL ? NULL : localPlayer->GetTeam(); bool showClass = localTeam == NULL ? true : *localTeam == teamInfo; idStaticList< idPlayer*, MAX_CLIENTS > players; teamInfo.SortPlayers( players, true ); float averageXP = 0.0f; float averagePing = 0.0f; int numSpectators = 0; if( g_debugPlayerList.GetInteger() ) { int i; idRandom random; random.SetSeed( gameLocal.time ); for( i = 0; i < g_debugPlayerList.GetInteger() - players.Num(); i++ ) { int index = sdUIList::InsertItem( list, L"", 0, 0 ); sdUIList::SetItemIcon( list, "soldier", index, 0 ); sdUIList::SetItemForeColor( list, colorWhite, index, 0 ); sdUIList::SetItemIcon( list, "guis/assets/icons/rank01", index, 1 ); sdUIList::SetItemForeColor( list, colorWhite, index, 1 ); sdUIList::SetItemText( list, L"", index, 2 ); sdUIList::SetItemText( list, va( L"Player %i", i ), index, 3 ); sdUIList::SetItemText( list, va( L"No mission", i ), index, 4 ); sdUIList::SetItemText( list, va( L"%i", 100 ), index, 5 ); averageXP += 100; int ping = random.RandomInt( 1000 ); sdUIList::SetItemText( list, va( L"%i", ping ), index, 6 ); averagePing += ping; sdUIList::SetItemText( list, va( L"%i", i ), index, 7 ); } } const wchar_t* noMissionText = L""; const sdPlayerTask::nodeType_t& objectiveTasks = sdTaskManager::GetInstance().GetObjectiveTasks( &teamInfo ); sdPlayerTask* objectiveTask = objectiveTasks.Next(); int fireTeamColorIndices[ 8 ] = { 1, 3, 8, 15, 17, 18, 28, 31 }; int i; int numInGamePlayers = 0; idStaticList< int, MAX_CLIENTS > fireTeamColors; fireTeamColors.SetNum( MAX_CLIENTS ); //bool sortByFireTeams = gui_scoreBoardSort.GetInteger() == 1; bool sortByFireTeams = false; int count = sdFireTeamManager::GetInstance().NumFireTeams(); for( i = 0; i < count; i++ ) { const sdFireTeam* fireTeam = sdFireTeamManager::GetInstance().FireTeamForIndex( i ); int leaderIndex = 0; idVec4 color = idStr::ColorForIndex( i ); // insert the leader int pi; for( pi = 0; pi < players.Num(); pi++ ) { idPlayer* player = players[ pi ]; const playerState_t& ps = playerState[ player->entityNumber ]; if ( objectiveTask != NULL ) { noMissionText = objectiveTask->GetTitle( player ); } else { noMissionText = noMission->GetText(); } if( gameLocal.rules->GetPlayerFireTeam( player->entityNumber ) == fireTeam && fireTeam->GetCommander() == player ) { if( sortByFireTeams ) { leaderIndex = InsertScoreboardPlayer( list, player, fireTeam, ps, noMissionText, showClass, averagePing, averageXP, numInGamePlayers, leaderIndex ); list->SetItemDataInt( fireTeamColorIndices[ i ], leaderIndex, 0, true ); // used for deriving background color leaderIndex++; } else { fireTeamColors[ pi ] = fireTeamColorIndices[ i ]; } break; } } for( pi = 0; pi < players.Num(); pi++ ) { idPlayer* player = players[ pi ]; const playerState_t& ps = playerState[ player->entityNumber ]; if ( objectiveTask != NULL ) { noMissionText = objectiveTask->GetTitle( player ); } else { noMissionText = noMission->GetText(); } if( gameLocal.rules->GetPlayerFireTeam( player->entityNumber ) == fireTeam && fireTeam->GetCommander() != player ) { if( sortByFireTeams ) { leaderIndex = InsertScoreboardPlayer( list, player, fireTeam, ps, noMissionText, showClass, averagePing, averageXP, numInGamePlayers, leaderIndex ); list->SetItemDataInt( fireTeamColorIndices[ i ], leaderIndex, 0, true ); // used for deriving background color } else { fireTeamColors[ pi ] = fireTeamColorIndices[ i ]; } } } } for ( i = 0; i < players.Num(); i++ ) { idPlayer* player = players[ i ]; const playerState_t& ps = playerState[ player->entityNumber ]; sdFireTeam* ft = gameLocal.rules->GetPlayerFireTeam( player->entityNumber ); if( sortByFireTeams && ft != NULL ) { continue; } if ( objectiveTask != NULL ) { noMissionText = objectiveTask->GetTitle( player ); } else { noMissionText = noMission->GetText(); } int index = InsertScoreboardPlayer( list, player, ft, ps, noMissionText, showClass, averagePing, averageXP, numInGamePlayers, -1 ); //if( !sortByFireTeams ) { // list->SetItemDataInt( fireTeamColors[ i ], index, 0, true ); // used for deriving background color //} } if( numInGamePlayers > 0 ) { averagePing /= numInGamePlayers; averageXP /= numInGamePlayers; } list->GetUI()->PushScriptVar( averageXP ); list->GetUI()->PushScriptVar( averagePing ); list->GetUI()->PushScriptVar( numInGamePlayers ); } /* ============ sdGameRules::InsertScoreboardPlayer ============ */ int sdGameRules::InsertScoreboardPlayer( sdUIList* list, const idPlayer* player, const sdFireTeam* fireTeam, const playerState_t& ps, const wchar_t* noMissionText, bool showClass, float& averagePing, float& averageXP, int& numInGamePlayers, int index ) { static idVec4 noDraw( 0, 0, 0, 0 ); index = sdUIList::InsertItem( list, L"", index, 0 ); bool spectating = player->IsSpectator(); // class if( showClass ) { const sdDeclPlayerClass* pc = player->GetInventory().GetClass() ? player->GetInventory().GetClass() : player->GetInventory().GetCachedClass(); if( pc != NULL ) { sdUIList::SetItemIcon( list, pc->GetName(), index, 0 ); sdUIList::SetItemForeColor( list, colorWhite, index, 0 ); } } // rank/voip if ( player->IsSendingVoice() ) { sdUIList::SetItemIcon( list, "voip", index, 1 ); sdUIList::SetItemForeColor( list, colorWhite, index, 1 ); } else if ( spectating ) { sdUIList::SetItemIcon( list, "spectating", index, 1 ); sdUIList::SetItemForeColor( list, colorWhite, index, 1 ); } else if ( const sdDeclRank* rank = player->GetProficiencyTable().GetRank() ) { bool isBot = player->IsType( idBot::Type ); bool shouldCheckReady = !isBot && ( ( gameLocal.rules->IsEndGame() && gameLocal.serverInfoData.gameReviewReadyWait ) || ( gameLocal.rules->IsWarmup() && !gameLocal.serverInfoData.adminStart && !gameLocal.rules->IsCountDown() ) ); if( shouldCheckReady && !player->IsReady() ) { sdUIList::SetItemIcon( list, "notready", index, 1 ); sdUIList::SetItemForeColor( list, colorWhite, index, 1 ); } else { if( player->IsCarryingObjective() ) { sdUIList::SetItemIcon( list, "documents", index, 1 ); sdUIList::SetItemForeColor( list, colorWhite, index, 1 ); } else { if( isBot ) { sdUIList::SetItemIcon( list, "bot", index, 1 ); sdUIList::SetItemForeColor( list, colorWhite, index, 1 ); } else { sdUIList::SetItemIcon( list, rank->GetMaterial(), index, 1 ); sdUIList::SetItemForeColor( list, colorWhite, index, 1 ); } } } } else { bool isBot = player->IsType( idBot::Type ); if( isBot ) { sdUIList::SetItemIcon( list, "bot", index, 1 ); sdUIList::SetItemForeColor( list, colorWhite, index, 1 ); } else { sdUIList::SetItemForeColor( list, noDraw, index, 1 ); } } // fireteam if( fireTeam != NULL ) { sdUIList::SetItemText( list, va( L"%hs", fireTeam->GetName() ), index, 2 ); } // player name idWStr cleanPlayerName = va( L"%hs", player->GetUserInfo().name.c_str() ); sdUIList::CleanUserInput( cleanPlayerName ); sdUIList::SetItemText( list, cleanPlayerName.c_str(), index, 3 ); // current task if( !spectating && ( fireTeam == NULL || fireTeam->GetCommander() == player ) ) { sdPlayerTask* task = NULL; if( sdFireTeam* fireTeam = gameLocal.rules->GetPlayerFireTeam( player->entityNumber ) ) { if( idPlayer* commander = fireTeam->GetCommander() ) { task = commander->GetActiveTask(); } } else { task = player->GetActiveTask(); } if( task != NULL ) { sdUIList::SetItemText( list, task->GetTitle(), index, 4 ); } else { sdUIList::SetItemText( list, noMissionText, index, 4 ); } } if( !spectating ) { sdUIList::SetItemText( list, va( L"%i", idMath::Ftoi( player->GetProficiencyTable().GetXP() ) ), index, 5 ); } sdUIList::SetItemText( list, va( L"%i", ps.ping ), index, 6 ); // entity number list->SetItemDataInt( player->entityNumber, index, 7 ); if( !spectating ) { numInGamePlayers++; averagePing += ps.ping; averageXP += player->GetProficiencyTable().GetXP(); } if( player == gameLocal.GetLocalPlayer() ) { list->SelectItem( index ); } return index; } /* ================ sdGameRules::NumActualClients ================ */ int sdGameRules::NumActualClients( bool countSpectators, bool countBots ) { int c = 0; for( int i = 0 ; i < gameLocal.numClients ; i++ ) { idPlayer* p = gameLocal.GetClient( i ); if ( !p ) { continue; } if ( !countBots && p->userInfo.isBot ) { continue; } if ( countSpectators || p->CanPlay() ) { c++; } } return c; } /* ================ sdGameRules::NewState ================ */ void sdGameRules::NewState( gameState_t news ) { assert( news != gameState ); switch( news ) { case GS_GAMEON: { sdGlobalStatsTracker::GetInstance().Clear(); gameLocal.StartAutorecording(); for ( int i = 0; i < MAX_CLIENTS; i++ ) { // Gordon: For local play, grab the latest local stats for life stats baseline if ( gameLocal.isServer && gameLocal.GetLocalPlayer() != NULL ) { gameLocal.clientRanks[ i ].calculated = false; gameLocal.clientStatsRequestsPending = true; } idPlayer* player = gameLocal.GetClient( i ); if ( player == NULL ) { continue; } player->SetGameStartTime( gameLocal.time ); } OnGameState_GameOn(); break; } case GS_GAMEREVIEW: { for ( int i = 0; i < MAX_CLIENTS; i++ ) { idPlayer* player = gameLocal.GetClient( i ); if ( player == NULL ) { continue; } if ( player->GetHealth() > 0 ) { player->RegisterTimeAlive(); } player->CalcLifeStats(); } if ( gameLocal.GetLocalPlayer() != NULL ) { sdGlobalStatsTracker::GetInstance().StartStatsRequest(); } if ( gameState == GS_GAMEON ) { // we've just finished the match for ( int i = 0; i < MAX_CLIENTS; i++ ) { idPlayer* player = gameLocal.GetClient( i ); if ( player == NULL ) { continue; } player->LogTimePlayed(); player->WriteStats(); } #if !defined( SD_DEMO_BUILD ) gameLocal.GetSDNet().FlushStats( false ); #endif /* !SD_DEMO_BUILD */ sdProficiencyManager::GetInstance().DumpProficiencyData(); } for ( int i = 0; i < MAX_CLIENTS; i++ ) { idPlayer* player = gameLocal.GetClient( i ); if ( player == NULL ) { continue; } idEntity* proxy = player->GetProxyEntity(); if ( proxy != NULL ) { proxy->GetUsableInterface()->OnExit( player, true ); } player->SetRemoteCamera( NULL, false ); } OnGameState_Review(); break; } case GS_NEXTMAP: { OnGameState_NextMap(); break; } case GS_WARMUP: { gameLocal.ClearEndGameStats(); OnGameState_Warmup(); if( gameLocal.DoClientSideStuff() ) { SetWarmupStatusMessage(); } break; } case GS_COUNTDOWN: { gameLocal.ClearEndGameStats(); gameLocal.StartAutorecording(); OnGameState_Countdown(); if( gameLocal.DoClientSideStuff() ) { statusText = common->LocalizeText( "game/warmup" ); } break; } } lastStateSwitchTime = gameLocal.time; gameState = news; if( gameLocal.DoClientSideStuff() ) { UpdateClientFromServerInfo( gameLocal.serverInfo, false ); } sdObjectiveManager::GetInstance().OnGameStateChange( gameState ); } /* ================ sdGameRules::OnLocalMapRestart ================ */ void sdGameRules::OnLocalMapRestart( void ) { if ( gameState == GS_GAMEREVIEW || gameState == GS_GAMEON || gameState == GS_NEXTMAP ) { // only stop autorecording if the restart is for the end of the round gameLocal.StopAutorecording(); } } /* ================ sdGameRules::NextStateDelayed ================ */ void sdGameRules::NextStateDelayed( gameState_t state, int delay ) { nextState = state; nextStateSwitch = gameLocal.time + delay; } /* ============ sdGameRules::SetWarmupStatusMessage ============ */ void sdGameRules::SetWarmupStatusMessage() { if( gameLocal.serverInfoData.adminStart ) { statusText = common->LocalizeText( "game/waitingforadmin" ); } else { int numPlayers; readyState_e ready = ArePlayersReady( false, true, &numPlayers ); if ( ready == RS_NOT_ENOUGH_CLIENTS ) { statusText = common->LocalizeText( "game/waitingforplayers" ); } else if( ready == RS_NOT_ENOUGH_READY ) { statusText = common->LocalizeText( "game/waitingforreadyplayers" ); } else { statusText = common->LocalizeText( "game/warmup" ); } } } /* ================ sdGameRules::Run ================ */ void sdGameRules::Run( void ) { UpdateChatLines(); pureReady = true; if( gameLocal.isClient ) { return; } if ( gameState == GS_INACTIVE ) { NewState( GS_WARMUP ); } if ( gameLocal.time > gameLocal.playerSpawnTime && !gameLocal.IsPaused() ) { CheckRespawns(); } if ( nextState != GS_INACTIVE && gameLocal.time > nextStateSwitch ) { NewState( nextState ); nextState = GS_INACTIVE; } // don't update the ping every frame to save bandwidth if ( gameLocal.time > pingUpdateTime ) { for ( int i = 0; i < MAX_CLIENTS; i++ ) { idPlayer* player = gameLocal.GetClient( i ); if ( player == NULL ) { playerState[ i ].ping = 0; } else if ( player->GetIsLagged() ) { playerState[ i ].ping = 999; } else { playerState[ i ].ping = networkSystem->ServerGetClientPing( i ); } } pingUpdateTime = gameLocal.time + 1000; } switch( gameState ) { case GS_GAMEREVIEW: { GameState_Review(); break; } case GS_NEXTGAME: { GameState_NextGame(); break; } case GS_WARMUP: { GameState_Warmup(); break; } case GS_COUNTDOWN: { GameState_Countdown(); break; } case GS_GAMEON: { GameState_GameOn(); break; } case GS_NEXTMAP: { GameState_NextMap(); break; } } } /* =============== sdGameRules::ClearChatData =============== */ void sdGameRules::ClearChatData() { chatLines.SetNum( MAX_CHAT_LINES ); for( int i = 0; i < chatLines.Num(); i++ ) { chatLines[ i ].GetNode().AddToEnd( chatFree ); } } /* =============== sdGameRules::L. =============== */ void sdGameRules::AddChatLine( chatMode_t chatMode, const idVec4& color, const wchar_t *fmt, ... ) { idWStr temp; va_list argptr; va_start( argptr, fmt ); vswprintf( temp, fmt, argptr ); va_end( argptr ); gameLocal.Printf( "%ls\n", temp.c_str() ); if ( gameLocal.DoClientSideStuff() ) { sdChatLine::node_t* node = chatFree.NextNode(); sdChatLine* line = NULL; if( node != NULL ) { line = node->Owner(); } else { int oldestIndex = 0; for( int i = 1; i < chatLines.Num(); i++ ) { if( chatLines[ i ].GetTime() < chatLines[ oldestIndex ].GetTime() ) { oldestIndex = i; } } assert( oldestIndex >= 0 && oldestIndex < chatLines.Num() ); line = &chatLines[ oldestIndex ]; } if( line != NULL ) { line->GetNode().AddToEnd( chatHead ); line->Set( temp.c_str(), chatMode ); } } } idCVar g_showChatLocation( "g_showChatLocation", "1", CVAR_BOOL | CVAR_GAME, "show/hide locations in chat text" ); /* =============== sdGameRules::AddRepeaterChatLine =============== */ void sdGameRules::AddRepeaterChatLine( const char* clientName, const int clientNum, const wchar_t *text ) { idVec4 color; sdProperties::sdFromString( color, g_chatDefaultColor.GetString() ); AddChatLine( CHAT_MODE_MESSAGE, color, L"%hs^0(%d): %ls", clientName, clientNum, text ); } /* =============== sdGameRules::AddChatLine =============== */ void sdGameRules::AddChatLine( const idVec3& origin, chatMode_t chatMode, const int clientNum, const wchar_t *text ) { const wchar_t* wideName = NULL; const char* name = NULL; if ( clientNum < 0 ) { name = "Server"; if ( clientNum == -1 ) { // Gordon: make this "high command" for your team idPlayer* player = gameLocal.GetLocalPlayer(); if ( player != NULL ) { sdTeamInfo* team = player->GetGameTeam(); if ( team != NULL ) { name = NULL; wideName = team->GetHighCommandName(); } } } } else { idPlayer* player = gameLocal.GetClient( clientNum ); if ( player ) { name = player->userInfo.name.c_str(); player->FlashPlayerIcon(); } else { name = "player"; } } idVec4 color; if ( chatMode == CHAT_MODE_SAY_TEAM || chatMode == CHAT_MODE_QUICK_TEAM ) { sdProperties::sdFromString( color, g_chatTeamColor.GetString() ); } else if ( chatMode == CHAT_MODE_SAY_FIRETEAM || chatMode == CHAT_MODE_QUICK_FIRETEAM ) { sdProperties::sdFromString( color, g_chatFireTeamColor.GetString() ); } else { sdProperties::sdFromString( color, g_chatDefaultColor.GetString() ); } if ( chatMode == CHAT_MODE_MESSAGE || chatMode == CHAT_MODE_OBITUARY ) { AddChatLine( chatMode, color, L"%ls", text ); } else { const wchar_t* locationString = L""; if ( g_showChatLocation.GetBool() && clientNum >= 0 ) { idWStr locationName; switch ( chatMode ) { case CHAT_MODE_SAY_TEAM: case CHAT_MODE_SAY_FIRETEAM: case CHAT_MODE_QUICK_TEAM: case CHAT_MODE_QUICK_FIRETEAM: sdLocationMarker::GetLocationText( origin, locationName ); locationString = va( L", ^L%ls^0", locationName.c_str() ); break; } } if ( name != NULL ) { AddChatLine( chatMode, color, L"%hs%ls: %ls", name, locationString, text ); } else { assert( wideName != NULL ); AddChatLine( chatMode, color, L"%ls%ls: %ls", wideName, locationString, text ); } } } const int ASYNC_PLAYER_PING_BITS = idMath::BitsForInteger( 999 ); /* ================ sdGameRules::FindNeedyTeam ================ */ sdTeamInfo* sdGameRules::FindNeedyTeam( idPlayer* ignore ) { sdTeamManagerLocal& manager = sdTeamManager::GetInstance(); sdTeamInfo* bestTeam = NULL; int bestCount = -1; int i, j; for ( i = 0; i < manager.GetNumTeams(); i++ ) { sdTeamInfo& team = manager.GetTeamByIndex( i ); int players = 0; for ( j = 0; j < gameLocal.numClients; j++ ) { idPlayer* player = gameLocal.GetClient( j ); if ( player == NULL || player == ignore ) { continue; } if ( player->GetGameTeam() == &team ) { players++; } } if ( !bestTeam || players < bestCount ) { bestTeam = &team; bestCount = players; } } return bestTeam; } /* ================ sdGameRules::CheckRespawns ================ */ void sdGameRules::CheckRespawns( bool force ) { for ( int i = 0 ; i < gameLocal.numClients ; i++ ) { idPlayer* player = gameLocal.GetClient( i ); if ( !player ) { continue; } if ( player->GetWantSpectate() ) { player->ServerSpectate(); if ( player->IsInLimbo() ) { player->SpawnFromSpawnSpot(); } continue; } if ( player->WantRespawn() || force ) { if ( gameState == GS_WARMUP || gameState == GS_COUNTDOWN || gameState == GS_GAMEON ) { if ( gameState == GS_GAMEON && !force ) { sdTeamInfo* team = player->GetTeam(); if ( team == NULL ) { continue; } if ( !team->AllowRespawn( player ) ) { continue; } } player->SpawnFromSpawnSpot(); } } } } /* ================ sdGameRules::MessageMode ================ */ void sdGameRules::MessageMode( const idCmdArgs &args ) { if ( sdDemoManager::GetInstance().InPlayBack() ) { return; } if ( networkSystem->IsDedicated() ) { gameLocal.Printf( "sdGameRules::MessageMode not valid without a local player\n" ); return; } int imode; const char* mode = args.Argv( 1 ); if ( !mode[ 0 ] ) { imode = 0; } else { imode = atoi( mode ); } sdQuickChatMenu* quickChatMenu = gameLocal.localPlayerProperties.GetQuickChatMenu(); if ( quickChatMenu->Enabled() ) { quickChatMenu->Enable( false ); } sdWeaponSelectionMenu* weaponMenu = gameLocal.localPlayerProperties.GetWeaponSelectionMenu(); if( weaponMenu->Enabled() ) { weaponMenu->Enable( false ); } sdQuickChatMenu* contextMenu = gameLocal.localPlayerProperties.GetContextMenu(); if( contextMenu->Enabled() ) { contextMenu->Enable( false ); } sdFireTeamMenu* fireTeamMenu = gameLocal.localPlayerProperties.GetFireTeamMenu(); if ( fireTeamMenu->Enabled() ) { fireTeamMenu->Enable( false ); } sdChatMenu* menu = gameLocal.localPlayerProperties.GetChatMenu(); menu->Enable( true, true ); if ( sdUserInterfaceLocal* chatUI = menu->GetGui() ) { if ( imode == 1 ) { chatUI->PostNamedEvent( "teamChat" ); } else if ( imode == 2 ) { chatUI->PostNamedEvent( "fireteamChat" ); } else { chatUI->PostNamedEvent( "globalChat" ); } } } /* ================ sdGameRules::DisconnectClient ================ */ void sdGameRules::DisconnectClient( int clientNum ) { gameLocal.GetProficiencyTable( clientNum ).Clear( true ); if ( playerState[ clientNum ].fireTeam != NULL ) { playerState[ clientNum ].fireTeam->RemoveMember( clientNum ); // this should set it to NULL assert( playerState[ clientNum ].fireTeam == NULL ); } playerState[ clientNum ].userGroup = -1; playerState[ clientNum ].muteStatus = 0; playerState[ clientNum ].numWarnings = 0; playerState[ clientNum ].backupTeam = NULL; playerState[ clientNum ].backupFireTeamState.fireTeamIndex = -1; playerState[ clientNum ].backupFireTeamState.fireTeamLeader = false; playerState[ clientNum ].backupFireTeamState.fireTeamPublic = true; playerState[ clientNum ].backupFireTeamState.fireTeamName.Clear(); } /* ================ sdGameRules::WantKilled ================ */ void sdGameRules::WantKilled( idPlayer* player ) { player->Suicide(); } /* ================ sdGameRules::MapRestart ================ */ void sdGameRules::MapRestart( void ) { assert( !gameLocal.isClient ); nextState = GS_INACTIVE; if ( gameState != GS_WARMUP ) { ClearPlayerReadyFlags(); NewState( GS_WARMUP ); } } /* ============ sdGameRules::OnPlayerReady ============ */ void sdGameRules::OnPlayerReady( idPlayer* player, bool ready ) { if( gameLocal.DoClientSideStuff() ) { if( gameState == GS_WARMUP ) { SetWarmupStatusMessage(); } else if( gameState == GS_GAMEON ) { statusText = common->LocalizeText( "guis/hud/in_progress" ); } } } /* ================ sdGameRules::OnTeamChange ================ */ idCVar g_autoFireTeam( "g_autoFireTeam", "0", CVAR_GAME | CVAR_BOOL | CVAR_NOCHEAT, "Prompt to join a fireteam when switching to a new team." ); void sdGameRules::OnTeamChange( idPlayer* player, sdTeamInfo* oldteam, sdTeamInfo* team ) { if ( oldteam == team ) { assert( false ); return; } if ( gameState == GS_WARMUP ) { if( gameLocal.DoClientSideStuff() ) { SetWarmupStatusMessage(); } } if ( !gameLocal.isClient ) { sdFireTeam* fireTeam = playerState[ player->entityNumber ].fireTeam; if ( fireTeam ) { fireTeam->RemoveMember( player->entityNumber ); } sdVoteManager::GetInstance().CancelFireTeamVotesForPlayer( player ); if ( g_autoFireTeam.GetBool() ) { if ( team != NULL ) { team->TryFindPrivateFireTeam( player ); } } if ( gameState == GS_GAMEON && oldteam != NULL ) { // when changing teams during game, kill and respawn player->Kill( NULL ); } } } /* ================ sdGameRules::ProcessChatMessage ================ */ void sdGameRules::ProcessChatMessage( idPlayer* player, gameReliableClientMessage_t mode, const wchar_t *text ) { assert( !gameLocal.isClient ); if ( player == NULL && mode != GAME_RELIABLE_CMESSAGE_CHAT ) { return; } if ( player != NULL ) { if ( playerState[ player->entityNumber ].muteStatus & MF_CHAT ) { const sdUserGroup& userGroup = sdUserGroupManager::GetInstance().GetGroup( player->GetUserGroup() ); if ( !userGroup.HasPermission( PF_NO_MUTE ) ) { player->SendLocalisedMessage( declHolder.declLocStrType[ "rules/messages/muted" ], idWStrList() ); return; } } if ( mode == GAME_RELIABLE_CMESSAGE_CHAT ) { if ( g_muteSpecs.GetBool() && player->GetGameTeam() == NULL ) { mode = GAME_RELIABLE_CMESSAGE_TEAM_CHAT; // Gordon: muted spectator global chat goes to team chat instead } else if ( si_disableGlobalChat.GetBool() ) { const sdUserGroup& userGroup = sdUserGroupManager::GetInstance().GetGroup( player->GetUserGroup() ); if ( !userGroup.HasPermission( PF_NO_MUTE ) ) { player->SendLocalisedMessage( declHolder.declLocStrType[ "rules/messages/globalchatdisabled" ], idWStrList() ); return; } } } } gameReliableServerMessage_t outMode; switch ( mode ) { default: case GAME_RELIABLE_CMESSAGE_CHAT: outMode = GAME_RELIABLE_SMESSAGE_CHAT; break; case GAME_RELIABLE_CMESSAGE_TEAM_CHAT: outMode = GAME_RELIABLE_SMESSAGE_TEAM_CHAT; break; case GAME_RELIABLE_CMESSAGE_FIRETEAM_CHAT: outMode = GAME_RELIABLE_SMESSAGE_FIRETEAM_CHAT; break; } idPlayer* localPlayer = gameLocal.GetLocalPlayer(); idVec3 location = player == NULL ? vec3_origin : player->GetPhysics()->GetOrigin(); int clientIndex = player ? player->entityNumber : -1; sdReliableServerMessage outMsg( outMode ); outMsg.WriteVector( location ); outMsg.WriteChar( clientIndex ); outMsg.WriteString( text ); switch ( mode ) { case GAME_RELIABLE_CMESSAGE_CHAT: outMsg.Send( sdReliableMessageClientInfoAll() ); AddChatLine( location, sdGameRules::CHAT_MODE_SAY, clientIndex, text ); break; case GAME_RELIABLE_CMESSAGE_TEAM_CHAT: { if ( player != NULL ) { for ( int i = 0; i < gameLocal.numClients; i++ ) { idPlayer* other = gameLocal.GetClient( i ); if ( other == NULL ) { continue; } if ( other->GetGameTeam() != player->GetGameTeam() ) { continue; } if ( localPlayer == other ) { AddChatLine( location, sdGameRules::CHAT_MODE_SAY_TEAM, clientIndex, text ); } else { outMsg.Send( sdReliableMessageClientInfo( i ) ); } } break; } } case GAME_RELIABLE_CMESSAGE_FIRETEAM_CHAT: { if ( player != NULL ) { sdFireTeam* fireTeam = GetPlayerFireTeam( player->entityNumber ); if ( fireTeam != NULL ) { for ( int i = 0; i < fireTeam->GetNumMembers(); i++ ) { idPlayer* other = fireTeam->GetMember( i ); if ( localPlayer == other ) { AddChatLine( location, sdGameRules::CHAT_MODE_SAY_FIRETEAM, clientIndex, text ); } else { outMsg.Send( sdReliableMessageClientInfo( other->entityNumber ) ); } } } else { taskHandle_t taskHandle = player->GetActiveTaskHandle(); if ( taskHandle.IsValid() ) { for ( int i = 0; i < MAX_CLIENTS; i++ ) { idPlayer* other = gameLocal.GetClient( i ); if ( other == NULL ) { continue; } if ( other->GetActiveTaskHandle() == taskHandle ) { if ( localPlayer == other ) { AddChatLine( location, sdGameRules::CHAT_MODE_SAY_FIRETEAM, clientIndex, text ); } else { outMsg.Send( sdReliableMessageClientInfo( other->entityNumber ) ); } } } } } } break; } } } /* ================ sdGameRules::ApplyNetworkState ================ */ void sdGameRules::ApplyNetworkState( const sdEntityStateNetworkData& newState ) { NET_GET_NEW( sdGameRulesNetworkState ); for ( int i = 0; i < MAX_CLIENTS; i++ ) { playerState[ i ].ping = newData.pings[ i ]; idPlayer* player = gameLocal.GetClient( i ); if ( !player ) { continue; } player->SetGameTeam( newData.teams[ i ] ); player->SetUserGroup( newData.userGroups[ i ] ); } matchStartedTime = newData.matchStartedTime; nextStateSwitch = newData.nextStateSwitch; gameState_t newGamaState = static_cast< gameState_t >( newData.state ); if ( newGamaState != gameState ) { // gameLocal.DPrintf( "%s -> %s\n", gameStateStrings[ gameState ], gameStateStrings[ newGamaState ] ); NewState( newGamaState ); } } /* ================ sdGameRules::ReadNetworkState ================ */ void sdGameRules::ReadNetworkState( const sdEntityStateNetworkData& baseState, sdEntityStateNetworkData& newState, const idBitMsg& msg ) const { NET_GET_STATES( sdGameRulesNetworkState ); sdTeamManagerLocal& teamManager = sdTeamManager::GetInstance(); bool teamsChanged = msg.ReadBool(); bool userGroupsChanged = msg.ReadBool(); for ( int i = 0; i < MAX_CLIENTS; i++ ) { newData.pings[ i ] = msg.ReadDeltaLong( baseData.pings[ i ] ); } if ( teamsChanged ) { for ( int i = 0; i < MAX_CLIENTS; i++ ) { newData.teams[ i ] = teamManager.ReadTeamFromStream( baseData.teams[ i ], msg ); } } else { for ( int i = 0; i < MAX_CLIENTS; i++ ) { newData.teams[ i ] = baseData.teams[ i ]; } } if ( userGroupsChanged ) { for ( int i = 0; i < MAX_CLIENTS; i++ ) { newData.userGroups[ i ] = msg.ReadDeltaLong( baseData.userGroups[ i ] ); } } else { for ( int i = 0; i < MAX_CLIENTS; i++ ) { newData.userGroups[ i ] = baseData.userGroups[ i ]; } } newData.state = msg.ReadDeltaLong( baseData.state ); newData.matchStartedTime = msg.ReadDeltaLong( baseData.matchStartedTime ); newData.nextStateSwitch = msg.ReadDeltaLong( baseData.nextStateSwitch ); } /* ================ sdGameRules::WriteNetworkState ================ */ void sdGameRules::WriteNetworkState( const sdEntityStateNetworkData& baseState, sdEntityStateNetworkData& newState, idBitMsg& msg ) const { NET_GET_STATES( sdGameRulesNetworkState ); bool teamsChanged = false; bool userGroupsChanged = false; for ( int i = 0; i < MAX_CLIENTS; i++ ) { newData.pings[ i ] = playerState[ i ].ping; newData.teams[ i ] = NULL; newData.userGroups[ i ] = playerState[ i ].userGroup; idPlayer* player = gameLocal.GetClient( i ); if ( player ) { newData.teams[ i ] = player->GetTeam(); } if ( newData.teams[ i ] != baseData.teams[ i ] ) { teamsChanged = true; } if ( newData.userGroups[ i ] != baseData.userGroups[ i ] ) { userGroupsChanged = true; } } newData.state = gameState; newData.matchStartedTime = matchStartedTime; newData.nextStateSwitch = nextStateSwitch; sdTeamManagerLocal& teamManager = sdTeamManager::GetInstance(); msg.WriteBool( teamsChanged ); msg.WriteBool( userGroupsChanged ); for ( int i = 0; i < MAX_CLIENTS; i++ ) { msg.WriteDeltaLong( baseData.pings[ i ], newData.pings[ i ] ); } if ( teamsChanged ) { for ( int i = 0; i < MAX_CLIENTS; i++ ) { teamManager.WriteTeamToStream( baseData.teams[ i ], newData.teams[ i ], msg ); } } if ( userGroupsChanged ) { for ( int i = 0; i < MAX_CLIENTS; i++ ) { msg.WriteDeltaLong( baseData.userGroups[ i ], newData.userGroups[ i ] ); } } msg.WriteDeltaLong( baseData.state, newData.state ); msg.WriteDeltaLong( baseData.matchStartedTime, newData.matchStartedTime ); msg.WriteDeltaLong( baseData.nextStateSwitch, newData.nextStateSwitch ); } /* ================ sdGameRules::CheckNetworkStateChanges ================ */ bool sdGameRules::CheckNetworkStateChanges( const sdEntityStateNetworkData& baseState ) const { NET_GET_BASE( sdGameRulesNetworkState ); for ( int i = 0; i < MAX_CLIENTS; i++ ) { if ( playerState[ i ].ping != baseData.pings[ i ] ) { return true; } if ( playerState[ i ].userGroup != baseData.userGroups[ i ] ) { return true; } sdTeamInfo* teamInfo = NULL; idPlayer* player = gameLocal.GetClient( i ); if ( player ) { teamInfo = player->GetTeam(); } if ( teamInfo != baseData.teams[ i ] ) { return true; } } NET_CHECK_FIELD( matchStartedTime, matchStartedTime ); NET_CHECK_FIELD( nextStateSwitch, nextStateSwitch ); if ( static_cast< gameState_t >( baseData.state ) != gameState ) { return true; } return false; } /* ================ sdGameRules::CreateNetworkStructure ================ */ sdEntityStateNetworkData* sdGameRules::CreateNetworkStructure( void ) const { return new sdGameRulesNetworkState(); } /* ================ sdGameRules::OnNewScriptLoad ================ */ void sdGameRules::OnNewScriptLoad( void ) { assert( !scriptObject ); scriptObject = gameLocal.program->AllocScriptObject( this, "rules" ); sdScriptHelper h1; scriptObject->CallNonBlockingScriptEvent( scriptObject->GetPreConstructor(), h1 ); } /* ================ sdGameRules::OnScriptChange ================ */ void sdGameRules::OnScriptChange( void ) { if ( scriptObject ) { sdScriptHelper h1; scriptObject->CallNonBlockingScriptEvent( scriptObject->GetDestructor(), h1 ); gameLocal.program->FreeScriptObject( scriptObject ); } } /* ================ sdGameRules::OnConnect ================ */ void sdGameRules::OnConnect( idPlayer* player ) { sdScriptHelper h1; h1.Push( player->GetScriptObject() ); scriptObject->CallNonBlockingScriptEvent( scriptObject->GetFunction( "OnConnect" ), h1 ); } /* ================ sdGameRules::OnNetworkEvent ================ */ void sdGameRules::OnNetworkEvent( const char* message ) { gameLocal.SetActionCommand( message ); sdScriptHelper h1; scriptObject->CallNonBlockingScriptEvent( scriptObject->GetFunction( "OnNetworkEvent" ), h1 ); } /* ================ sdGameRules::Event_SendNetworkEvent ================ */ void sdGameRules::Event_SendNetworkEvent( int clientIndex, bool isRepeaterClient, const char* message ) { if ( !isRepeaterClient ) { if ( clientIndex == -1 ) { if ( gameLocal.GetLocalPlayer() != NULL ) { OnNetworkEvent( message ); } } else { idPlayer* player = gameLocal.GetClient( clientIndex ); if ( player != NULL && gameLocal.IsLocalPlayer( player ) ) { OnNetworkEvent( message ); } } } sdReliableServerMessage msg( GAME_RELIABLE_SMESSAGE_NETWORKEVENT ); msg.WriteLong( NETWORKEVENT_RULES_ID ); msg.WriteString( message ); if ( isRepeaterClient ) { msg.Send( sdReliableMessageClientInfoRepeater( clientIndex ) ); } else { msg.Send( sdReliableMessageClientInfo( clientIndex ) ); } } /* ================ sdGameRules::Event_SetEndGameCamera ================ */ void sdGameRules::Event_SetEndGameCamera( idEntity* other ) { if ( gameLocal.isClient ) { return; } endGameCamera = other; if ( gameLocal.isServer ) { SendCameraEvent( other, sdReliableMessageClientInfoAll() ); } } /* ================ sdGameRules::Event_EndGame ================ */ void sdGameRules::Event_EndGame( void ) { EndGame(); } /* ================ sdGameRules::Event_SetWinningTeam ================ */ void sdGameRules::Event_SetWinningTeam( idScriptObject* object ) { SetWinner( object ? object->GetClass()->Cast< sdTeamInfo >() : NULL ); } /* ================ sdGameRules::Event_GetKeySuffix ================ */ void sdGameRules::Event_GetKeySuffix( void ) { sdProgram::ReturnString( GetKeySuffix() ); } /* ================ sdGameRules::ShuffleTeams ================ */ void sdGameRules::ShuffleTeams( shuffleMode_t sm ) { idList< shuffleData_t > players; for ( int i = 0; i < MAX_CLIENTS; i++ ) { idPlayer* player = gameLocal.GetClient( i ); if ( !player ) { continue; } sdTeamInfo* team = player->GetGameTeam(); if ( team == NULL ) { continue; } shuffleData_t& data = players.Alloc(); data.second = player; switch ( sm ) { case SM_RANDOM: data.first = gameLocal.random.RandomInt( 999999 ); break; case SM_XP: data.first = player->GetProficiencyTable().GetXP(); break; case SM_SWAP: data.first = 0; break; } } switch ( sm ) { case SM_RANDOM: players.Sort( SortPlayers_Random ); break; case SM_XP: players.Sort( SortPlayers_XP ); break; } sdTeamManagerLocal& teamManager = sdTeamManager::GetInstance(); int numTeams = teamManager.GetNumTeams(); if ( sm == SM_SWAP ) { for ( int i = 0; i < players.Num(); i++ ) { idPlayer* player = players[ i ].second; int index = player->GetGameTeam()->GetIndex() + 1; if ( index >= numTeams ) { index = 0; } SetClientTeam( player, index + 1, true, "" ); } } else { int cycle = 0; for ( int i = 0; i < players.Num(); i++ ) { idPlayer* player = players[ i ].second; SetClientTeam( player, cycle + 1, true, "" ); cycle++; if ( cycle >= numTeams ) { cycle = 0; } } } } /* ================ sdGameRules::SortPlayers_Random ================ */ int sdGameRules::SortPlayers_Random( const shuffleData_t* a, const shuffleData_t* b ) { return b->first - a->first; } /* ================ sdGameRules::SortPlayers_XP ================ */ int sdGameRules::SortPlayers_XP( const shuffleData_t* a, const shuffleData_t* b ) { return b->first - a->first; } /* ============ sdGameRules::UpdateChatLines ============ */ void sdGameRules::UpdateChatLines() { sdChatLine::node_t* node = chatHead.NextNode(); while( node != NULL ) { sdChatLine::node_t* next = node->NextNode(); sdChatLine* line = node->Owner(); line->CheckExpired(); node = next; } } /* ============ sdGameRules::CreateChatList ============ */ void sdGameRules::CreateChatList( sdUIList* list ) { sdUIList::ClearItems( list ); if( gameLocal.rules == NULL ) { return; } assert( list ); int mode = 0; int num = 0; int showExpired = 0; list->GetUI()->PopScriptVar( num ); list->GetUI()->PopScriptVar( mode ); list->GetUI()->PopScriptVar( showExpired ); if( num <= 0 ) { num = MAX_CHAT_LINES; } sdChatLine::node_t* node = gameLocal.rules->chatHead.PrevNode(); idWStr cleanedInput; int added = 0; int index = -1; while( node != NULL && added < num ) { const sdChatLine& line = *node->Owner(); node = node->PrevNode(); if( mode != CHAT_MODE_OBITUARY && line.IsObituary() ) { continue; } if( mode == CHAT_MODE_OBITUARY && !line.IsObituary() ) { continue; } if( !showExpired && line.IsExpired() ) { continue; } cleanedInput = line.GetText(); sdUIList::CleanUserInput( cleanedInput ); index = sdUIList::InsertItem( list, cleanedInput.c_str(), 0, 0 ); sdUIList::SetItemForeColor( list, line.GetColor(), index, -1 ); added++; } } /* ============ sdGameRules::ParseNetworkMessage ============ */ bool sdGameRules::ParseNetworkMessage( const idBitMsg& msg ) { int msgType = msg.ReadLong(); if ( msgType == EVENT_CREATE ) { int typeNum = msg.ReadLong(); idTypeInfo* type = idClass::GetType( typeNum ); assert( type ); gameLocal.SetRules( type ); return true; } assert( gameLocal.rules ); return gameLocal.rules->ParseNetworkMessage( msgType, msg ); } /* ============ sdGameRules::ParseNetworkMessage ============ */ bool sdGameRules::ParseNetworkMessage( int msgType, const idBitMsg& msg ) { switch ( msgType ) { case EVENT_SETCAMERA: endGameCamera.ForceSpawnId( msg.ReadLong() ); return true; } return false; } /* ============ sdGameRules::GetStatusText ============ */ const wchar_t* sdGameRules::GetStatusText() const { return statusText.c_str(); } /* ============ sdGameRules::SetPlayerFireTeam ============ */ void sdGameRules::SetPlayerFireTeam( int clientNum, sdFireTeam* fireTeam ) { playerState[ clientNum ].fireTeam = fireTeam; idPlayer* player = gameLocal.GetClient( clientNum ); if ( player ) { player->OnFireTeamJoined( fireTeam ); } } /* ================ sdGameRules::HandleGuiEvent ================ */ bool sdGameRules::HandleGuiEvent( const sdSysEvent* event ) { if ( !IsEndGame() ) { return false; } sdUserInterfaceLocal* scoreboardUI = gameLocal.GetUserInterface( gameLocal.localPlayerProperties.GetScoreBoard() ); if ( !scoreboardUI ) { return false; } return scoreboardUI->PostEvent( event ); } /* ================ sdGameRules::TranslateGuiBind ================ */ bool sdGameRules::TranslateGuiBind( const idKey& key, sdKeyCommand** cmd ) { if ( !IsEndGame() ) { return false; } sdUserInterfaceLocal* scoreboardUI = gameLocal.GetUserInterface( gameLocal.localPlayerProperties.GetScoreBoard() ); if ( scoreboardUI == NULL ) { return false; } if ( scoreboardUI->Translate( key, cmd ) ) { return true; } return false; } /* ============ sdGameRules::sdChatLine::Set ============ */ void sdGameRules::sdChatLine::Set( const wchar_t* text, chatMode_t mode ) { this->time = gameLocal.ToGuiTime( gameLocal.time ); CheckExpired(); if( flags.expired ) { return; } this->text = text; flags.obituary = ( mode == CHAT_MODE_OBITUARY ); flags.team = ( mode == CHAT_MODE_QUICK_TEAM ) || ( mode == CHAT_MODE_SAY_TEAM ) || ( mode == CHAT_MODE_SAY_FIRETEAM ) || ( mode == CHAT_MODE_QUICK_FIRETEAM ); if ( mode == CHAT_MODE_SAY_TEAM || mode == CHAT_MODE_QUICK_TEAM ) { sdProperties::sdFromString( color, g_chatTeamColor.GetString() ); } else if ( mode == CHAT_MODE_SAY_FIRETEAM || mode == CHAT_MODE_QUICK_FIRETEAM ) { sdProperties::sdFromString( color, g_chatFireTeamColor.GetString() ); } else { sdProperties::sdFromString( color, g_chatDefaultColor.GetString() ); } } /* ============ sdGameRules::ArgCompletion_RuleTypes ============ */ void sdGameRules::ArgCompletion_RuleTypes( const idCmdArgs &args, void( *callback )( const char *s ) ) { for ( int i = 0; i < idClass::GetNumTypes(); i++ ) { idTypeInfo* type = idClass::GetType( i ); if ( !sdGameRules::IsRuleType( *type ) ) { continue; } callback( va( "%s %s", args.Argv( 0 ), type->classname ) ); } } /* ============ sdGameRules::GetWarmupTime ============ */ int sdGameRules::GetWarmupTime( void ) { return Max( 0, MINS2MS( g_warmup.GetFloat() ) ); } /* ============ sdGameRules::GetTimeLimit ============ */ int sdGameRules::GetTimeLimit( void ) const { return gameLocal.serverInfoData.timeLimit; } /* ============ sdGameRules::OnGameState_Warmup ============ */ void sdGameRules::OnGameState_Warmup( void ) { adminStarted = false; needsRestart = false; autoReadyStartTime = -1; for ( int i = 0; i < MAX_CLIENTS; i++ ) { idPlayer* player = gameLocal.GetClient( i ); if ( player == NULL ) { continue; } player->SetSpawnPoint( NULL ); } } /* ============ sdGameRules::NumReady ============ */ int sdGameRules::NumReady( int& total ) { total = 0; int ready = 0; for ( int i = 0; i < MAX_CLIENTS; i++ ) { idPlayer* player = gameLocal.GetClient( i ); if ( player == NULL || player->GetGameTeam() == NULL || player->IsType( idBot::Type ) ) { continue; } total++; if ( player->IsReady() ) { ready++; } } return ready; } /* ============ sdGameRules::ArePlayersReady ============ */ readyState_e sdGameRules::ArePlayersReady( bool readyIfNoPlayers, bool checkMin, int* numRequired ) { float readyFrac = idMath::ClampFloat( 0.f, 1.f, ( gameLocal.serverInfoData.readyPercent / 100.f ) ); if ( checkMin ) { if ( gameLocal.IsMultiPlayer() ) { bool allowBots = false; if ( !networkSystem->IsRankedServer() ) { allowBots = g_useBotsInPlayerTotal.GetBool(); } int c = NumActualClients( false, allowBots ); if ( c < gameLocal.serverInfoData.minPlayers ) { if ( numRequired != NULL ) { *numRequired = gameLocal.serverInfoData.minPlayers - c; } return RS_NOT_ENOUGH_CLIENTS; } } } int total = 0; int ready = NumReady( total ); float amountReady; if ( total == 0 ) { amountReady = 0; if ( readyIfNoPlayers ) { return RS_READY; } } else { amountReady = ready / ( float )total; } if ( amountReady < readyFrac ) { if ( numRequired != NULL ) { *numRequired = idMath::Ceil( readyFrac * total ) - ready; } return RS_NOT_ENOUGH_READY; } return RS_READY; } /* ============ sdGameRules::CanStartMatch ============ */ bool sdGameRules::CanStartMatch( void ) const { if ( adminStarted ) { return true; } if ( gameLocal.serverInfoData.adminStart ) { return false; } switch ( ArePlayersReady( false, true ) ) { case RS_READY: return true; case RS_NOT_ENOUGH_CLIENTS: return false; case RS_NOT_ENOUGH_READY: if ( g_autoReadyPercent.GetFloat() != 0.f ) { int totalPlaying = NumActualClients( false ); int playersRequired = si_maxPlayers.GetInteger() * g_autoReadyPercent.GetFloat() / 100.f; if ( autoReadyStartTime == -1 ) { if ( totalPlaying >= playersRequired ) { autoReadyStartTime = gameLocal.time; } } else { if ( totalPlaying < playersRequired ) { autoReadyStartTime = -1; } else { if ( ( gameLocal.time - autoReadyStartTime ) > MINS2MS( g_autoReadyWait.GetFloat() ) ) { return true; } } } } return false; default: gameLocal.Warning( "CanStartMatch: Unknown ready state" ); break; } return false; } /* ============ sdGameRules::StartMatch ============ */ void sdGameRules::StartMatch( void ) { int warmupTime = GetWarmupTime(); if ( warmupTime == 0 ) { NewState( GS_GAMEON ); } else { needsRestart = true; NewState( GS_COUNTDOWN ); NextStateDelayed( GS_GAMEON, warmupTime ); } } /* ============ sdGameRules::ClearPlayerReadyFlags ============ */ void sdGameRules::ClearPlayerReadyFlags( void ) const { for ( int i = 0; i < MAX_CLIENTS; i++ ) { idPlayer* player = gameLocal.GetClient( i ); if ( player == NULL ) { continue; } player->SetReady( false, true ); } } /* ============ sdGameRules::OnTimeLimitHit ============ */ void sdGameRules::OnTimeLimitHit( void ) { sdScriptHelper h1; scriptObject->CallNonBlockingScriptEvent( scriptObject->GetFunction( "OnTimeLimitHit" ), h1 ); } /* ============ sdGameRules::WriteInitialReliableMessages ============ */ void sdGameRules::WriteInitialReliableMessages( const sdReliableMessageClientInfoBase& target ) { if ( endGameCamera.IsValid() ) { SendCameraEvent( endGameCamera, target ); } } /* ============ sdGameRules::SendCameraEvent ============ */ void sdGameRules::SendCameraEvent( idEntity* entity, const sdReliableMessageClientInfoBase& target ) { sdReliableServerMessage msg( GAME_RELIABLE_SMESSAGE_RULES_DATA ); msg.WriteLong( sdGameRules::EVENT_SETCAMERA ); msg.WriteLong( gameLocal.GetSpawnId( entity ) ); msg.Send( target ); } /* ============ sdGameRules::CallScriptEndGame ============ */ void sdGameRules::CallScriptEndGame( void ) { if ( !gameLocal.isClient ) { sdScriptHelper h1; scriptObject->CallNonBlockingScriptEvent( scriptObject->GetFunction( "OnGameEnd" ), h1 ); } } /* ============ sdGameRules::RecordWinningTeam ============ */ void sdGameRules::RecordWinningTeam( sdTeamInfo* winner, const char* prefix, bool includeTeamName ) { for ( int i = 0; i < MAX_CLIENTS; i++ ) { idPlayer* player = gameLocal.GetClient( i ); if ( player == NULL || player->IsSpectator() ) { continue; } sdTeamInfo* playerTeam = player->GetGameTeam(); const char* statName = NULL; if ( winner == NULL ) { statName = va( includeTeamName ? "%s_drawn_%s" : "%s_drawn", prefix, playerTeam->GetLookupName() ); } else { if ( winner == playerTeam ) { statName = va( includeTeamName ? "%s_won_%s" : "%s_won", prefix, playerTeam->GetLookupName() ); } else { statName = va( includeTeamName ? "%s_lost_%s" : "%s_lost", prefix, playerTeam->GetLookupName() ); } } sdPlayerStatEntry* stat = sdGlobalStatsTracker::GetInstance().GetStat( sdGlobalStatsTracker::GetInstance().AllocStat( statName, sdNetStatKeyValue::SVT_INT ) ); stat->IncreaseValue( i, 1 ); } } /* ============ sdGameRules::IsRuleType ============ */ bool sdGameRules::IsRuleType( const idTypeInfo& type ) { if ( networkSystem->IsRankedServer() ) { return &type == &sdGameRulesCampaign::Type; } return type.IsType( sdGameRules::Type ) && ( &type != &sdGameRules::Type ); } /* ============ sdGameRules::UpdateClientFromServerInfo ============ */ void sdGameRules::UpdateClientFromServerInfo( const idDict& serverInfo, bool allowMedia ) { if( gameState == GS_WARMUP ) { SetWarmupStatusMessage(); } else if( gameState == GS_GAMEON ) { statusText = common->LocalizeText( "guis/hud/in_progress" ); } } /* ============ sdGameRules::SetupLoadScreenUI ============ */ void sdGameRules::SetupLoadScreenUI( sdUserInterfaceScope& scope, const char* status, bool currentMap, int mapIndex, const idDict& metaData, const sdDeclMapInfo* mapInfo ) { using namespace sdProperties; // setup the icon state if ( sdProperty* property = scope.GetProperty( va( "status%d", mapIndex ), PT_STRING ) ) { *property->value.stringValue = status; } // setup the name if ( sdProperty* property = scope.GetProperty( va( "title%d", mapIndex ), PT_WSTRING ) ) { *property->value.wstringValue = va( L"%hs", metaData.GetString( "pretty_name" ) ); } if( currentMap ) { // setup the name if ( sdProperty* property = scope.GetProperty( "mapName", PT_WSTRING ) ) { *property->value.wstringValue = va( L"%hs", metaData.GetString( "pretty_name" ) ); } } if( mapInfo != NULL ) { idStr defaultPosition = va( "%i %i", SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2 ); // setup the icon position idVec2 position = mapInfo->GetData().GetVec2( "mapPosition", defaultPosition.c_str() ); if ( sdProperty* property = scope.GetProperty( va( "position%d", mapIndex ), PT_VEC2 ) ) { *property->value.vec2Value = position; } if( currentMap ) { // setup the map shot if ( sdProperty* property = scope.GetProperty( "mapShot", PT_STRING ) ) { *property->value.stringValue = mapInfo->GetServerShot()->GetName(); } // setup the location if ( sdProperty* property = scope.GetProperty( "mapLocation", PT_INT ) ) { *property->value.intValue = declHolder.declLocStrType.LocalFind( mapInfo->GetData().GetString( "mapLocation", "guis/mainmenu/nointel" ) )->Index(); } // setup the briefing if ( sdProperty* property = scope.GetProperty( "mapBriefing", PT_INT ) ) { *property->value.intValue = declHolder.declLocStrType.LocalFind( mapInfo->GetData().GetString( "mapBriefing", "guis/mainmenu/nointel" ) )->Index(); } // setup the music if ( sdProperty* property = scope.GetProperty( "mapMusic", PT_STRING ) ) { *property->value.stringValue = mapInfo->GetData().GetString( "snd_music" ); } } } } /* ============ sdGameRules::GetProbeState ============ */ byte sdGameRules::GetProbeState( void ) const { switch( gameState ) { case GS_WARMUP: // FALL THROUGH case GS_COUNTDOWN: return PGS_WARMUP; case GS_GAMEON: return PGS_RUNNING; case GS_GAMEREVIEW: return PGS_REVIEWING; } return PGS_LOADING; } /* ============ sdGameRules::GetServerBrowserScore ============ */ int sdGameRules::GetServerBrowserScore( const sdNetSession& session ) const { int score = 0; if ( session.GetGameState() & PGS_WARMUP ) { score += sdHotServerList::BROWSER_GOOD_BONUS; } else if ( session.GetGameState() & PGS_WARMUP ) { score += 0; } else { int minsLeft = ( int )( MS2SEC( session.GetSessionTime() ) / 60.f ); if ( minsLeft >= 15 ) { score += sdHotServerList::BROWSER_OK_BONUS; } } return score; } /* ============ sdGameRules::GetBrowserStatusString ============ */ void sdGameRules::GetBrowserStatusString( idWStr& str, const sdNetSession& netSession ) const { str.Clear(); if( netSession.GetGameState() & PGS_WARMUP ) { str = va( L"%ls", declHolder.declLocStrType.LocalFind( "guis/mainmenu/server/warmup" )->GetText() ); return; } else if( netSession.GetGameState() & PGS_LOADING ) { str = va( L"%ls", declHolder.declLocStrType.LocalFind( "guis/mainmenu/server/loading" )->GetText() ); return; } else if( netSession.GetGameState() & PGS_REVIEWING ) { str = va( L"%ls", declHolder.declLocStrType.LocalFind( "guis/mainmenu/server/reviewing" )->GetText() ); return; } else { if( netSession.GetSessionTime() == 0 ) { str = infinity->GetText(); } else { idWStr::hmsFormat_t format; format.showZeroMinutes = true; format.showZeroSeconds = false; str = va( L"%ls", idWStr::MS2HMS( netSession.GetSessionTime(), format ) ); } } } /* ============ sdGameRules::InhibitEntitySpawn ============ */ bool sdGameRules::InhibitEntitySpawn( idDict &spawnArgs ) const { if ( spawnArgs.GetBool( "stopwatchOnly" ) ) { return true; } return false; } /* ============ sdGameRules_SingleMapHelper::ArgCompletion_StartGame ============ */ void sdGameRules_SingleMapHelper::ArgCompletion_StartGame( const idCmdArgs& args, argCompletionCallback_t callback ) { // public builds only allow maps with metadata // otherwise, unofficial defs wouldn't be loaded after pure restarts #if defined( SD_PUBLIC_BUILD ) if( gameLocal.mapMetaDataList == NULL ) { return; } const char* cmd = args.Argv( 1 ); int len = idStr::Length( cmd ); int num = gameLocal.mapMetaDataList->GetNumMetaData(); for ( int i = 0; i < num; i++ ) { const metaDataContext_t& metaData = gameLocal.mapMetaDataList->GetMetaDataContext( i ); if ( !gameLocal.IsMetaDataValidForPlay( metaData, false ) ) { continue; } const idDict& meta = *metaData.meta; const char* metaName = meta.GetString( "metadata_name" ); if ( idStr::Icmpn( metaName, cmd, len ) ) { continue; } callback( va( "%s %s", args.Argv( 0 ), metaName ) ); } #else idCmdSystem::ArgCompletion_EntitiesName( args, callback ); #endif } /* ============ sdGameRules_SingleMapHelper::OnUserStartMap ============ */ userMapChangeResult_e sdGameRules_SingleMapHelper::OnUserStartMap( const char* text, idStr& reason, idStr& mapName ) { mapName = text; SanitizeMapName( mapName, true ); #if defined( SD_PUBLIC_BUILD ) idStr metaDataName = text; SanitizeMapName( metaDataName, false ); const metaDataContext_t* metaData = gameLocal.mapMetaDataList->FindMetaDataContext( metaDataName.c_str() ); if ( metaData == NULL || !gameLocal.IsMetaDataValidForPlay( *metaData, false ) ) { reason = va( "Unknown map '%s'", metaDataName.c_str() ); return UMCR_ERROR; } if( metaData->addon ) { if( !fileSystem->IsAddonPackReferenced( metaData->pak ) ) { fileSystem->ReferenceAddonPack( metaData->pak ); idCmdArgs args; args.AppendArg( "spawnServer" ); args.AppendArg( text ); cmdSystem->SetupReloadEngine( args ); return UMCR_STOP; } } #endif return UMCR_CONTINUE; } /* ================ sdGameRules_SingleMapHelper::SanitizeMapName ================ */ void sdGameRules_SingleMapHelper::SanitizeMapName( idStr& mapName, bool setExtension ) { if ( mapName.Icmpn( "maps/", 5 ) ) { mapName = "maps/" + mapName; } if ( setExtension ) { mapName.SetFileExtension( "entities" ); } else { mapName.StripFileExtension(); } }