#include < amxmodx >
#include < amxmisc >
#include < engine >
#include < cstrike >
#include < hamsandwich >
 
#define OppositeTeam(%1) CsTeams:( ( ( _:%1 ) % 2 ) + 1 )
/*CsTeams:OppositeTeam( CsTeams:iTeam )
{
	return CsTeams:( ( ( _:iTeam ) % 2 ) + 1 );
}*/
 
#define MAX_PLAYERS 32
 
new const g_szSpawnClassnames[ CsTeams ][ ] =
{
	"",
	"info_player_deathmatch",
	"info_player_start",
	""
};
 
new const g_szFakeSpawnClassnames[ CsTeams ][ ] =
{
	"",
	"removed_t_spawn",
	"removed_ct_spawn",
	""
};
 
enum _:RemoveMethods
{
	Remove_Kick,
	Remove_Spec,
	Remove_Transfer
};
 
const g_iDefaultMethod = Remove_Spec;
 
new const g_szRemoveMethods[ RemoveMethods ][ ] =
{
	"kick",
	"spec",
	"transfer"
};
 
new Trie:g_tMethodNameToIndex;
 
new Float:g_fJoinTime[ MAX_PLAYERS + 1 ];
 
new pCvar_RemoveMethod;
 
new bool:g_bFreezeTime = false;
 
public plugin_init( )
{
	register_plugin( "Team Limiter", "0.0.3", "Exolent" );
 
	register_concmd( "team_limits", "CmdSetTeamLimits", ADMIN_RCON, "<terrorist limit> <ct limit> -- * for map default" );
 
	register_event( "HLTV", "EventNewRound", "a", "1=0", "2=0" );
	register_logevent( "EventRoundStart", 2, "1=Round_Start" );
 
	pCvar_RemoveMethod = register_cvar( "tl_remove", g_szRemoveMethods[ g_iDefaultMethod ] );
 
	g_tMethodNameToIndex = TrieCreate( );
 
	for( new iMethod = 0; iMethod < RemoveMethods; iMethod++ )
	{
		TrieSetCell( g_tMethodNameToIndex, g_szRemoveMethods[ iMethod ], iMethod );
	}
}
 
public plugin_end( )
{
	TrieDestroy( g_tMethodNameToIndex );
}
 
public client_putinserver( iPlayer )
{
	g_fJoinTime[ iPlayer ] = get_gametime( );
}
 
