//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: Teamplay game rules that manage a round based structure for you // //============================================================================= #ifndef TEAMPLAYROUNDBASED_GAMERULES_H #define TEAMPLAYROUNDBASED_GAMERULES_H #ifdef _WIN32 #pragma once #endif #include "teamplay_gamerules.h" #include "teamplay_round_timer.h" #include "GameEventListener.h" #ifdef GAME_DLL #include "team_control_point.h" extern ConVar mp_respawnwavetime; extern ConVar mp_showroundtransitions; extern ConVar mp_enableroundwaittime; extern ConVar mp_showcleanedupents; extern ConVar mp_bonusroundtime; extern ConVar mp_restartround; extern ConVar mp_winlimit; extern ConVar mp_maxrounds; extern ConVar mp_stalemate_timelimit; extern ConVar mp_stalemate_enable; #else #define CTeamplayRoundBasedRules C_TeamplayRoundBasedRules #define CTeamplayRoundBasedRulesProxy C_TeamplayRoundBasedRulesProxy #endif extern ConVar tf_arena_use_queue; extern ConVar mp_stalemate_meleeonly; extern ConVar mp_forceautoteam; class CTeamplayRoundBasedRules; //----------------------------------------------------------------------------- // Round states //----------------------------------------------------------------------------- enum gamerules_roundstate_t { // initialize the game, create teams GR_STATE_INIT = 0, //Before players have joined the game. Periodically checks to see if enough players are ready //to start a game. Also reverts to this when there are no active players GR_STATE_PREGAME, //The game is about to start, wait a bit and spawn everyone GR_STATE_STARTGAME, //All players are respawned, frozen in place GR_STATE_PREROUND, //Round is on, playing normally GR_STATE_RND_RUNNING, //Someone has won the round GR_STATE_TEAM_WIN, //Noone has won, manually restart the game, reset scores GR_STATE_RESTART, //Noone has won, restart the game GR_STATE_STALEMATE, //Game is over, showing the scoreboard etc GR_STATE_GAME_OVER, //Game is in a bonus state, transitioned to after a round ends GR_STATE_BONUS, //Game is awaiting the next wave/round of a multi round experience GR_STATE_BETWEEN_RNDS, GR_NUM_ROUND_STATES }; enum { WINREASON_NONE =0, WINREASON_ALL_POINTS_CAPTURED, WINREASON_OPPONENTS_DEAD, WINREASON_FLAG_CAPTURE_LIMIT, WINREASON_DEFEND_UNTIL_TIME_LIMIT, WINREASON_STALEMATE, WINREASON_TIMELIMIT, WINREASON_WINLIMIT, WINREASON_WINDIFFLIMIT, #if defined(TF_CLIENT_DLL) || defined(TF_DLL) WINREASON_RD_REACTOR_CAPTURED, WINREASON_RD_CORES_COLLECTED, WINREASON_RD_REACTOR_RETURNED, #endif }; enum stalemate_reasons_t { STALEMATE_JOIN_MID, STALEMATE_TIMER, STALEMATE_SERVER_TIMELIMIT, NUM_STALEMATE_REASONS, }; #if defined(TF_CLIENT_DLL) || defined(TF_DLL) /// Info about a player in a PVE game or any other mode that we /// might eventually decide to use the lobby system for. struct LobbyPlayerInfo_t { int m_nEntNum; //< Index of player (1...MAX_PLAYERS), or 0 if the guy is in the lobby but not yet known to us CUtlString m_sPlayerName; //< Player display name CSteamID m_steamID; //< Steam ID of the player int m_iTeam; //< Team selection. bool m_bInLobby; //< Is this guy in the lobby? bool m_bConnected; //< Is this a bot? bool m_bBot; //< Is this a bot? bool m_bSquadSurplus; //< Did he present a voucher to get surplus for his squad }; #endif //----------------------------------------------------------------------------- // Purpose: Per-state data //----------------------------------------------------------------------------- class CGameRulesRoundStateInfo { public: gamerules_roundstate_t m_iRoundState; const char *m_pStateName; void (CTeamplayRoundBasedRules::*pfnEnterState)(); // Init and deinit the state. void (CTeamplayRoundBasedRules::*pfnLeaveState)(); void (CTeamplayRoundBasedRules::*pfnThink)(); // Do a PreThink() in this state. }; //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- class CTeamplayRoundBasedRulesProxy : public CGameRulesProxy { public: DECLARE_CLASS( CTeamplayRoundBasedRulesProxy, CGameRulesProxy ); DECLARE_NETWORKCLASS(); #ifdef GAME_DLL DECLARE_DATADESC(); void InputSetStalemateOnTimelimit( inputdata_t &inputdata ); #endif //---------------------------------------------------------------------------------- // Client specific #ifdef CLIENT_DLL void OnPreDataChanged( DataUpdateType_t updateType ); void OnDataChanged( DataUpdateType_t updateType ); #endif // CLIENT_DLL }; //----------------------------------------------------------------------------- // Purpose: Teamplay game rules that manage a round based structure for you //----------------------------------------------------------------------------- class CTeamplayRoundBasedRules : public CTeamplayRules, public CGameEventListener { DECLARE_CLASS( CTeamplayRoundBasedRules, CTeamplayRules ); public: CTeamplayRoundBasedRules(); #ifdef CLIENT_DLL DECLARE_CLIENTCLASS_NOBASE(); // This makes datatables able to access our private vars. void SetRoundState( int iRoundState ); #else DECLARE_SERVERCLASS_NOBASE(); // This makes datatables able to access our private vars. #endif float GetLastRoundStateChangeTime( void ) const { return m_flLastRoundStateChangeTime; } float m_flLastRoundStateChangeTime; // Data accessors inline gamerules_roundstate_t State_Get( void ) { return m_iRoundState; } bool IsInWaitingForPlayers( void ) { return m_bInWaitingForPlayers; } virtual bool InRoundRestart( void ) { return State_Get() == GR_STATE_PREROUND; } bool InStalemate( void ) { return State_Get() == GR_STATE_STALEMATE; } bool RoundHasBeenWon( void ) { return State_Get() == GR_STATE_TEAM_WIN; } virtual float GetNextRespawnWave( int iTeam, CBasePlayer *pPlayer ); virtual bool HasPassedMinRespawnTime( CBasePlayer *pPlayer ); virtual void LevelInitPostEntity( void ); virtual float GetRespawnTimeScalar( int iTeam ); virtual float GetRespawnWaveMaxLength( int iTeam, bool bScaleWithNumPlayers = true ); virtual bool ShouldRespawnQuickly( CBasePlayer *pPlayer ) { return false; } float GetMinTimeWhenPlayerMaySpawn( CBasePlayer *pPlayer ); // Return false if players aren't allowed to cap points at this time (i.e. in WaitingForPlayers) virtual bool PointsMayBeCaptured( void ) { return ((State_Get() == GR_STATE_RND_RUNNING || State_Get() == GR_STATE_STALEMATE) && !IsInWaitingForPlayers()); } virtual void SetLastCapPointChanged( int iIndex ) { m_iLastCapPointChanged = iIndex; } int GetLastCapPointChanged( void ) { return m_iLastCapPointChanged; } virtual int GetWinningTeam( void ){ return m_iWinningTeam; } int GetWinReason() { return m_iWinReason; } bool InOvertime( void ){ return m_bInOvertime; } void SetOvertime( bool bOvertime ); bool InSetup( void ){ return m_bInSetup; } void BalanceTeams( bool bRequireSwitcheesToBeDead ); bool SwitchedTeamsThisRound( void ) { return m_bSwitchedTeamsThisRound; } virtual bool ShouldBalanceTeams( void ); bool IsInTournamentMode( void ); bool IsInHighlanderMode( void ); bool IsInPreMatch( void ) { return (IsInTournamentMode() && IsInWaitingForPlayers()); } bool IsWaitingForTeams( void ) { return m_bAwaitingReadyRestart; } bool IsInStopWatch( void ) { return m_bStopWatch; } void SetInStopWatch( bool bState ) { m_bStopWatch = bState; } virtual void StopWatchModeThink( void ) { }; bool IsTeamReady( int iTeamNumber ) { return m_bTeamReady[iTeamNumber]; } bool IsPlayerReady( int iIndex ) { return m_bPlayerReady[iIndex]; } virtual void HandleTeamScoreModify( int iTeam, int iScore) { }; float GetRoundRestartTime( void ) { return m_flRestartRoundTime; } //Arena Mode virtual bool IsInArenaMode( void ) { return false; } //Koth Mode virtual bool IsInKothMode( void ) { return false; } //Training Mode virtual bool IsInTraining( void ) { return false; } virtual bool IsInItemTestingMode( void ) { return false; } void SetMultipleTrains( bool bMultipleTrains ){ m_bMultipleTrains = bMultipleTrains; } bool HasMultipleTrains( void ){ return m_bMultipleTrains; } virtual int GetBonusRoundTime( bool bFinal = false ); #if defined(TF_CLIENT_DLL) || defined(TF_DLL) // Get list of all the players, including those in the lobby but who have // not yet joined. void GetAllPlayersLobbyInfo( CUtlVector &vecPlayers, bool bIncludeBots = false ); // Get list of players who are on the defending team now, or are likely // to end up on the defending team (not yet connected or assigned a team) void GetPotentialPlayersLobbyPlayerInfo( CUtlVector &vecLobbyPlayers, bool bIncludeBots = false ); #endif void SetAllowBetweenRounds( bool bValue ) { m_bAllowBetweenRounds = bValue; } public: // IGameEventListener Interface virtual void FireGameEvent( IGameEvent * event ); //---------------------------------------------------------------------------------- // Server specific #ifdef GAME_DLL // Derived game rules class should override these public: // Override this to prevent removal of game specific entities that need to persist virtual bool RoundCleanupShouldIgnore( CBaseEntity *pEnt ); virtual bool ShouldCreateEntity( const char *pszClassName ); // Called when a new round is being initialized virtual void SetupOnRoundStart( void ) { return; } // Called when a new round is off and running virtual void SetupOnRoundRunning( void ) { return; } // Called before a new round is started (so the previous round can end) virtual void PreviousRoundEnd( void ) { return; } // Send the team scores down to the client virtual void SendTeamScoresEvent( void ) { return; } // Send the end of round info displayed in the win panel virtual void SendWinPanelInfo( void ) { return; } // Setup spawn points for the current round before it starts virtual void SetupSpawnPointsForRound( void ) { return; } // Called when a round has entered stalemate mode (timer has run out) virtual void SetupOnStalemateStart( void ) { return; } virtual void SetupOnStalemateEnd( void ) { return; } virtual void SetSetup( bool bSetup ); virtual bool ShouldGoToBonusRound( void ) { return false; } virtual void SetupOnBonusStart( void ) { return; } virtual void SetupOnBonusEnd( void ) { return; } virtual void BonusStateThink( void ) { return; } virtual void BetweenRounds_Start( void ) { return; } virtual void BetweenRounds_End( void ) { return; } virtual void BetweenRounds_Think( void ) { return; } virtual void PreRound_End( void ) { return; } bool PrevRoundWasWaitingForPlayers() { return m_bPrevRoundWasWaitingForPlayers; } virtual bool ShouldScorePerRound( void ){ return true; } bool CheckNextLevelCvar( bool bAllowEnd = true ); virtual bool TimerMayExpire( void ); virtual bool IsValveMap( void ){ return false; } virtual void RestartTournament( void ); virtual bool TournamentModeCanEndWithTimelimit( void ){ return true; } public: void State_Transition( gamerules_roundstate_t newState ); void RespawnPlayers( bool bForceRespawn, bool bTeam = false, int iTeam = TEAM_UNASSIGNED ); void SetForceMapReset( bool reset ); void SetRoundToPlayNext( string_t strName ){ m_iszRoundToPlayNext = strName; } string_t GetRoundToPlayNext( void ){ return m_iszRoundToPlayNext; } void AddPlayedRound( string_t strName ); bool IsPreviouslyPlayedRound ( string_t strName ); string_t GetLastPlayedRound( void ); virtual void SetWinningTeam( int team, int iWinReason, bool bForceMapReset = true, bool bSwitchTeams = false, bool bDontAddScore = false, bool bFinal = false ) OVERRIDE; virtual void SetStalemate( int iReason, bool bForceMapReset = true, bool bSwitchTeams = false ); virtual void SetRoundOverlayDetails( void ){ return; } virtual float GetWaitingForPlayersTime( void ) { return mp_waitingforplayers_time.GetFloat(); } void ShouldResetScores( bool bResetTeam, bool bResetPlayer ){ m_bResetTeamScores = bResetTeam; m_bResetPlayerScores = bResetPlayer; } void ShouldResetRoundsPlayed( bool bResetRoundsPlayed ){ m_bResetRoundsPlayed = bResetRoundsPlayed; } void SetFirstRoundPlayed( string_t strName ){ m_iszFirstRoundPlayed = strName ; } string_t GetFirstRoundPlayed(){ return m_iszFirstRoundPlayed; } void SetTeamRespawnWaveTime( int iTeam, float flValue ); void AddTeamRespawnWaveTime( int iTeam, float flValue ); virtual void FillOutTeamplayRoundWinEvent( IGameEvent *event ) {} // derived classes may implement to add fields to this event void SetStalemateOnTimelimit( bool bStalemate ) { m_bAllowStalemateAtTimelimit = bStalemate; } bool IsGameUnderTimeLimit( void ); CTeamRoundTimer *GetActiveRoundTimer( void ); void HandleTimeLimitChange( void ); void SetTeamReadyState( bool bState, int iTeam ) { m_bTeamReady.Set( iTeam, bState ); } void SetPlayerReadyState( int iIndex, bool bState ) { m_bPlayerReady.Set( iIndex, bState ); } void ResetPlayerAndTeamReadyState( void ); virtual void PlayTrainCaptureAlert( CTeamControlPoint *pPoint, bool bFinalPointInMap ){ return; } virtual void PlaySpecialCapSounds( int iCappingTeam, CTeamControlPoint *pPoint ){ return; } bool PlayThrottledAlert( int iTeam, const char *sound, float fDelayBeforeNext ); void BroadcastSound( int iTeam, const char *sound, int iAdditionalSoundFlags = 0 ); int GetRoundsPlayed( void ) { return m_nRoundsPlayed; } virtual void RecalculateControlPointState( void ){ return; } virtual bool ShouldSkipAutoScramble( void ){ return false; } virtual bool ShouldWaitToStartRecording( void ){ return IsInWaitingForPlayers(); } protected: virtual void Think( void ); virtual void CheckChatText( CBasePlayer *pPlayer, char *pText ); void CheckChatForReadySignal( CBasePlayer *pPlayer, const char *chatmsg ); // Game beginning / end handling virtual void GoToIntermission( void ); void SetInWaitingForPlayers( bool bWaitingForPlayers ); void CheckWaitingForPlayers( void ); virtual bool AllowWaitingForPlayers( void ) { return true; } void CheckRestartRound( void ); bool CheckTimeLimit( bool bAllowEnd = true ); int GetTimeLeft( void ); virtual bool CheckWinLimit( bool bAllowEnd = true ); bool CheckMaxRounds( bool bAllowEnd = true ); void CheckReadyRestart( void ); #if defined(TF_CLIENT_DLL) || defined(TF_DLL) bool AreLobbyPlayersOnTeamReady( int iTeam ); bool AreLobbyPlayersConnected( void ); #endif virtual bool CanChangelevelBecauseOfTimeLimit( void ) { return true; } virtual bool CanGoToStalemate( void ) { return true; } // State machine handling void State_Enter( gamerules_roundstate_t newState ); // Initialize the new state. void State_Leave(); // Cleanup the previous state. void State_Think(); // Update the current state. static CGameRulesRoundStateInfo* State_LookupInfo( gamerules_roundstate_t state ); // Find the state info for the specified state. // State Functions void State_Enter_INIT( void ); void State_Think_INIT( void ); void State_Enter_PREGAME( void ); void State_Think_PREGAME( void ); void State_Enter_STARTGAME( void ); void State_Think_STARTGAME( void ); void State_Enter_PREROUND( void ); void State_Leave_PREROUND( void ); void State_Think_PREROUND( void ); void State_Enter_RND_RUNNING( void ); void State_Think_RND_RUNNING( void ); void State_Enter_TEAM_WIN( void ); void State_Think_TEAM_WIN( void ); void State_Enter_RESTART( void ); void State_Think_RESTART( void ); void State_Enter_STALEMATE( void ); void State_Think_STALEMATE( void ); void State_Leave_STALEMATE( void ); void State_Enter_BONUS( void ); void State_Think_BONUS( void ); void State_Leave_BONUS( void ); void State_Enter_BETWEEN_RNDS( void ); void State_Leave_BETWEEN_RNDS( void ); void State_Think_BETWEEN_RNDS( void ); // mp_scrambleteams_auto void ResetTeamsRoundWinTracking( void ); protected: virtual void InitTeams( void ); virtual int CountActivePlayers( void ); virtual void RoundRespawn( void ); virtual void CleanUpMap( void ); virtual void CheckRespawnWaves( void ); void ResetScores( void ); void ResetMapTime( void ); void PlayStartRoundVoice( void ); void PlayWinSong( int team ); void PlayStalemateSong( void ); void PlaySuddenDeathSong( void ); virtual const char* GetStalemateSong( int nTeam ) { return "Game.Stalemate"; } virtual const char* WinSongName( int nTeam ) { return "Game.YourTeamWon"; } virtual const char* LoseSongName( int nTeam ) { return "Game.YourTeamLost"; } virtual void RespawnTeam( int iTeam ) { RespawnPlayers( false, true, iTeam ); } void HideActiveTimer( void ); virtual void RestoreActiveTimer( void ); virtual void InternalHandleTeamWin( int iWinningTeam ){ return; } bool MapHasActiveTimer( void ); void CreateTimeLimitTimer( void ); virtual float GetLastMajorEventTime( void ) OVERRIDE { return m_flLastTeamWin; } protected: CGameRulesRoundStateInfo *m_pCurStateInfo; // Per-state data float m_flStateTransitionTime; // Timer for round states float m_flWaitingForPlayersTimeEnds; CHandle m_hWaitingForPlayersTimer; float m_flNextPeriodicThink; bool m_bChangeLevelOnRoundEnd; bool m_bResetTeamScores; bool m_bResetPlayerScores; bool m_bResetRoundsPlayed; // Stalemate EHANDLE m_hPreviousActiveTimer; CHandle m_hStalemateTimer; float m_flStalemateStartTime; CHandle m_hTimeLimitTimer; bool m_bForceMapReset; // should the map be reset when a team wins and the round is restarted? bool m_bPrevRoundWasWaitingForPlayers; // was the previous map reset after a waiting for players period bool m_bInitialSpawn; string_t m_iszRoundToPlayNext; CUtlVector m_iszPreviousRounds; // we'll store the two previous rounds so we won't play them again right away if there are other rounds that can be played first string_t m_iszFirstRoundPlayed; // store the first round played after a full restart so we can pick a different one next time if we have other options float m_flOriginalTeamRespawnWaveTime[ MAX_TEAMS ]; bool m_bAllowStalemateAtTimelimit; bool m_bChangelevelAfterStalemate; float m_flRoundStartTime; // time the current round started float m_flNewThrottledAlertTime; // time that we can play another throttled alert int m_nRoundsPlayed; bool m_bUseAddScoreAnim; gamerules_roundstate_t m_prevState; bool m_bPlayerReadyBefore[MAX_PLAYERS+1]; // Test to see if a player has hit ready before float m_flLastTeamWin; private: CUtlMap < int, int > m_GameTeams; // Team index, Score #endif // End server specific //---------------------------------------------------------------------------------- //---------------------------------------------------------------------------------- // Client specific #ifdef CLIENT_DLL public: virtual void OnPreDataChanged( DataUpdateType_t updateType ); virtual void OnDataChanged( DataUpdateType_t updateType ); virtual void HandleOvertimeBegin(){} virtual void GetTeamGlowColor( int nTeam, float &r, float &g, float &b ){ r = 0.76f; g = 0.76f; b = 0.76f; } private: bool m_bOldInWaitingForPlayers; bool m_bOldInOvertime; bool m_bOldInSetup; #endif // CLIENT_DLL public: bool WouldChangeUnbalanceTeams( int iNewTeam, int iCurrentTeam ); bool AreTeamsUnbalanced( int &iHeaviestTeam, int &iLightestTeam ); virtual bool HaveCheatsBeenEnabledDuringLevel( void ) { return m_bCheatsEnabledDuringLevel; } protected: CNetworkVar( gamerules_roundstate_t, m_iRoundState ); CNetworkVar( bool, m_bInOvertime ); // Are we currently in overtime? CNetworkVar( bool, m_bInSetup ); // Are we currently in setup? CNetworkVar( bool, m_bSwitchedTeamsThisRound ); protected: CNetworkVar( int, m_iWinningTeam ); // Set before entering GR_STATE_TEAM_WIN CNetworkVar( int, m_iWinReason ); CNetworkVar( bool, m_bInWaitingForPlayers ); CNetworkVar( bool, m_bAwaitingReadyRestart ); CNetworkVar( float, m_flRestartRoundTime ); CNetworkVar( float, m_flMapResetTime ); // Time that the map was reset CNetworkArray( float, m_flNextRespawnWave, MAX_TEAMS ); // Minor waste, but cleaner code CNetworkArray( bool, m_bTeamReady, MAX_TEAMS ); CNetworkVar( bool, m_bStopWatch ); CNetworkVar( bool, m_bMultipleTrains ); // two trains in this map? CNetworkArray( bool, m_bPlayerReady, MAX_PLAYERS ); CNetworkVar( bool, m_bCheatsEnabledDuringLevel ); public: CNetworkArray( float, m_TeamRespawnWaveTimes, MAX_TEAMS ); // Time between each team's respawn wave private: float m_flStartBalancingTeamsAt; float m_flNextBalanceTeamsTime; bool m_bPrintedUnbalanceWarning; float m_flFoundUnbalancedTeamsTime; float m_flAutoBalanceQueueTimeEnd; int m_nAutoBalanceQueuePlayerIndex; int m_nAutoBalanceQueuePlayerScore; protected: bool m_bAllowBetweenRounds; public: float m_flStopWatchTotalTime; int m_iLastCapPointChanged; }; // Utility function bool FindInList( const char **pStrings, const char *pToFind ); inline CTeamplayRoundBasedRules* TeamplayRoundBasedRules() { return static_cast(g_pGameRules); } #endif // TEAMPLAYROUNDBASED_GAMERULES_H