/*  AMX Mod X script.
 
    Round Non-Stop Plugin
    
    (c) Copyright 2006-2007, Simon Logic (slspam@land.ru)
    This file is provided as is (no warranties).


    Preamble:
    	This plugin is intended to replace old & buggy Fake Team Bot plugin 
    	(by OneEyed).
    
    Info:
        This plugin allows you to play a map without round restart.
        
        Attention! Using this plugin has sense only when free for all mode 
        is enabled (e.g. by CSDM) and/or there is a registered client command 
        or server time-function which respawns dead players. Such 
        a command/function is usually provided by another plugin.

        By this time plugin has autodetect feature to be activated only 
        if the following plugins have been detected:
        * amx_respawn by f117bomb
        * CSDM by CSDM Team
        
        Still you have a chance to test this plugin on your own without having
        above plugins by using a command 'amx_force_round_nonstop' or cvar 
        'amx_round_nonstop'.

	Note:        
        This plugin will not 100% stop round restart because it does not 
        remove map CS specific objectives. Use should use another plugin 
        (e.g CSDM) in conjunction with this one.

    Requirements:
        * CS/CZ mod
        * Fakemeta & CStrike modules

    New commands:
        amx_force_round_nonstop <on|off|auto>
            turns round non-stop on/off/autodetect immediately
        amx_round_nonstop_state
            prints plugin status

    New cvars:
        amx_round_nonstop <-1|0|1> (default=-1)
            controls plugin behaviour:
            -1 - autodetect
             0 - always off
             1 - always on
        amx_round_nonstop_flags <flags> (default=abdfg)
            customize plugin:
            a - hide system bots (visually); turning this flag off is ususally 
            useful for testing or fun
            b - move system bots high enough to free up spawn area 
            (recommended)
            c - activate anti-idle-kick mechanism (recommended when 
            'mp_autokick' is on); feature is activated on bot spawn only
            d - kick bots when server is full (actually max players per each
            team are considered); otherwise system bots will never be kicked
            e - leave bots when server is empty; bots will enter the server 
            before any player
            f - show bots as spectators on Score Table; this flag also
            makes bots totally be hidden on the radar
            g - show bots ping as BOT on Score Table
        amx_round_nonstop_botname_t <string> (default='.')
        	customize system bot name for TERRORIST team (applied on bot
        	respawn only)
        amx_round_nonstop_botname_ct <string> (default=':')
        	customize system bot name for CT team (applied on bot
        	respawn only)

    Known issues:
        * make sure a map has at least two spawn points for each side, 
        otherwise you may get troubles on game commencing
        * on DE maps (may be other CS specific) an engine moves system
        bots to random spawn point each 6 seconds, thus moving them
        high by using flag value 'b' has no sense in this case

    Credits:
    	* OneEyed for the plugin main idea
    	* Space Headed Productions team for their BotAPI
    	* VEN for his discovery of CBasePlayer::m_fLastMovement offset
    	* jim_yang for the idea of showing system bots as spectators on Score
    	Table (idea has been taken from Roundend Blocker plugin)
 
    TODO:
		* introduce plugin management via menu

    History:
    0.3.3 [2007-02-26]
    ! fixed a bug with incorrect detecting of full server using 'maxplayers'
    value
    ! fixed bad porting of BotAPI partial functionality (system bots won't
	hidden at all on respawn; they were hidden on round restart only)
	+ added new flag 'g' for 'amx_round_nonstop_flags' cvar (see description
	above)
	* only one generic task (taskCheckCvar) is allowed (optimization)
    0.3.2 [2007-02-22]
    + cvar 'amx_round_nonstop_botnamet'
    + cvar 'amx_round_nonstop_botnamect'
    + extended 'amx_force_round_nonstop' command
    - avoid using of BotAPI (too many complains on compiling, sorry)
    - removed redundant mod validation because CStrike module is required
    ! forgot to check 'maxplayers' on detecting a full server (spawn points
    quantity was previously used)
    0.3.1 [2007-02-16]
    + added possibilty to show system bots as spectators on Score Table
    (see new flag 'f' for 'amx_round_nonstop_flags' cvar)
    0.3.0 [2007-02-15]
    ! many fixes
    + added anti-idle-kick mechanism for system bots
    * renamed cvar 'amx_round_nonstop_safety' to 'amx_round_nonstop_flags'
    + added more flag values for cvar 'amx_round_nonstop_flags' to extend
    plugin features
    * plugins is rewritten from task-driven to event-driven mechanism (as i 
    planned long ago)
    0.2.0 [2006-12-21]
    * ported from AMX Mod 0.9.9 to AMX Mod X
    + system bots now hidden on a radar
    + replaced 'Game commencing' with 'Round Non-Stop commencing'
    0.1.2 [2006-11-26]
    + added 'amx_round_nonstop_safety' cvar
    0.1.1 [2006-11-16]
    * first public beta release
    0.1.0 [2006-11-12]
    * internal release for testing
*/

