/* AMXModX Script
 
   Title:    Play or Be Kicked
   Author:   Brad Jones

   Current Version:   1.4
   Release Date:      2005-12-11
   Compatibility:     AMXX 1.60 or higher

   This plugin has the ability to kick players for any of the following three events:

      * not joining a team or spectator mode in time when initially joining the server
      * spectatating too long
      * being AFK too long

   Which events your server looks for is configurable as is the amount of time allowed for each event.


   INSTALLATION

      File Locations:
      
         .\plugins\pbk.amxx
         .\data\lang\pbk.txt
         .\data\lang\time_length.txt
         

   OPTIONS (CVARS)

      pbk_join_min_players <players>
         
         Minimum number of players on the server before people that haven't fully joined start getting kicked.

         The default is 4 players.

      pbk_spec_min_players <players>
         
         Minimum number of players on the server before people spectating start getting kicked.

         The default is 4 players.

      pbk_afk_min_players <players>
         
         Minimum number of players on the server before people AFK (away from keyboard) start getting kicked.

         The default is 4 players.

      pbk_join_time <n> (default 120)
   
         Maximum number of seconds a player has to initially join a team before getting kicked.
         A value of 0 will disable checking of initial join status.

      pbk_spec_time <n> (default 120)
      
         Maximum number of seconds a player is allowed to spectate before getting kicked.
         A value of 0 will disable checking of spectator status.
         
      pbk_afk_time <n> (default 90)
      
         Maximum number of seconds a player is allowed to be AFK before getting kicked.
         A value of 0 will disable checking of AFK status.

      pbk_warning_time <n> (default 20)
      
         Number of seconds a player starts getting warned about an impending kick.

      pbk_immunity <iFlags>
      
         Specifies which events players with the immunity flag will not be kicked for.
         A value of 0 means everyone can be kicked for any event.
         
         1: joining
         2: spectating
         4: being AFK
         
         The default is 7 (players with immunity won't be kicked for any event)

         The immunity flag is set as ADMIN_IMMUNITY by default but can be changed in the script.
         
      pbk_immunity_warning <iFlags>
      
         Specifies which events to show the countdown warning to players with immunity.
         A value of 0 will mean that the countdown is never shown for any event.
         
         The flags are additive.
         1: joining
         2: spectating
         4: being AFK
         
         The default is 7 (show warning for all events).
         
      pbk_log <iFlags>
      
         Specifies how to log kicks.  A value of 0 will not log any kicks.

         The flags are additive.         
         1: log in the AMXX log
         2: log in the chat log (allows kicks to be seen in programs like HLSW that show you the chat log)
         4: log in their own file ("pbkMM.log" where "MM" is the two-digit month)
         
         The default is 3 (log in AMXX and chat logs).
         
      pbk_log_cnt <0..11>
         
         Indicates the number of previous month's logs to retain. The current month is always retained.
         
         The default is 2 months.

         
   CHANGE LOG:

      2005-12-11   1.4   - Fixed inconsistencies between what the docs said the default values for certain
                           CVARs were and what they were set as in the code.  The docs were correct, 
                           the code was wrong.
                         - Replaced "pbk_min_players" with three new CVARs: "pbk_join_min_players", 
                           "pbk_spec_min_players", and "pbk_afk_min_players".  This allows you to have
                           finer control over when players should be kicked.
      2005-11-11   1.3   - Fixed issue where player would be able to choose a team but not a model
                           and then sit there indefinitely. 
                         - Added CVAR "pbk_log" which lets you specify how to log kicks. Options are to log
                           in the AMXX log (as was previously done), the chat log (allows kicks to be seen in
                           programs like HLSW that show you the chat log), and in their own file (pbkMM.log 
                           where MM is the two-digit month). The default is to log in the AMXX and chat logs.
                         - Added CVAR "pbk_log_cnt" which lets you specify how many months of logs to keep if
                           you are logging kicks into their own file.
                         - Added functionality to kick AFK users via new CVAR "pbk_afk_time". A value of 0 will
                           disable checking of AFK status.
                         - Renamed CVAR "pbk_un_time" to "pbk_join_time". Added the ability to specify 0 to 
                           disable checking of initial join status.
                         - Added ability to specify 0 for the "pbk_spec_time" CVAR to disable checking of 
                           spectator status.
                         - Removed CVAR "pbk_restrict". Functionality that it provided is now being provided
                           via "pbk_join_time", "pbk_spec_time", and "pbk_afk_time".
                         - Renamed CVAR "pbk_allow_immunity" to "pbk_immunity".
                         - Changed the options for "pbk_immunity" to allow indication of what events an
                           immune player can be immune from being kicked. Options are "joining", "spectating", 
                           and "being AFK".
                         - Added CVAR "pbk_immunity_warning" to indicate whether players with immunity should
                           be shown the warning countdown. The default is to show the countdown.
                         - Fixed issue where the time length wasn't multilingual.

      2005-07-16   1.1   - Will ignore HLTV users. 
                         - Added CVAR to allow immune players to be kicked. 
                         - Replaced CVAR "pbk_time" with "pbk_spec_time" and "pbk_un_time".

      2005-07-10   1.0   Initial release.
   
*/

