//========= 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"

#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,
};

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
{
	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 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( void );

#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<LobbyPlayerInfo_t> &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 GetMvMPotentialDefendersLobbyPlayerInfo( CUtlVector<LobbyPlayerInfo_t> &vecMvmDefenders, bool bIncludeBots = false );

#endif

	//----------------------------------------------------------------------------------
	// 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; }

	bool PrevRoundWasWaitingForPlayers() { return m_bPrevRoundWasWaitingForPlayers; }

	virtual bool ShouldScorePerRound( void ){ return true; }

	bool CheckNextLevelCvar( void );

	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 );
	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 );
	}

	virtual void PlayTrainCaptureAlert( CTeamControlPoint *pPoint, bool bFinalPointInMap ){ return; }

	virtual void PlaySpecialCapSounds( int iCappingTeam ){ 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( void );
	int			 GetTimeLeft( void );
	virtual	bool CheckWinLimit( void );
	bool		 CheckMaxRounds( void );

	void		 CheckReadyRestart( void );
#if defined(TF_CLIENT_DLL) || defined(TF_DLL)
	bool		 AreDefendingPlayersReady();
#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_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* LoseSongName( void ) { 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 );

protected:
	CGameRulesRoundStateInfo	*m_pCurStateInfo;			// Per-state data 
	float						m_flStateTransitionTime;	// Timer for round states

	float						m_flWaitingForPlayersTimeEnds;
	CHandle<CTeamRoundTimer>	m_hWaitingForPlayersTimer;

	float						m_flNextPeriodicThink;
	bool						m_bChangeLevelOnRoundEnd;

	bool						m_bResetTeamScores;
	bool						m_bResetPlayerScores;
	bool						m_bResetRoundsPlayed;

	// Stalemate
	EHANDLE						m_hPreviousActiveTimer;
	CHandle<CTeamRoundTimer>	m_hStalemateTimer;
	float						m_flStalemateStartTime;

	CHandle<CTeamRoundTimer>	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<string_t>		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;

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 );

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 );
	
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;

public:

	float	m_flStopWatchTotalTime;
	int		m_iLastCapPointChanged;
};

// Utility function
bool FindInList( const char **pStrings, const char *pToFind );

inline CTeamplayRoundBasedRules* TeamplayRoundBasedRules()
{
	return static_cast<CTeamplayRoundBasedRules*>(g_pGameRules);
}

#endif // TEAMPLAYROUNDBASED_GAMERULES_H