#include <amxmodx>
#include <amxmisc> // is_running()
#include <fakemeta>
#include <fakemeta_stocks>
#include <cstrike>
//#include <bot_api>

#pragma tabsize 4

#define MY_PLUGIN_NAME    "Round Non-Stop"
#define MY_PLUGIN_VERSION "0.3.3"
#define MY_PLUGIN_AUTHOR  "Simon Logic"

#define MAX_CLIENTS 32
	// max number of players on server
#define MAX_BOT_NAME 31
	// max length of the bot name
#define MAX_TEAMS 3
	// max number of teams in game + one for index padding

#define MAX_MAP_COORD_Z 4095.0

#define TASK_ANTIIDLEKICK_BASE 65
#define TASK_CHECKCVAR_ID 70

#define OFFSET_LAST_MOVEMENT 124
	// for 32bit cpu only

// plugin flags...
#define SFL_HIDE              (1<<0)
#define SFL_TAKEOFFSPAWNAREA  (1<<1)
#define SFL_ANTIIDLEKICK      (1<<2)
#define SFL_KICKONSERVERFULL  (1<<3)
#define SFL_KICKONSERVEREMPTY (1<<4)
#define SFL_SHOWASSPECTATORS  (1<<5)
#define SFL_SHOWBOTPINGASBOT  (1<<6)

// spawn IDs (first two must match CS_TEAM_X)...
#define SPID_T   1
#define SPID_CT  2
#define SPID_VIP 3

// compilation options...
//#define _DEBUG
	// compile with debug messages
//#define _ASSERT
	// compile with assertions


enum t_bot
{
	i_id,
	i_task_antikick
	// s_name[MAX_BOT_NAME+1]
}

new const g_arrBotNameDef[MAX_TEAMS][] = { // default names
    "",   // a pad to match array index to team index
    ".",  // terrorist bot name
    ":"   // ct bot name
}
new const g_arrSpawnClasses[][] = { // index must match SPID_*
    "",
    "info_player_deathmatch", // CS_TEAM_T
    "info_player_start",      // CS_TEAM_CT
    "info_vip_start"
}
new const  // other resource strings
	g_sClassname[] = "classname",
	g_sGameCommencing[] = "#Game_Commencing"
new g_sSpectator[] = "SPECTATOR"

new bool:g_bAMD64
new bool:g_bFalseRespawn[MAX_CLIENTS+1]
new g_iRoundTime
new g_iMaxPlayers
new g_iSpawnCount[MAX_TEAMS]
new g_tBots[MAX_TEAMS][t_bot] // 1st element is dummy element
new Float:g_fGenericTaskTime
new g_cvarState, g_cvarFlags, g_cvarAmxRespawn, g_cvarCsdmActive
new g_cvarBotNames[MAX_TEAMS]
//new g_fmSetOrigin