public CmdSetTeamLimits( iAdmin, iLevel, iCID )
{
	if( !cmd_access( iAdmin, iLevel, iCID, 3 ) )
	{
		return PLUGIN_HANDLED;
	}
 
	new szT[ 3 ], szCT[ 3 ];
	read_argv( 1, szT,  charsmax( szT  ) );
	read_argv( 2, szCT, charsmax( szCT ) );
 
	new iNewLimit[ CsTeams ];
	iNewLimit[ CS_TEAM_T  ] = is_str_num( szT  ) ? str_to_num( szT  ) : -1;
	iNewLimit[ CS_TEAM_CT ] = is_str_num( szCT ) ? str_to_num( szCT ) : -1;
 
	if( iNewLimit[ CS_TEAM_T ] < 0 && iNewLimit[ CS_TEAM_CT ] < 0 )
	{
		console_print( iAdmin, "Setting both limits to < 0 doesn't change the team limit of either team." );
		return PLUGIN_HANDLED;
	}
 
	new iRemoveMethod = GetRemoveMethod( );
 
	new iAllPlayers[ MAX_PLAYERS ], iAllNum;
	get_players( iAllPlayers, iAllNum );
 
	new iPlayers[ CsTeams ][ MAX_PLAYERS ], iNum[ CsTeams ];
 
	new iPlayer, CsTeams:iTeam;
	for( new i = 0; i < iAllNum; i++ )
	{
		iPlayer = iAllPlayers[ i ];
		iTeam = cs_get_user_team( iPlayer );
 
		iPlayers[ iTeam ][ iNum[ iTeam ]++ ] = iPlayer;
	}
 
	new iTransferPlayers[ CsTeams ][ MAX_PLAYERS ], iTransferNum[ CsTeams ];
 
	new bool:bSame = true;
	new iCurLimit;
	new iFakeCount;
	new iDiff;
	new iEntity;
 
	for( new CsTeams:iTeam = CS_TEAM_T; iTeam <= CS_TEAM_CT; iTeam++ )
	{
		iCurLimit = 0;
		iEntity = 0;
		while( ( iEntity = find_ent_by_class( iEntity, g_szSpawnClassnames[ iTeam ] ) ) )
		{
			iCurLimit++;
		}
 
		if( iNewLimit[ iTeam ] < 0 )
		{
			// there is no limit, so check if fake ones exist so the maximum spawns are available
 
			iFakeCount = 0;
			iEntity = 0;
			while( ( iEntity = find_ent_by_class( iEntity, g_szFakeSpawnClassnames[ iTeam ] ) ) )
			{
				iFakeCount++;
			}
 
			// set to real spawns + fake spawns (even if no fake ones exist, it will still skip)
			iNewLimit[ iTeam ] = iCurLimit + iFakeCount;
		}
 
		iDiff = iCurLimit - iNewLimit[ iTeam ];
 
		if( !iDiff )
		{
			continue;
		}
 
		bSame = false;
 
		if( iDiff > 0 )
		{
			// current > new = deleting
 
			iCurLimit = iDiff;
			while( iDiff > 0 )
			{
				iEntity = find_ent_by_class( -1, g_szSpawnClassnames[ iTeam ] );
 
				if( !is_valid_ent( iEntity ) )
				{
					iNewLimit[ iTeam ] += iDiff;
 
					console_print( iAdmin, "There were not enough %sT spawn points to remove. Only removed %i of %i.",\
						iTeam == CS_TEAM_T ? "" : "C", ( iCurLimit - iDiff ), iCurLimit );
 
					break;
				}
 
				entity_set_string( iEntity, EV_SZ_classname, g_szFakeSpawnClassnames[ iTeam ] );
 
				iDiff--;
			}
 
			iDiff = iNum[ iTeam ] - iNewLimit[ iTeam ];
 
			if( iDiff > 0 )
			{
				// more players than spawn points
 
				if( iNum[ iTeam ] > 1 )
				{
					// sort from earliest joining to latest
					SortCustom1D( iPlayers[ iTeam ], iNum[ iTeam ], "SortByJoinTime" );
				}
 
				// last players in array are the last players who joined
				while( iDiff > 0 )
				{
					iPlayer = iPlayers[ iTeam ][ iNum[ iTeam ] - iDiff ];
 
					switch( iRemoveMethod )
					{
						case Remove_Kick:
						{
							message_begin( MSG_ONE, SVC_DISCONNECT, _, iPlayer );
							write_string( "Sorry, your spawn point was removed." );
							message_end( );
						}
						case Remove_Spec:
						{
							TransferPlayer( iPlayer, CS_TEAM_SPECTATOR );
						}
						case Remove_Transfer:
						{
							iTransferPlayers[ iTeam ][ iTransferNum[ iTeam ]++ ] = iPlayer;
						}
					}
 
					iDiff--;
				}
			}
		}
		else
		{
			// current < new = adding
			iDiff *= -1;
 
			iCurLimit = iDiff;
			while( iDiff > 0 )
			{
				iEntity = find_ent_by_class( -1, g_szFakeSpawnClassnames[ iTeam ] );
 
				if( !is_valid_ent( iEntity ) )
				{
					iNewLimit[ iTeam ] -= iDiff;
 
					console_print( iAdmin, "There were not enough %sT spawn points to restore. Only restored %i of %i.",\
						iTeam == CS_TEAM_T ? "" : "C", ( iCurLimit - iDiff ), iCurLimit );
 
					break;
				}
 
				entity_set_string( iEntity, EV_SZ_classname, g_szSpawnClassnames[ iTeam ] );
 
				iDiff--;
			}
		}
	}
 
	if( bSame )
	{
		console_print( iAdmin, "The teams are already limited to this amount." );
	}
	else
	{
		if( iRemoveMethod == Remove_Transfer )
		{
			// find how many both need to transfer so they don't just trade places
			new iSame = min( iTransferNum[ CS_TEAM_T ], iTransferNum[ CS_TEAM_CT ] );
			new i;
			new CsTeams:iOppositeTeam;
			new iAvailable;
 
			for( new CsTeams:iTeam = CS_TEAM_T; iTeam <= CS_TEAM_CT; iTeam++ )
			{
				SortCustom1D( iTransferPlayers[ iTeam ], iTransferNum[ iTeam ], "SortByJoinTime" );
 
				// transfer last joined players who can't fit on a team to spectator
				for( i = 0; i < iSame; i++ )
				{
					TransferPlayer( iTransferPlayers[ iTeam ][ iTransferNum[ iTeam ] - i - 1 ], CS_TEAM_SPECTATOR );
				}
 
				// find how many players need to be transferred
				iDiff = iTransferNum[ iTeam ] - iSame;
 
				// get opposite team to transfer to
				iOppositeTeam = OppositeTeam( iTeam );
 
				// find how many slots are available for players
				iAvailable = iNewLimit[ iOppositeTeam ] - iNum[ iOppositeTeam ];
 
				// available < needed = move extra players to spectator
				while( i < iTransferNum[ iTeam ] )
				{
					TransferPlayer( iTransferPlayers[ iTeam ][ iTransferNum[ iTeam ] - i - 1 ], ( iAvailable < iDiff ) ? CS_TEAM_SPECTATOR : iOppositeTeam );
 
					iDiff--;
					i++;
				}
			}
		}
 
		console_print( iAdmin, "The teams have been limited to T:%i, CT:%i", iNewLimit[ CS_TEAM_T ], iNewLimit[ CS_TEAM_CT ] );
 
		new szName[ 32 ];
		get_user_name( iAdmin, szName, charsmax( szName ) );
 
		new szSteamID[ 35 ];
		get_user_authid( iAdmin, szSteamID, charsmax( szSteamID ) );
 
		show_activity( iAdmin, szName, "limited the teams to T:%i, CT:%i", iNewLimit[ CS_TEAM_T ], iNewLimit[ CS_TEAM_CT ] );
 
		log_amx( "%s<%s> limited the teams to T:%i, CT:%i", szName, szSteamID, iNewLimit[ CS_TEAM_T ], iNewLimit[ CS_TEAM_CT ] );
	}
 
	return PLUGIN_HANDLED;
}
 