/*--------------------------------------------------------
COMPILER OPTIONS
--------------------------------------------------------*/
//--------------------------------------------------------
// Set the flag that indicates how often a player is
// checked for non-play. This flag also indicates the 
// warning message frequency.
#define CHECK_FREQ 5
//--------------------------------------------------------
// Set the flag that indicates if a player has immunity
// from being kicked.
#define IMMUNITY ADMIN_IMMUNITY
//--------------------------------------------------------


#include <amxmodx>
#include <amxmisc>
#include <logging>
#include <time_length>

new const PLUGIN[]  = "Play or Be Kicked";
new const VERSION[] = "1.4";
new const AUTHOR[]  = "Brad Jones";

// team flags
#define TEAM_T  1
#define TEAM_CT 2

// event flags
#define EVENT_JOIN 1
#define EVENT_SPEC 2
#define EVENT_AFK  4

// coordinate info
#define MAX_PLAYER_CNT 33	// really 32, but 32 is 0-31 and we want 1-32, so... 33
#define MAX_COORD_CNT   3


new g_playerJoined[MAX_PLAYER_CNT], g_playerSpawned[MAX_PLAYER_CNT];
new g_timeJoin[MAX_PLAYER_CNT], g_timeSpec[MAX_PLAYER_CNT], g_timeAFK[MAX_PLAYER_CNT];
new g_prevCoords[MAX_PLAYER_CNT][MAX_COORD_CNT];

new bool:g_roundInProgress = false;

public plugin_init()
{
	register_plugin(PLUGIN, VERSION, AUTHOR);
	
	register_cvar("pbk_debug", "-1");
	register_cvar("pbk_version", VERSION, FCVAR_SERVER|FCVAR_SPONLY);  // For GameSpy/HLSW and such

	register_dictionary("pbk.txt");
	register_dictionary("time_length.txt");

	register_event("ResetHUD", "event_resethud", "be");

	register_logevent("event_round_start", 2, "0=World triggered", "1=Round_Start");
	register_logevent("event_round_end", 2, "0=World triggered", "1=Round_End")	;
	
	register_cvar("pbk_join_min_players", "4");
	register_cvar("pbk_spec_min_players", "4");
	register_cvar("pbk_afk_min_players", "4");

	register_cvar("pbk_join_time", "120");
	register_cvar("pbk_spec_time", "120");
	register_cvar("pbk_afk_time", "90");

	register_cvar("pbk_immunity", "7");
	register_cvar("pbk_immunity_warning", "7");

	register_cvar("pbk_warning_time", "20");

	register_cvar("pbk_log", "3");
	register_cvar("pbk_log_cnt", "2");

	cycle_log_files("pbk", clamp(get_cvar_num("pbk_log_cnt"), 0, 11)); // must keep between 0 and 11 months

	set_task(float(CHECK_FREQ), "check_players", _, _, _, "b");

	return PLUGIN_CONTINUE;
}