forward bool:existsRespawnEngine()
forward bool:isActive()
//-----------------------------------------------------------------------------
public plugin_init()
{   
    g_bAMD64 = bool:is_amd64_server()
    g_iMaxPlayers = get_maxplayers()
    g_tBots[_:CS_TEAM_T][i_id] = 0
    g_tBots[_:CS_TEAM_CT][i_id] = 0

	register_plugin(MY_PLUGIN_NAME, MY_PLUGIN_VERSION, MY_PLUGIN_AUTHOR)
    
	g_iRoundTime = 1

	g_cvarState = register_cvar("amx_round_nonstop", "-1")
	g_cvarFlags = register_cvar("amx_round_nonstop_flags", "abdfg")
	g_cvarBotNames[_:CS_TEAM_T] = register_cvar("amx_round_nonstop_botname_t", ".")
	g_cvarBotNames[_:CS_TEAM_CT] = register_cvar("amx_round_nonstop_botname_ct", ":")
    
    register_concmd("amx_force_round_nonstop", "cmdForce", ADMIN_CFG, "Sets Round Non-Stop state immediately")
    register_clcmd("amx_round_nonstop_state", "cmdState", ADMIN_ALL, "Prints Round Non-Stop state")
    register_event("ResetHUD", "onPlayerSpawn", "be")
    register_event("TextMsg", "onRestartNotify", "a", "2=#Game_will_restart_in")
    register_event("HLTV", "onNewRound", "a", "1=0", "2=0")
    register_logevent("onRoundStart", 2, "1=Round_Start")
	
	register_message(get_user_msgid("TextMsg"), "msgTextMsg")
	register_message(get_user_msgid("Radar"), "msgRadar")
	register_message(get_user_msgid("TeamInfo"), "msgTeamInfo")
	register_message(get_user_msgid("RoundTime"), "msgRoundTime")

	register_forward(FM_StartFrame, "onStartFrame")

	server_print("[AMXX] Plugin %s initialized", MY_PLUGIN_NAME)
}
//-----------------------------------------------------------------------------
public plugin_cfg()
{
	g_cvarAmxRespawn = get_cvar_pointer("amx_respawn")
	g_cvarCsdmActive = get_cvar_pointer("csdm_active")
		
	// NOTE: PSP Fixer plugin balances spawn points within plugin_cfg(), 
	// thus we need to put a small delay to count _proper_ spawn points
	set_task(0.1, "taskCountSP")
	
	//launchGenericTask(0.2)
}
//-----------------------------------------------------------------------------
public taskCountSP()
{
	g_iSpawnCount[SPID_T] = countSP(SPID_T)
	g_iSpawnCount[SPID_CT] = countSP(SPID_CT)

	g_iRoundTime = get_cvar_num("mp_roundtime")
}
//-----------------------------------------------------------------------------
stock launchGenericTask(Float:interval)
{
	if(task_exists(TASK_CHECKCVAR_ID))
	{
		static Float:fTime;	fTime = get_gametime()
		if((fTime - g_fGenericTaskTime) > interval)
		{
			remove_task(TASK_CHECKCVAR_ID)
			set_task(interval, "taskCheckCvar", TASK_CHECKCVAR_ID)
			g_fGenericTaskTime = fTime
#if defined _DEBUG
			log_amx("re-launchGenericTask(interval=%.1f)", interval)
#endif
		}
	}
	else
	{
		set_task(interval, "taskCheckCvar", TASK_CHECKCVAR_ID)
		g_fGenericTaskTime = get_gametime()
#if defined _DEBUG
		log_amx("launchGenericTask(interval=%.1f)", interval)
#endif
	}
}
//-----------------------------------------------------------------------------
public client_disconnect(id)
{
#if defined _DEBUG
	log_amx("client_disconnect(%d)::begin", id)
#endif
	if(isFakeBot(id))
		cleanupBotData(id)
	else
	{
		// NOTE: thus we can avoid simultaneous bots creation & kick during
		// server shutdown
		launchGenericTask(0.5)
	}
#if defined _DEBUG
	log_amx("client_disconnect(%d)::end", id)
#endif
}
//-----------------------------------------------------------------------------
public msgRadar(msg_id, msg_dest, msg_entity)
{
	static id
	
	id = get_msg_arg_int(1)
	
	if(id && (id == g_tBots[_:CS_TEAM_T][i_id] || id == g_tBots[_:CS_TEAM_CT][i_id]))
	{
		/*
		set_msg_arg_float(2, ARG_COORD, 0.0)
		set_msg_arg_float(3, ARG_COORD, 0.0)
		set_msg_arg_float(4, ARG_COORD, 0.0)
		*/
		return PLUGIN_HANDLED // this is the same as above
	}

	return PLUGIN_CONTINUE
}
//-----------------------------------------------------------------------------
public msgTextMsg(msg_id, msg_dest, msg_entity)
{
    if(get_msg_arg_int(1) == 4 && isActive() 
    && (g_tBots[_:CS_TEAM_CT][i_id] || g_tBots[_:CS_TEAM_T][i_id]))
    {
        static sTemp[sizeof(g_sGameCommencing)]
        
        get_msg_arg_string(2, sTemp, sizeof(sTemp))
        if(equal(sTemp, g_sGameCommencing))
        	set_msg_arg_string(2, "Round Non-Stop Commencing!")
    }

    return PLUGIN_CONTINUE
}
//-----------------------------------------------------------------------------
public msgTeamInfo(msg_id, msg_dest, msg_entity)
{
	static id

	id = get_msg_arg_int(1)
	
	if(id && (id == g_tBots[_:CS_TEAM_CT][i_id] || id == g_tBots[_:CS_TEAM_T][i_id])
	&& (SFL_SHOWASSPECTATORS & getPCvarAsFlags(g_cvarFlags)))
		set_msg_arg_string(2, g_sSpectator)

	return PLUGIN_CONTINUE
}
//-----------------------------------------------------------------------------
public msgRoundTime(msg_id, msg_dest, msg_entity)
{
	if(isActive())
		set_msg_arg_int(1, ARG_SHORT, g_iRoundTime)

	return PLUGIN_CONTINUE
}
//-----------------------------------------------------------------------------
public onStartFrame()
{
	if(g_tBots[_:CS_TEAM_T][i_id])
		updateBotMarker(g_tBots[_:CS_TEAM_T][i_id])
	if(g_tBots[_:CS_TEAM_CT][i_id])
		updateBotMarker(g_tBots[_:CS_TEAM_CT][i_id])
}
//-----------------------------------------------------------------------------
public updateBotMarker(id)
{
	if(pev_valid(id))
	{
		static Float:fMSec

		set_pev(id, pev_flags, pev(id, pev_flags) | FL_FAKECLIENT)
		global_get(glb_frametime, fMSec)
		fMSec *= 1000.0
		// NOTE: without that a bot will not receive in-game events
		EF_RunPlayerMove(id, Float:{0.0,0.0,0.0}, 0.0, 0.0, 0.0, 0, 0, floatround(fMSec))
	}
}
//-----------------------------------------------------------------------------
public onNewRound()
{
#if defined _DEBUG
	log_amx("onNewRound()::begin")
#endif
	// NOTE: after new round is triggered onPlayerSpawn does not
	// called on system bots, thus i make a fix; it's long enough
	// to make sure a double respawn is gone (double respawn is 
	// specific for CSDM)
	if(SFL_TAKEOFFSPAWNAREA & getPCvarAsFlags(g_cvarFlags))
		set_task(1.0, "taskMoveBotsHigh")

#if defined _DEBUG
	new arr[2]
	arr[0] = g_tBots[1][i_id]
	arr[1] = 0
	set_task(0.1, "taskShowBotOrigin", 700, arr, sizeof(arr))

	log_amx("onNewRound()::end")
#endif
}
//-----------------------------------------------------------------------------
public taskMoveBotsHigh()
{
	new pid

	for(new i=1; i<MAX_TEAMS; i++)
	{			
		pid = g_tBots[i][i_id]
		if(pid && isFakeBot(pid))
			moveBotHigh(pid)
	}
}
//-----------------------------------------------------------------------------
#if defined _DEBUG
public taskShowBotOrigin(arr[2])
{
	new id = arr[0]

	if(is_user_connected(id))
	{
		new Float:vecOrigin[3]
		pev(id, pev_origin, vecOrigin)
		log_amx("FakeBot[%d].origin = {%.3f, %.3f, %.3f}", id, vecOrigin[0], vecOrigin[1], vecOrigin[2])
		if(arr[1] < 10)
		{
			arr[1]++
			set_task(0.2, "taskShowBotOrigin", 700, arr, sizeof(arr))
		}
	}
}
#endif
//-----------------------------------------------------------------------------
public onRoundStart()
{
#if defined _DEBUG
	log_amx("onRoundStart()::begin")
#endif
	g_iRoundTime = get_cvar_num("mp_roundtime")

	// NOTE: hooking SetOrigin does not help to block changing of bot origin
	/*
	if(SFL_TAKEOFFSPAWNAREA & getPCvarAsFlags(g_cvarFlags)) {
		if(!g_fmSetOrigin)
			g_fmSetOrigin = register_forward(FM_SetOrigin, "onSetOrigin")
	}
	else {
		if(g_fmSetOrigin) {
			unregister_forward(FM_SetOrigin, g_fmSetOrigin)
			g_fmSetOrigin = 0
		}
	}
	*/

	launchGenericTask(0.1)

#if defined _DEBUG
	log_amx("onRoundStart()::end")
#endif
}
//-----------------------------------------------------------------------------
/*
public onSetOrigin(id, const Float:vecOrigin[3])
{
	if(id && (id == g_tBots[_:CS_TEAM_CT][i_id] || id == g_tBots[_:CS_TEAM_T][i_id]))
	{		
		static Float:vecTemp[3]
		
		vecTemp[0] = vecOrigin[0]
		vecTemp[1] = vecOrigin[1]
		vecTemp[2] = MAX_MAP_COORD_Z
		
		EF_SetOrigin(id, vecTemp)

		return FMRES_OVERRIDE
	}
	
	return FMRES_IGNORED
}
*/
//-----------------------------------------------------------------------------
public onRestartNotify()
{
#if defined _DEBUG
	log_amx("onRestartNotify()")
#endif
	new arrPlayers[MAX_CLIENTS], iCount
    
	get_players(arrPlayers, iCount, "a")

	for(new i=0; i<iCount; i++)
		g_bFalseRespawn[arrPlayers[i]] = true
}
//-----------------------------------------------------------------------------
public onPlayerSpawn(id)
{
	if(g_bFalseRespawn[id]) {
		g_bFalseRespawn[id] = false
		return PLUGIN_CONTINUE
	}
#if defined _DEBUG
	log_amx("onPlayerSpawn(id=%d)::begin", id)
#endif
	if(isFakeBot(id))
	{
		new arr[1]; arr[0] = id
		
		if(task_exists(id))
			remove_task(id)
		set_task(0.1, "taskHideBot", id, arr, sizeof(arr))
	}
	else
	{
		launchGenericTask(0.5)
	}
#if defined _DEBUG
	log_amx("onPlayerSpawn(id=%d)::end", id)
#endif
	return PLUGIN_CONTINUE
}
//-----------------------------------------------------------------------------
public cmdState(pid, acl, cid)
{
    console_print(pid, "[AMXX] Round Non-Stop is %s", isActive() ? "ACTIVE": "INACTIVE")
    return PLUGIN_HANDLED
}
//-----------------------------------------------------------------------------
public cmdForce(pid, acl, cid)
{
    if(!access(pid, acl)) {
        console_print(pid, "[AMXX] You have no access to that command")
        return PLUGIN_HANDLED
    }
    if(read_argc() != 2) {
        printForceHelp(pid)
        return PLUGIN_HANDLED
    }
    
    new sArg[5]; read_argv(1, sArg, sizeof(sArg)-1); strtolower(sArg)
    if(equal(sArg, "on")) {
        set_pcvar_num(g_cvarState, 1)
        //taskCheckCvar()
        launchGenericTask(0.1)
    }
    else if(equal(sArg, "off")) {
        set_pcvar_num(g_cvarState, 0)
        //taskCheckCvar()
        launchGenericTask(0.1)
    }
    else if(equal(sArg, "auto")) {
        set_pcvar_num(g_cvarState, -1)
        //taskCheckCvar()
        launchGenericTask(0.1)
    }
    else {
        printForceHelp(pid)
    }

    return PLUGIN_HANDLED
}
//-----------------------------------------------------------------------------
printForceHelp(id)
{
    console_print(id, "[AMXX] Usage: amx_force_round_nonstop <on|off|auto>")
}
//-----------------------------------------------------------------------------
bool:isActive()
{
    new bool:bResult

    switch(get_pcvar_num(g_cvarState))
    {
    case -1:
        bResult = existsRespawnEngine()
    case 0:
        bResult = false
    case 1:
        bResult = true
    default: {
        new i
        for(i=1; i<MAX_TEAMS; i++)
			if(!isFakeBot(g_tBots[i][i_id]))
				break
		bResult = i == MAX_TEAMS // actually it's suspended state
        }
    }
    
    return bResult
}
//-----------------------------------------------------------------------------
public taskCheckCvar()
{
    new bool:bActive
#if defined _DEBUG
	log_amx("taskCheckCvar()::begin")
#endif
    switch(get_pcvar_num(g_cvarState))
    {
    case -1:
        bActive = existsRespawnEngine()
    case 0:
        bActive = false
    case 1:
        bActive = true
    default:
        // do nothing (it's a hidden feature)
        return
    }

    if(bActive) {
    	new bool:arrFlags[MAX_TEAMS] = {true, ...} // by default create bots
		new i, iFlags, arrCount[MAX_TEAMS], arrPlayers[MAX_CLIENTS]

       	// count players on server...
       	get_players(arrPlayers, i, "e", "CT") // e - match with team
        arrCount[_:CS_TEAM_CT] = i
        get_players(arrPlayers, i, "e", "TERRORIST")
   	    arrCount[_:CS_TEAM_T] = i

   	    iFlags = getPCvarAsFlags(g_cvarFlags)

       	if(!arrCount[_:CS_TEAM_CT] && !arrCount[_:CS_TEAM_T])
       	{
			if(SFL_KICKONSERVEREMPTY & iFlags) {
				arrFlags[_:CS_TEAM_CT] = false
				arrFlags[_:CS_TEAM_T] = false
			}
       	}
		else if(SFL_KICKONSERVERFULL & iFlags)
       	{
	       	for(i=1; i<MAX_TEAMS; i++)
    	   	{
       			// exclude system bots from count vars
       			if(g_tBots[i][i_id]) arrCount[i]--
#if defined _DEBUG
	       		log_amx("arrCount[%d] = %d", i, arrCount[i])
    	   		log_amx("g_iSpawnCount[%d] = %d", i, g_iSpawnCount[i])
#endif
				if(g_iSpawnCount[i] > 1)
				{
					if(arrCount[i] && (g_iSpawnCount[i] - arrCount[i]) < 2)
						// server is full according to spawn points quantity
						// available on map
						arrFlags[i] = false
				}
			}
		
			if((g_iMaxPlayers - arrCount[_:CS_TEAM_CT] - arrCount[_:CS_TEAM_T]) < 3)
			{	// server is full according to 'maxplayers'
				arrFlags[_:CS_TEAM_T] = false
				arrFlags[_:CS_TEAM_CT] = false
			}
		}

		updateBots(arrFlags)
	}
	else {
		// kick them all
		updateBots(bool:{false, false, false})
	}
#if defined _DEBUG
	log_amx("taskCheckCvar()::end")
#endif
	return
}
//-----------------------------------------------------------------------------
bool:createBot(team_id)
{
    new pid
#if defined _DEBUG
	log_amx("createBot(team_id=%d)", team_id)
#endif
    //pid = find_player("ali", g_arrBotNameDef[team_id]) // name match, case insensitive, skip real players
    pid = g_tBots[team_id][i_id]
    if(pid)
    {
    	if(is_user_bot(pid)) {
        	// fakebot is already in game
        	if(CS_TEAM_T <= cs_get_user_team(pid) <= CS_TEAM_CT)
            	return false
#if defined _DEBUG       	
       	// TODO: discover whether it happens
       	log_amx("bot has invalid team")
#endif
		}
		else
			// NOTE: normally this shoud never happen
			pid = 0
    }
    
    if(!pid)
    {
    	new sName[MAX_BOT_NAME+1]

    	get_pcvar_string(g_cvarBotNames[team_id], sName, MAX_BOT_NAME)
    	
    	if(!sName[0])
    		pid = create_bot(g_arrBotNameDef[team_id])
		else
			pid = create_bot(sName)
        
        // store fake bot's id
        g_tBots[team_id][i_id] = pid
	}

    if(pid > 0) {
        new sNum[2]
        
        num_to_str(team_id, sNum, sizeof(sNum)-1)
        // set bot's team & model
        //setTeam(pid, team_id)
        engclient_cmd(pid, "jointeam", sNum) // this is more proper than setTeam()

       	// spawn a bot right here
		cs_user_spawn(pid)

		// NOTE: a bot will be hidden on respawn event

        return true
    }

    return false
}
//-----------------------------------------------------------------------------
updateBots(bool:flags[MAX_TEAMS])
{
#if defined _DEBUG
	log_amx("updateBots(StateT=%d, StateCS=%d)", flags[_:CS_TEAM_T], flags[_:CS_TEAM_CT])
#endif
    for(new i=1; i<MAX_TEAMS; i++)
        if(flags[i]) 
        	createBot(i)
		else
		{
			new id = g_tBots[i][i_id]
			
			if(id && is_user_connected(id))
				server_cmd("kick #%d", get_user_userid(id))
		}
}
//-----------------------------------------------------------------------------
cleanupBotData(id)
{
#if defined _DEBUG
	log_amx("cleanupBot(id=%d)", id)
#endif
	new i
	
	for(i=1; i<MAX_TEAMS; i++)
		if(g_tBots[i][i_id] == id)
		{
			new id_task = g_tBots[i][i_task_antikick]

			if(task_exists(id_task))
			{
#if defined _ASSERT
				assert id_task != 0
#endif
				remove_task(id_task)
			}
            
			g_tBots[i][i_id] = 0
			g_tBots[i][i_task_antikick] = 0
			
			break
		}
}
//-----------------------------------------------------------------------------
moveBotHigh(id)
{
	// take the bot off the spawn area to allow real players 
	// to be spawned here
	new Float:vecOrigin[3]

	pev(id, pev_origin, vecOrigin)
	vecOrigin[2] = MAX_MAP_COORD_Z
#if defined _DEBUG
	log_amx("new origin for FakeBot[%d]: {%.3f, %.3f, %.3f}", id, vecOrigin[0], vecOrigin[1], vecOrigin[2])
#endif
	EF_SetOrigin(id, vecOrigin)
}
//-----------------------------------------------------------------------------
public taskHideBot(arr[1])
{
	new id = arr[0]
	if(pev_valid(id))
		hideBot(id)
}
//-----------------------------------------------------------------------------
hideBot(pid, forced=false) // NOTE: pid must be validated before this call
{
#if defined _DEBUG
    log_amx("hideBot(%d)", pid)
#endif
    if(!forced && pev(pid, pev_solid) == SOLID_NOT) {
#if defined _DEBUG
        log_amx("FakeBot[%d] is already hidden", pid)
#endif
        return
    }
    
    new iFlags = getPCvarAsFlags(g_cvarFlags)

    // NOTE: see HLSDK client.cpp::ClientDisconnect() for more info
    
    if(SFL_HIDE & iFlags)
    {
    	// make invisible (actually until hit)
    	//set_pev(pid, pev_effects, pev(pid, pev_effects) | EF_NODRAW | EF_NOINTERP)
    	set_pev(pid, pev_effects, pev(pid, pev_effects) | EF_NODRAW)
	}
    // do not attract autoaim (a bot can escape grenade damage)
    set_pev(pid, pev_takedamage, DAMAGE_NO)
    // not solid (a bot can escape bullet hit)
    set_pev(pid, pev_solid, SOLID_NOT)
    // do not attract homing weapons
    set_pev(pid, pev_flags, pev(pid, pev_flags) | FL_NOTARGET)
    // make a bot hang in the air + noclip for reliability
    set_pev(pid, pev_movetype, MOVETYPE_NOCLIP)
    
	if(SFL_TAKEOFFSPAWNAREA & iFlags)
		moveBotHigh(pid)

	new id_team = _:cs_get_user_team(pid)
	new id_task = g_tBots[id_team][i_task_antikick]
	
	if(id_task && task_exists(id_task))
		remove_task(id_task)
		        
	if(SFL_ANTIIDLEKICK & iFlags && g_iRoundTime)
    {
		new arr[1]; arr[0] = pid; /*arr[1] = team_id*/
        	
		id_task = genTaskID(pid + TASK_ANTIIDLEKICK_BASE)
		set_task(g_iRoundTime * 60.0, "taskResetLastMovementTime", id_task, arr, sizeof(arr), "b")
		g_tBots[id_team][i_task_antikick] = id_task
	}
}
//-----------------------------------------------------------------------------
public taskResetLastMovementTime(arr[1])
{
	new id = arr[0]
#if defined _DEBUG
	log_amx("taskResetLastMovementTime(id=%d)", id)
#endif
	if(!is_user_connected(id) || !is_user_alive(id))
		return

#if defined _DEBUG
	log_amx("reset last movement time on FakeBot[%d]", id)
#endif
	if(!g_bAMD64)
		set_pdata_float(id, OFFSET_LAST_MOVEMENT, get_gametime())
}
//-----------------------------------------------------------------------------
stock setTeam(id, team_id) // NOTE: id must be valdated before this call
{    
    // NOTE: bot is dead here because he hasn't selected a team yet
#if defined _DEBUG
    log_amx("setTeam(id=%d, team_id=%d)", id, team_id)
#endif
    switch(team_id)
    {
        case 1: cs_set_user_team(id, CS_TEAM_T, CS_T_TERROR)
        case 2: cs_set_user_team(id, CS_TEAM_CT, CS_CT_URBAN)
    }
}
//-----------------------------------------------------------------------------
stock genTaskID(base_id=1, max_id=9999)
{
    new iTaskId = base_id
    
    while(task_exists(iTaskId) && iTaskId <= max_id) 
        iTaskId++
    if(iTaskId > max_id)
        return 0
    return iTaskId
}
//-----------------------------------------------------------------------------
bool:existsRespawnEngine()
{
	return (g_cvarAmxRespawn && get_pcvar_num(g_cvarAmxRespawn))
		|| (g_cvarCsdmActive && get_pcvar_num(g_cvarCsdmActive))
}
//-----------------------------------------------------------------------------
bool:isFakeBot(pid)
{
	if(pid && is_user_connected(pid) && is_user_bot(pid))
	{   
	    return (pid == g_tBots[_:CS_TEAM_T][i_id] || pid == g_tBots[_:CS_TEAM_CT][i_id])
	}
	
	return false
}
//-----------------------------------------------------------------------------
getPCvarAsFlags(pcvar)
{
	new sValue[27]

	get_pcvar_string(pcvar, sValue, sizeof(sValue) - 1)

	return read_flags(sValue)
}
//-----------------------------------------------------------------------------
// count spawn points on current map
countSP(type, bool:check_origin = true)
{
    new iCount = 0, iID = 0
#if defined _ASSERT
    assert type != 0
#endif
    if(check_origin) {
        new Float:vecOrigin[3]
        do {
            iID = EF_FindEntityByString(iID, g_sClassname, g_arrSpawnClasses[type])
            if(iID > 0) {
                pev(iID, pev_origin, vecOrigin)
                if(vecOrigin[0] || vecOrigin[1] || vecOrigin[2]) iCount++
            }
        } while(iID > 0)
    } else {
        while(0 < (iID = EF_FindEntityByString(iID, g_sClassname, g_arrSpawnClasses[type])))
            iCount++
            
	}

    return iCount
}
//-----------------------------------------------------------------------------
public create_bot(const name[]) // taken from BotAPI 0.5.1
{
	new id

	id = EF_CreateFakeClient(name)

	if (pev_valid(id))
	{
		EF_FreeEntPrivateData(id)

		DF_MetaFunc_CallGameEntity("player", id)
		set_user_info(id, "rate", "3500")
		set_user_info(id, "cl_updaterate", "25")
		set_user_info(id, "cl_lw", "1")
		set_user_info(id, "cl_lc", "1")
		set_user_info(id, "cl_dlmax", "128")
		set_user_info(id, "cl_righthand", "1")
		set_user_info(id, "_vgui_menus", "0")
		set_user_info(id, "_ah", "0")
		set_user_info(id, "dm", "0")
		set_user_info(id, "tracker", "0")
		set_user_info(id, "friends", "0")
		if(SFL_SHOWBOTPINGASBOT & getPCvarAsFlags(g_cvarFlags))
			set_user_info(id, "*bot", "1")
		set_pev(id, pev_flags, pev(id, pev_flags) | FL_FAKECLIENT)
		set_pev(id, pev_colormap, id)

		new msg[128]

		DF_ClientConnect(id, name, "127.0.0.1", msg)
		DF_ClientPutInServer(id)
		EF_RunPlayerMove(id, Float:{0.0,0.0,0.0}, 0.0, 0.0, 0.0, 0, 0, 76)

		return id
	}

	return 0
}
//-----------------------------------------------------------------------------