public EventNewRound( )
{
	g_bFreezeTime = true;
}
 
public EventRoundStart( )
{
	g_bFreezeTime = false;
}
 
public SortByJoinTime( iIndex1, iIndex2, iPlayers[ ], iData[ ], iDataSize )
{
	return ( ( g_fJoinTime[ iPlayers[ iIndex1 ] ] - g_fJoinTime[ iPlayers[ iIndex2 ] ] ) < 0.0 ) ? -1 : 1;
}
 
GetRemoveMethod( )
{
	new szMethod[ 32 ];
	get_pcvar_string( pCvar_RemoveMethod, szMethod, charsmax( szMethod ) );
	strtolower( szMethod );
 
	new iMethod;
	if( TrieGetCell( g_tMethodNameToIndex, szMethod, iMethod ) )
	{
		return iMethod;
	}
 
	if( is_str_num( szMethod ) )
	{
		iMethod = str_to_num( szMethod );
 
		if( 0 <= iMethod < RemoveMethods )
		{
			return iMethod;
		}
	}
 
	static szInvalidMessage[ 128 ];
	new iLen = formatex( szInvalidMessage, charsmax( szInvalidMessage ), "Invalid remove method '%s'. Valid Methods: [", szMethod );
 
	for( iMethod = 0; iMethod < RemoveMethods; iMethod++ )
	{
		iLen += formatex( szInvalidMessage[ iLen ], charsmax( szInvalidMessage ) - iLen, "'%s', ", g_szRemoveMethods[ iMethod ] );
	}
 
	// remove the last ", " from the end
	iLen -= 2;
 
	copy( szInvalidMessage[ iLen ], charsmax( szInvalidMessage ) - iLen, "]" );
 
	log_amx( "%s", szInvalidMessage );
 
	set_pcvar_string( pCvar_RemoveMethod, g_szRemoveMethods[ g_iDefaultMethod ] );
 
	return g_iDefaultMethod;
}
 
TransferPlayer( iPlayer, CsTeams:iTeam )
{
	static const szTeamNames[ CsTeams ][ ] = 
	{
		"Spectator",
		"Terrorist",
		"Counter-Terrorist",
		"Spectator"
	};
 
	client_print( iPlayer, print_chat, "* Sorry, your spawn point was removed, so you are now a %s.", szTeamNames[ iTeam ] );
 
	cs_set_user_team( iPlayer, iTeam );
 
	if( is_user_alive( iPlayer ) )
	{
		// if switched teams during freezetime, revive player
		if( ( CS_TEAM_T <= iTeam <= CS_TEAM_CT ) && g_bFreezeTime )
		{
			ExecuteHamB( Ham_CS_RoundRespawn, iPlayer );
		}
		else
		{
			user_silentkill( iPlayer );
		}
	}
}
/* AMXX-Studio Notes - DO NOT MODIFY BELOW HERE
*{\\ rtf1\\ ansi\\ deff0{\\ fonttbl{\\ f0\\ fnil Tahoma;}}\n\\ viewkind4\\ uc1\\ pard\\ lang1033\\ f0\\ fs16 \n\\ par }
*/