public client_PostThink(id)
{
	if (!g_playerJoined[id])
	{
		new team[16], teamID = get_user_team(id, team, 15);
		if (teamID == TEAM_T || teamID == TEAM_CT || equal(team, "SPECTATOR")) g_playerJoined[id] = true;
	}
	return PLUGIN_CONTINUE;
}

public client_disconnect(id)
{
	g_playerJoined[id] = false;
	g_playerSpawned[id] = false;
	
	g_timeJoin[id] = 0;
	g_timeSpec[id] = 0;
	g_timeAFK[id] = 0;
	
	return PLUGIN_CONTINUE;
}

public event_resethud(id)
{
	if (!g_playerSpawned[id]) g_playerSpawned[id] = true;
	return PLUGIN_CONTINUE;
}

public event_round_end()
{
	g_roundInProgress = false;
	return PLUGIN_CONTINUE;
}

public event_round_start()
{
	// reset the coords of each player (for use in AFK checking)
	new iPlayers[32], playerCnt, id;
	get_players(iPlayers, playerCnt, "c"); // skip bots
	
	for (new playerIdx = 0; playerIdx < playerCnt; playerIdx++)
	{
		id = iPlayers[playerIdx];
		get_user_origin(id, g_prevCoords[id], 3);
	}
	
	// note that the round has started
	g_roundInProgress = true;
	
	return PLUGIN_CONTINUE;
}

public check_players()
{
	new playerCnt = get_playersnum();
	new team[16], eventType, curCoords[MAX_COORD_CNT];

	new bool:checkJoinStatus = (get_cvar_num("pbk_join_time") && playerCnt >= get_cvar_num("pbk_join_min_players"));
	new bool:checkSpecStatus = (get_cvar_num("pbk_spec_time") && playerCnt >= get_cvar_num("pbk_spec_min_players"));
	new bool:checkAFKStatus  = (get_cvar_num("pbk_afk_time") && playerCnt >= get_cvar_num("pbk_afk_min_players") && g_roundInProgress);

	for (new id = 1; id <= get_maxplayers(); id++)
	{
		if (!is_user_connected(id) || is_user_bot(id) || is_user_hltv(id)) continue;

		if (g_playerJoined[id])
		{
			get_user_team(id, team, 15);
			eventType = equal(team, "SPECTATOR") ? EVENT_SPEC : EVENT_AFK;

			if (eventType == EVENT_AFK && checkAFKStatus && g_playerSpawned[id] && is_user_alive(id))
			{
				// grab the current 'weapon hit point' coords for the player
				get_user_origin(id, curCoords, 3);

				// compare to previous coords
				if (g_prevCoords[id][0] == curCoords[0] && g_prevCoords[id][1] == curCoords[1] && g_prevCoords[id][2] == curCoords[2])
				{
					g_timeAFK[id] += CHECK_FREQ;
				}
				else
				{
					g_prevCoords[id] = curCoords;
					g_timeAFK[id] = 0;
				}
			}
			else if (eventType == EVENT_SPEC && checkSpecStatus)
			{
				g_timeSpec[id] += CHECK_FREQ;
			}
			else continue;
		}
		else 
		{
			eventType = EVENT_JOIN;
			if (checkJoinStatus) g_timeJoin[id] += CHECK_FREQ;
			else continue;
		}
		handle_time_elapsed(id, eventType);
	}
}

public handle_time_elapsed(id, eventType)
{
	new immunityFlag = get_cvar_num("pbk_immunity");
	new warningFlag = get_cvar_num("pbk_immunity_warning");
	new maxSeconds, elapsedSeconds, eventImmunity, showWarning;
	if (eventType == EVENT_JOIN)
	{
		maxSeconds = get_cvar_num("pbk_join_time");
		elapsedSeconds = g_timeJoin[id];
		eventImmunity = immunityFlag & EVENT_JOIN;
		showWarning = eventImmunity ? warningFlag & EVENT_JOIN : 1;
	}
	else if (eventType == EVENT_SPEC)
	{
		maxSeconds = get_cvar_num("pbk_spec_time");
		elapsedSeconds = g_timeSpec[id];
		eventImmunity = immunityFlag & EVENT_SPEC;
		showWarning = eventImmunity ? warningFlag & EVENT_SPEC : 1;
	}
	else if (eventType == EVENT_AFK)
	{
		maxSeconds = get_cvar_num("pbk_afk_time");
		elapsedSeconds = g_timeAFK[id];
		eventImmunity = immunityFlag & EVENT_AFK;
		showWarning = eventImmunity ? warningFlag & EVENT_AFK : 1;
	}
	else return;
	
	new warningStartSeconds = maxSeconds - get_cvar_num("pbk_warning_time");
	
	if (elapsedSeconds >= maxSeconds) 
	{
		// if players can have immunity for this event and the player has immunity, abort
		if (eventImmunity && get_user_flags(id) & IMMUNITY) return;

		// get the correct message formats for this event type
		new msgReason[32], msgAnnounce[32];
		switch (eventType)
		{
			case EVENT_JOIN:
			{
				copy(msgReason, 31, "KICK_JOIN_REASON");
				copy(msgAnnounce, 31, "KICK_JOIN_ANNOUNCE");
			}
			case EVENT_SPEC:
			{
				copy(msgReason, 31, "KICK_SPEC_REASON");
				copy(msgAnnounce, 31, "KICK_SPEC_ANNOUNCE");
			}
			case EVENT_AFK:
			{
				copy(msgReason, 31, "KICK_AFK_REASON");
				copy(msgAnnounce, 31, "KICK_AFK_ANNOUNCE");
			}
		}

		// kick the user
		new maxTime[128];
		get_time_length(id, maxSeconds, timeunit_seconds, maxTime, 127);
		
		server_cmd("kick #%d %L", get_user_userid(id), id, msgReason, maxTime);

		// announce the kick to the rest of the world
		new players[32], playerCnt;
		get_players(players, playerCnt, "c");
		new playerName[32];
		get_user_name(id, playerName, 31);
		new playerID;

		for (new playerIdx = 0; playerIdx < playerCnt; playerIdx++)
		{
			playerID = players[playerIdx];
			get_time_length(playerID, maxSeconds, timeunit_seconds, maxTime, 127);
			client_print(playerID, print_chat, "[PBK] %L", playerID, msgAnnounce, playerName, maxTime);
		}

		// log the kick
		new logFlags = get_cvar_num("pbk_log");
		if (logFlags)
		{
			get_time_length(0, maxSeconds, timeunit_seconds, maxTime, 127);
			
			new logText[128];
			format(logText, 127, "%L", LANG_SERVER, msgAnnounce, "", maxTime);
			// remove the single space that not providing a name added
			trim(logText);
			
			create_log_entry(id, "PBK", logFlags, logText);
		}
	}
	else if (warningStartSeconds <= elapsedSeconds && showWarning)
	{
		// get the correct message format for this event type
		new msgWarning[32];
		switch (eventType)
		{
			case EVENT_JOIN: copy(msgWarning, 31, "KICK_JOIN_WARNING");
			case EVENT_SPEC: copy(msgWarning, 31, "KICK_SPEC_WARNING");
			case EVENT_AFK:  copy(msgWarning, 31, "KICK_AFK_WARNING");
		}
		
		new timeLeft[128]
		get_time_length(id, maxSeconds - elapsedSeconds, timeunit_seconds, timeLeft, 127);

		// warn the user about their impending departure
		client_print(id, print_chat, "[PBK] %L", id, msgWarning, timeLeft);
	} 
}