#pragma semicolon 1
#pragma newdecls required
#define DEBUG

#define PLUGIN_AUTHOR "jenz"
#define PLUGIN_VERSION "1.8"
#define generic_length 256

#include <sourcemod>
#include <sdktools>
#include <sdkhooks>
#include <outputinfo>
#include <cstrike>
#include <dhooks>
#include <socket>
#include <UNLOZE.secret>

int target_friend_afk_counter[MAXPLAYERS][MAXPLAYERS + 1];
int target_friend[MAXPLAYERS + 1];
int target_enemy[MAXPLAYERS + 1];
bool human_too_close[MAXPLAYERS + 1];
bool bot_follow_tp[MAXPLAYERS + 1];
int ports[7] = {48470, 48471, 48472, 48473, 48474, 48479, 48480}; //last three indexes are ports its sending udp from in plugin
int server_ports[2] = {27015, 27035}; //server ports: ze, ze2
float client_old_coords[MAXPLAYERS + 1][3];
float targetfriend_prev_coords[MAXPLAYERS + 1][3];

int has_to_jump_bots[MAXPLAYERS + 1];
float coords_run_cmd[MAXPLAYERS + 1][3];
bool chat_cooldown = false;

//socket for bot input
Handle global_socket;

//timer handle
Handle g_hTimer_pressing = null;
Handle g_hTimer_bot_connect = null;

//cmdrun handle for gamedata txt
Handle g_hGetRunCmdPre;

//is botplayer
bool is_bot_player[MAXPLAYERS + 1];
int specific_bot_player[MAXPLAYERS + 1];

public Plugin myinfo = 
{
	name = "coordinates for the bot",
	author = PLUGIN_AUTHOR,
	description = "hello ",
	version = PLUGIN_VERSION,
	url = ""
};

public void OnPluginStart()
{
    for (int i = 1; i <= MaxClients; i++)
        if (IsValidClient(i))
        {
            target_friend[i] = 0;
            target_enemy[i] = 0;
            OnClientPostAdminCheck(i);
        }

    Handle hGameConf;
    if ((hGameConf = LoadGameConfigFile("OnPlayerRunCmdPre.games")) == INVALID_HANDLE)
    {
        SetFailState("Couldnt load \"OnPlayerRunCmd.games\" game config!");
        return;
    }

    //CBasePlayer::PlayerRunCommand(CUserCmd*, IMoveHelper*)
    int iGetRunCmdPre;
    if ((iGetRunCmdPre = GameConfGetOffset(hGameConf, "GetRunCmdPre")) == -1)
    {
        delete hGameConf;
        SetFailState("GameConfGetOffset(hGameConf, \"GetRunCmdPre\") failed!");
        return;
    }

    if ((g_hGetRunCmdPre = DHookCreate(iGetRunCmdPre, HookType_Entity, ReturnType_Int, ThisPointer_CBaseEntity, OnGetRunCmdPre)) == INVALID_HANDLE)
    {
        delete hGameConf;
        SetFailState("DHookCreate(iGetRunCmdPre, HookType_Entity, ReturnType_Int, ThisPointer_CBaseEntity, OnGetRunCmdPre) failed!");
        return;
    }

    for (int i = 1; i <= MaxClients; i++)
    {
        if (IsValidClient(i) && is_bot_player[i])
        {
            if (g_hGetRunCmdPre != INVALID_HANDLE)
                DHookEntity(g_hGetRunCmdPre, false, i);
        }
    }

    //talking
    RegConsoleCmd("sm_autism", cmd_talk, "talking to the bot through java application");
    RegConsoleCmd("sm_botrtv", cmd_botrtv, "making bots rtv");
    HookEvent("round_start", Event_RoundStart, EventHookMode_PostNoCopy);
    HookEvent("round_end", Event_RoundEnd, EventHookMode_PostNoCopy);

    //UDP connection
    connect_socket();
    chat_cooldown = false;
    
    g_hTimer_bot_connect = CreateTimer(30.0, bot_check_connect, _, TIMER_REPEAT);
    g_hTimer_pressing = CreateTimer(0.30, recursive_pressing, _, TIMER_REPEAT);
}


public MRESReturn OnGetRunCmdPre(int entity, Handle hReturn)
{
    if (!IsValidEntity(entity))
        return MRES_Ignored;
    //check here if fell off edge, if did change some boolean around tell it to OnPlayerRunCmd when still on ground there
    if (IsValidClient(entity) && IsPlayerAlive(entity) && !(GetEntityFlags(entity) & FL_ONGROUND) && !has_to_jump_bots[entity]
        && is_bot_player[entity] )
    {
        has_to_jump_bots[entity] = 1;
    }

    return MRES_Ignored;
}

public void get_new_angles(int client, int target, float angles[3])
{
    if (IsValidClient(target))
    {
        float TargetPos[3];
        float ClientPos[3];
        float Result[3];
        GetClientEyePosition(target, TargetPos);
        GetClientEyePosition(client, ClientPos);
        MakeVectorFromPoints(ClientPos, TargetPos, Result);
        GetVectorAngles(Result, Result);
        angles[0] = Result[0];
        angles[1] = Result[1];
        angles[2] = Result[2];
   }
}

public Action OnPlayerRunCmd(int client, int& buttons, int& impulse, float vel[3], float angles[3], int& weapon, int& subtype, int& cmdnum, int& tickcount, int& seed, int mouse[2]) 
{
    //we use info we just got from OnPlayerRunCmdPre to determine if to jump
    if (!IsValidClient(client) || !IsPlayerAlive(client) || !is_bot_player[client]) return Plugin_Continue;

    //2023 october: the new place to set angles for the client instead of using face_call, through this is teleporting avoided which can trigger trigger_multiples
    if (!bot_follow_tp[client])
    {
        int targeteam = 0;
        if (GetClientTeam(client) != 3)
        {
            //2 = autismo is zm and should follow enemies sometimes
            targeteam = 2;
        }
        else
        {
            //3 = autismo is human and should follow closest moving ct
            targeteam = 3;
        }

        bool found_enemy = false;
        for (int clienti = 1; clienti <= MaxClients; clienti++)
        {
            if (!IsValidClient(clienti)) continue;
            if (GetClientTeam(clienti) == 2 && IsPlayerAlive(clienti))
            {
                found_enemy = true;
            }
        }

        float pos[3];
        float dist_target = -1.0;
        if (IsValidClient(target_friend[client]))
        {
            GetEntPropVector(target_friend[client], Prop_Send, "m_vecOrigin", pos);
            dist_target = get_power_distance(client, pos);
        }

        float max_range_dist = 65.0;
        //here we put into consideration if the human is leaving or standing around. If the bots already catched up to the human they can have a larger distance between the player and themselves so they dont constantly follow him. If the player is leaving the bots needs to follow him much closer.
        if (targeteam == 3 && human_too_close[client])
        {
            max_range_dist = 200.0;
        }

        float enemy_distance = -1.0;
        if (IsValidClient(target_enemy[client]))
        {
            GetEntPropVector(target_enemy[client], Prop_Send, "m_vecOrigin", pos);
            enemy_distance = get_power_distance(client, pos);
        }


        if (GetEntityMoveType(client) == MOVETYPE_LADDER || (IsValidClient(target_friend[client]) && GetEntityMoveType(target_friend[client]) == MOVETYPE_LADDER))
        {
            if (IsValidClient(target_friend[client]))
            {
                get_new_angles(client, target_friend[client], angles);
            }
        }
        //if bot is ct: is close enough to friend, can stop following friend and focus on shooting zombies
        else if (targeteam == 3 && 0 < dist_target < max_range_dist && IsValidClient(target_enemy[client]))
        {
            get_new_angles(client, target_enemy[client], angles);
        }
        //if bot is ct: if bot is not close enough to friend follow him, //dist_target being larger than max_range_dist means a friend was found to follow
        //we also check if zm already spawned, if not then stay close to friend
        else if ((targeteam == 3 && dist_target > max_range_dist) || !found_enemy)
        {
            get_new_angles(client, target_friend[client], angles);
        }
        //if bot is zm follow enemy sometimes
        else if (targeteam == 2 && 0 < enemy_distance < max_range_dist * 5 && IsValidClient(target_enemy[client]))
        {
            get_new_angles(client, target_enemy[client], angles);
        }
        //if bot is zm just follow the closest friend zm, but not constantly when too close because people start bitching then
        else if (targeteam == 2 && dist_target > max_range_dist / 2)
        {
            get_new_angles(client, target_friend[client], angles);
        }
        //if bot is zm and no close enemies and no friends far away enough to follow then check if friend is close, if its the case then do nothing
        else if (targeteam == 2 && 0 < dist_target < max_range_dist / 2)
        {
            get_new_angles(client, target_friend[client], angles);
        }
        //if bot is zm and there are no friends close or far away but there is also no enemy close check if there is an enemy far away
        else if (targeteam == 2 && IsValidClient(target_enemy[client]))
        {
            get_new_angles(client, target_enemy[client], angles);
        }
    }

    if ((GetEntityMoveType(client) & MOVETYPE_LADDER)) 
    {
        has_to_jump_bots[client] = 2;
        GetEntPropVector(client, Prop_Send, "m_vecOrigin", coords_run_cmd[client]);
        return Plugin_Continue;
    }
    if (GetEntProp(client, Prop_Data, "m_nWaterLevel") != 0)
    {
        has_to_jump_bots[client] = 2;
        return Plugin_Continue;
    }

    //make sure fell of an edge instead of actually jumping.
    if (buttons & IN_JUMP && has_to_jump_bots[client] == 1)
    {
        has_to_jump_bots[client] = 2;
        return Plugin_Continue;
    }

    //!bot_follow_tp[client] ensures that bot is not busy with running into a teleporter
    if (has_to_jump_bots[client] == 1 && !bot_follow_tp[client])
    {
        //this check accomedates checking for if the bot actually was teleported by the map because then he should not teleport back again to coords_run_cmd
        //the famous voodoo incidents that people have seen.
        float distance_bot_current_position_last_position = get_power_distance(client, coords_run_cmd[client]);

        if (distance_bot_current_position_last_position > 500.0)
        {
            has_to_jump_bots[client] = 2;
            return Plugin_Continue;
        }

        //teleport back on ground so can jump
        TeleportEntity(client, coords_run_cmd[client], NULL_VECTOR, NULL_VECTOR);
        buttons |= IN_JUMP; //jump
        has_to_jump_bots[client] = 2;
    }
    if (GetEntityFlags(client) & FL_ONGROUND)
    {
        GetEntPropVector(client, Prop_Send, "m_vecOrigin", coords_run_cmd[client]);
        has_to_jump_bots[client] = 0;
    }

    return Plugin_Continue;
}

public void OnPluginEnd()
{
    if (g_hTimer_pressing != null)
    {
        delete g_hTimer_pressing;
    }
    if (g_hTimer_bot_connect != null)
    {
        delete g_hTimer_bot_connect;
    }
}
public void Event_RoundEnd(Handle event, const char[] name, bool dontBroadcast)
{
    for (int i = 1; i <= MaxClients; i++)
    {
        if (IsValidClient(i))
        {
            if (is_bot_player[i])
            {
                has_to_jump_bots[i] = 3;
            }
        }
    }
}

public void Event_RoundStart(Handle event, const char[] name, bool dontBroadcast)
{
    for (int i = 1; i <= MaxClients; i++)
    {
        if (IsValidClient(i))
        {
            if (is_bot_player[i])
            {
                target_friend[i] = -1;
                target_enemy[i] = -1;
                human_too_close[i] = false;
                bot_follow_tp[i] = false;
                targetfriend_prev_coords[i][0] = 0.0;
                targetfriend_prev_coords[i][1] = 0.0;
                targetfriend_prev_coords[i][2] = 0.0;
                GetEntPropVector(i, Prop_Send, "m_vecOrigin", coords_run_cmd[i]);
                has_to_jump_bots[i] = 0;
                for (int j = 1; j <= MaxClients; j++)
                {
                    if (IsValidClient(j) && j != i)
                    {
                        target_friend_afk_counter[i][j] = 0;
                    }
                }
            }
        }
    }
}

public void cmd_talk_help(int port, int client, char[] info)
{
	char msg[generic_length * 5];
	chat_cooldown = true;
	char magic_code[16];
	Format(magic_code, sizeof(magic_code), autism_bot_code); //included with unloze.secrets simply.
	Format(msg, sizeof(msg), "clientmessage:%N %s %s", client, magic_code, info);
	send_socket_msg(msg, strlen(msg), port);
	CreateTimer(2.0, bot_chat_cooldown);

}

public void is_autism_bot1(int client)
{
    char auth[50];
    GetClientAuthId(client, AuthId_Engine, auth, sizeof(auth));
    if (StrEqual("[U:1:120378081]", auth, false) || StrEqual("STEAM_0:1:60189040", auth, false))
    {
        is_bot_player[client] = true;
        specific_bot_player[client] = 1;
    }
}

public void is_autism_bot2(int client)
{
    char auth[50];
    GetClientAuthId(client, AuthId_Engine, auth, sizeof(auth));
    if (StrEqual("[U:1:1036189204]", auth, false) || StrEqual("STEAM_0:0:518094602", auth, false))
    {
        is_bot_player[client] = true;
        specific_bot_player[client] = 2;
    }
}

public void is_autism_bot3(int client)
{
    char auth[50];
    GetClientAuthId(client, AuthId_Engine, auth, sizeof(auth));
    if (StrEqual("[U:1:408797742]", auth, false) || StrEqual("STEAM_0:0:204398871", auth, false))
    {
        is_bot_player[client] = true;
        specific_bot_player[client] = 3;
    }
}

public void is_autism_bot4(int client)
{
    char auth[50];
    GetClientAuthId(client, AuthId_Engine, auth, sizeof(auth));
    if (StrEqual("[U:1:1221121532]", auth, false) || StrEqual("STEAM_0:0:610560766", auth, false))
    {
        is_bot_player[client] = true;
        specific_bot_player[client] = 4;
    }
}

public Action cmd_botrtv(int client, int args)
{
    if ((CheckCommandAccess(client, "sm_kick", ADMFLAG_KICK)) || (CheckCommandAccess(client, "sm_reserved", ADMFLAG_RESERVATION)))
    {
		for (int i = 1; i <= MaxClients; i++)
		{
			if (IsValidClient(i) && !IsFakeClient(i))
			{
				char msg[generic_length];
				Format(msg, sizeof(msg), "rtv");
				if (specific_bot_player[i] == 1)
				{
					send_socket_msg(msg, strlen(msg), ports[0]);
				}
				else if (specific_bot_player[i] == 2)
				{
					send_socket_msg(msg, strlen(msg), ports[1]);
				}
				else if (specific_bot_player[i] == 3)
				{
					send_socket_msg(msg, strlen(msg), ports[2]);
				}
				else if(specific_bot_player[i] == 4)
				{
					send_socket_msg(msg, strlen(msg), ports[3]);
				}
			}
		}
    }
    return Plugin_Handled;
}

public Action cmd_talk(int client, int args)
{
    char info[generic_length];
    GetCmdArgString(info, sizeof(info));

    if (strlen(info) == 0)
    {
        PrintToChat(client, "Add a message to the command if autism bot is ingame and running on discord");
        return Plugin_Handled;
    }
    if (is_bot_player[client])
    {
        return Plugin_Handled;
    }
    if (chat_cooldown)
    {
        PrintToChat(client, "spamming bot too much, applying cooldown");
        return Plugin_Handled;
    }
    bool bot_found = false;
    for (int i = 1; i <= MaxClients; i++)
    {
        if (IsValidClient(i) && !IsFakeClient(i))
        {
            if (specific_bot_player[i] == 1)
            {
                cmd_talk_help(ports[0], client, info);
                bot_found = true;
            }
            if (specific_bot_player[i] == 2)
            {
                cmd_talk_help(ports[1], client, info);
                bot_found = true;
            }
            if (specific_bot_player[i] == 3)
            {
                cmd_talk_help(ports[2], client, info);
                bot_found = true;
            }
            if (specific_bot_player[i] == 4)
            {
                cmd_talk_help(ports[3], client, info);
                bot_found = true;
            }
        }
    }
    if (!bot_found)
        PrintToChat(client, "bot not connected to server");
    return Plugin_Handled;
}

public Action bot_chat_cooldown(Handle timer, any data)
{
	chat_cooldown = false;
	return Plugin_Continue;
}

public void OnMapStart()
{
	chat_cooldown = false;
}

public void send_socket_msg(char[] query_msg, int len, int port)
{
	//LogMessage("query_msg: %s port: %i", query_msg, port);
	if (global_socket != INVALID_HANDLE && SocketIsConnected(global_socket))
		SocketSendTo(global_socket, query_msg, len, "127.0.0.1", port); //udp
}

public Action bot_check_connect(Handle timer, any data)
{
    //this is designed for being ran from several css servers
    int client_count = GetClientCount(false);
    char msg[generic_length];
    int i_port = GetConVarInt(FindConVar("hostport"));
    bool bot1_connected = false;
    bool bot2_connected = false;
    bool bot3_connected = false;
    bool bot4_connected = false;
    for (int i = 1; i <= MaxClients; i++)
        if (IsValidClient(i) && !IsFakeClient(i))
        {
            if (specific_bot_player[i] == 1)
                bot1_connected = true;
            if (specific_bot_player[i] == 2)
                bot2_connected = true;
            if (specific_bot_player[i] == 3)
                bot3_connected = true;
            if (specific_bot_player[i] == 4)
                bot4_connected = true;
        }

    if (i_port == server_ports[0])
    {
        int max_allowed_players = 55;
        char current_map[128];
        GetCurrentMap(current_map, sizeof(current_map));
        if (client_count > max_allowed_players || StrEqual(current_map, "ze_paranoid_rezurrection_v11_9", false))
        {
            Format(msg, sizeof(msg), "connect to ze2");
            send_socket_msg(msg, strlen(msg), ports[0]);
            send_socket_msg(msg, strlen(msg), ports[1]);
            send_socket_msg(msg, strlen(msg), ports[2]);
            send_socket_msg(msg, strlen(msg), ports[3]);
        }
        else if (client_count < max_allowed_players - 5)
        {
            Format(msg, sizeof(msg), "connect to ze");
            if (!bot1_connected)
                send_socket_msg(msg, strlen(msg), ports[0]);
            if (!bot2_connected)
                send_socket_msg(msg, strlen(msg), ports[1]);
            if (!bot3_connected)
                send_socket_msg(msg, strlen(msg), ports[2]);
            if (!bot4_connected)
                send_socket_msg(msg, strlen(msg), ports[3]);
        }
    }
    else
    {
        //in case the bot is not connected to ze2 anymore make sure that is_bot_connected_to_ze2 is set to false again
        Format(msg, sizeof(msg), "not connected to ze2");
        if (!bot1_connected)
            send_socket_msg(msg, strlen(msg), ports[0]);
        if (!bot2_connected)
            send_socket_msg(msg, strlen(msg), ports[1]);
        if (!bot3_connected)
            send_socket_msg(msg, strlen(msg), ports[2]);
        if (!bot4_connected)
            send_socket_msg(msg, strlen(msg), ports[3]);
    }
    return Plugin_Continue;
}

public Action recursive_pressing(Handle timer, any data)
{
    bool found_valid_ct = false;
    int valid_ct_counter = 0;
    bool found_enemy = false;
    for (int client = 1; client <= MaxClients; client++)
    {
        if (!IsValidClient(client)) continue;
        if (GetClientTeam(client) == 2 && IsPlayerAlive(client))
        {
            found_enemy = true;
        }
        if (!is_bot_player[client] && GetClientTeam(client) > 1)
        {
            valid_ct_counter++;
            if (valid_ct_counter >= 3) //autismbots first join when there are three active players.
            {
                found_valid_ct = true;
            }
        }
    }
    for (int client = 1; client <= MaxClients; client++)
    {
        if (!IsValidClient(client)) continue;
        if (!found_valid_ct && is_bot_player[client])
        {
            if (GetClientTeam(client) != 0)
                ChangeClientTeam(client, 0);
            continue;
        }
        if (is_bot_player[client])
        {
            if (GetClientTeam(client) == 1 || GetClientTeam(client) == 0)
            {
                ChangeClientTeam(client, 2);
                continue;
            }
        }
        if (is_bot_player[client])
        {
            if (IsPlayerAlive(client))
            {
                int targeteam = 0;
                if (GetClientTeam(client) != 3)
                {
                    //2 = autismo is zm and should follow enemies sometimes
                    targeteam = 2;
                }
                else
                {
                    //3 = autismo is human and should follow closest moving ct
                    targeteam = 3;
                }
                int previous_friend = target_friend[client];
                target_enemy[client] = GetClosestClient_option1(false, client, 0);
                float enemy_distance = -1.0;
                float pos[3];
                if (IsValidClient(target_enemy[client]))
                {
                    GetEntPropVector(target_enemy[client], Prop_Send, "m_vecOrigin", pos);
                    enemy_distance = get_power_distance(client, pos);
                }

                target_friend[client] = GetClosestClient_option1(true, client, target_enemy[client]);
                float dist_target = -1.0;
                if (IsValidClient(target_friend[client]))
                {
                    GetEntPropVector(target_friend[client], Prop_Send, "m_vecOrigin", pos);
                    dist_target = get_power_distance(client, pos);
                }

                float max_range_dist = 65.0;
                //here we put into consideration if the human is leaving or standing around. If the bots already catched up to the human they can have a larger distance between the player and themselves so they dont constantly follow him. If the player is leaving the bots needs to follow him much closer.
                if (targeteam == 3 && human_too_close[client])
                {
                    max_range_dist = 200.0;
                }

                
                int state = -1;
                //Our previous friend is still an alive human, if we cant see him anymore he went around a corner or teleported
                if (targetfriend_prev_coords[client][0] != 0.0 && IsValidClient(previous_friend) && GetClientTeam(previous_friend) == GetClientTeam(client)
                    && IsPlayerAlive(previous_friend) && previous_friend != target_friend[client])
                {
                    float ClientPos[3];
                    float friend_prev[3];
                    GetClientEyePosition(client, ClientPos);
                    //if the bot cant see him anymore he around corner or teleported most likley.
                    //therefore a small distance check should also be needed to assure that the bots take the
                    //teleporter
                    float distance_prev_friend_prior = get_power_distance(previous_friend, targetfriend_prev_coords[client]);
                    GetEntPropVector(previous_friend, Prop_Send, "m_vecOrigin", friend_prev);
                    if (!IsPointVisible(ClientPos, friend_prev) && distance_prev_friend_prior > 500.0)
                    {
                        bot_follow_tp[client] = true;
                    }
                }
                if (IsValidClient(target_friend[client]) && !bot_follow_tp[client])
                {
                    GetEntPropVector(target_friend[client], Prop_Send, "m_vecOrigin", targetfriend_prev_coords[client]);
                }
                if (bot_follow_tp[client])
                {
                    float ClientPos[3];
                    float Result[3];
                    GetClientEyePosition(client, ClientPos);
                    MakeVectorFromPoints(ClientPos, targetfriend_prev_coords[client], Result);
                    GetVectorAngles(Result, Result);
                    float distance_between_bot_and_tp = get_power_distance(client, targetfriend_prev_coords[client]);
                    //when we are close enough to teleport just run forward without facing the tp
                    if (distance_between_bot_and_tp > 250.0)
                    {
                        TeleportEntity(client, NULL_VECTOR, Result, NULL_VECTOR);
                    }
                    if (!IsPointVisible(ClientPos, targetfriend_prev_coords[client]))
                    {
                        targetfriend_prev_coords[client][0] = 0.0;
                        targetfriend_prev_coords[client][1] = 0.0;
                        targetfriend_prev_coords[client][2] = 0.0;
                        bot_follow_tp[client] = false;
                    }
                    state = 8; //if its walking into a TP or up/down a ladder it matters to use +forward;
                }
                //if the bot or its target is on the ladder just use forward to reach the ladder
                else if (GetEntityMoveType(client) == MOVETYPE_LADDER || (IsValidClient(target_friend[client]) && GetEntityMoveType(target_friend[client]) == MOVETYPE_LADDER))
                {
                    state = 8;
                }
                //if bot is ct: is close enough to friend, can stop following friend and focus on shooting zombies
                else if (targeteam == 3 && 0 < dist_target < max_range_dist && IsValidClient(target_enemy[client]))
                {
                    human_too_close[client] = true;
                    state = 0;
                }
                //if bot is ct: if bot is not close enough to friend follow him, //dist_target being larger than max_range_dist means a friend was found to follow
                //we also check if zm already spawned, if not then stay close to friend
                else if ((targeteam == 3 && dist_target > max_range_dist) || !found_enemy)
                {
                    human_too_close[client] = false;
                    state = 1;
                }
                //if bot is ct and close enough to friend then just do nothing in case of there being no enemy to shoot at. 
                else if (targeteam == 3)
                {
                    human_too_close[client] = true;
                    state = 2;
                }
                //if bot is zm follow enemy sometimes
                else if (targeteam == 2 && 0 < enemy_distance < max_range_dist * 5 && IsValidClient(target_enemy[client]))
                {
                    state = 3;
                }
                //if bot is zm just follow the closest friend zm, but not constantly when too close because people start bitching then
                else if (targeteam == 2 && dist_target > max_range_dist / 2)
                {
                    state = 4;
                }
                //if bot is zm and no close enemies and no friends far away enough to follow then check if friend is close, if its the case then do nothing
                else if (targeteam == 2 && 0 < dist_target < max_range_dist / 2)
                {
                    state = 5;
                }
                //if bot is zm and there are no friends close or far away but there is also no enemy close check if there is an enemy far away
                else if (targeteam == 2 && IsValidClient(target_enemy[client]))
                {
                    state = 6;
                }
                //else nothing to do as zm
                else if (targeteam == 2)
                {
                    state = 7;
                }
                if (GetEntProp(client, Prop_Data, "m_nWaterLevel") == 0 && GetEntityMoveType(client) != MOVETYPE_LADDER)
                    trace_hulling_bot(client);
                char message[generic_length * 7];
                Format(message, sizeof(message), "dist_target: %f enemy_distance: %f targeteam: %i state: %i", dist_target, enemy_distance, targeteam, state);
                if (specific_bot_player[client] == 1)
                {
                    send_socket_msg(message, strlen(message), ports[0]);
                }
                if (specific_bot_player[client] == 2)
                {
                    send_socket_msg(message, strlen(message), ports[1]);
                }
                if (specific_bot_player[client] == 3)
                {
                    send_socket_msg(message, strlen(message), ports[2]);
                }
                if (specific_bot_player[client] == 4)
                {
                    send_socket_msg(message, strlen(message), ports[3]);
                }
            }
        }
    }
    found_valid_ct = false;
    for (int client = 1; client <= MaxClients; client++)
    {
        if (!IsValidClient(client)) continue;
        if (!is_bot_player[client] && GetClientTeam(client) == 3 && IsPlayerAlive(client))
        {
            found_valid_ct = true;
            break;
        }
    }
    if (!found_valid_ct)
    {
        for (int client = 1; client <= MaxClients; client++)
        {
            if (!IsValidClient(client)) continue;
            if (is_bot_player[client] && GetClientTeam(client) == 3)
            {
                ForcePlayerSuicide(client);
            }
        }
    }
    return Plugin_Continue;
}

//https://developer.valvesoftware.com/wiki/Dimensions
public void trace_hulling_bot(int client)
{
    char message[generic_length * 3];
    float step_cap = 18.0;
    float crouch_min = 48.0;
    float stand_min = 63.0;
    float feet_origin[3], mins[3], maxs[3], eye_position[3];
    int BOUNDINGBOX_INFLATION_OFFSET = 3;
    GetClientEyePosition(client, eye_position);
    GetClientAbsOrigin(client, feet_origin);
    GetClientMins(client, mins);
    GetClientMaxs(client, maxs);
    //increasing boxes sizes

    for (int ij = 0; ij < sizeof(mins) - 1; ij++)
    {
        mins[ij] -= BOUNDINGBOX_INFLATION_OFFSET;
        maxs[ij] += BOUNDINGBOX_INFLATION_OFFSET;
    }

    //acts as full body check
    feet_origin[2] += BOUNDINGBOX_INFLATION_OFFSET;
    TR_TraceHullFilter(feet_origin, feet_origin, mins, maxs, MASK_ALL, TraceRayDontHitSelf);
    if (TR_DidHit())
    {
        //check 0.0 to 48.0 units starting from feet
        feet_origin[2] -= BOUNDINGBOX_INFLATION_OFFSET;
        mins[2] = 0.0;
        maxs[2] = crouch_min;
        TR_TraceHullFilter(feet_origin, feet_origin, mins, maxs, MASK_ALL, TraceRayDontHitSelf);
        if (!(TR_DidHit()))
        {
            //can crouch
            Format(message, sizeof(message), "hull info:crouch");
            if (specific_bot_player[client] == 1)
            {
                send_socket_msg(message, strlen(message), ports[0]);
            }
            if (specific_bot_player[client] == 2)
            {
                send_socket_msg(message, strlen(message), ports[1]);
            }
            if (specific_bot_player[client] == 3)
            {
                send_socket_msg(message, strlen(message), ports[2]);
            }
            if (specific_bot_player[client] == 4)
            {
                send_socket_msg(message, strlen(message), ports[3]);
            }
            return;
        }
        //something blocks floor crouch
        eye_position[2] += 5.0;
        float new_mins[3], new_maxs[3], new_eyes[3];
        new_mins[0] = mins[0];
        new_mins[1] = mins[1];
        new_mins[2] = crouch_min + 16.0;
        new_maxs[0] = maxs[0];
        new_maxs[1] = maxs[1];
        new_maxs[2] = maxs[2] + 16.0;
        new_eyes[0] = eye_position[0];
        new_eyes[1] = eye_position[1];
        new_eyes[2] = eye_position[2] - 64.0;
        TR_TraceHullFilter(new_eyes, new_eyes, new_mins, new_maxs, MASK_ALL, TraceRayDontHitSelf);
        if (!(TR_DidHit()))
        {
            //should not block jump level
            Format(message, sizeof(message), "hull info:jump");
            if (specific_bot_player[client] == 1)
            {
                send_socket_msg(message, strlen(message), ports[0]);
            }
            if (specific_bot_player[client] == 2)
            {
                send_socket_msg(message, strlen(message), ports[1]);
            }
            if (specific_bot_player[client] == 3)
            {
                send_socket_msg(message, strlen(message), ports[2]);
            }
            if (specific_bot_player[client] == 4)
            {
                send_socket_msg(message, strlen(message), ports[3]);
            }
            return;
        }
        else
        {
            //still potentially crouch hole in wall or one side jumpable
            mins[2] = step_cap;
            float iterator = step_cap;
            while (iterator < stand_min)
            {
                maxs[2] = iterator;
                iterator += step_cap;
                for (int jj = 0; jj < 2; jj++)
                    for (int ij = 0; ij < 2; ij++)
                    {
                        if (jj == 0)
                            mins[ij] += BOUNDINGBOX_INFLATION_OFFSET;
                        else
                            maxs[ij] -= BOUNDINGBOX_INFLATION_OFFSET;

                        float eyes_down[3];
                        eyes_down[0] = eye_position[0];
                        eyes_down[1] = eye_position[1] + 10.0;
                        eyes_down[2] = eye_position[2];

                        float eyes_forward[3];
                        eyes_forward[0] = eye_position[0];
                        eyes_forward[1] = eye_position[1] + 10.0;
                        eyes_forward[2] = eye_position[2];

                        float maxs_new[3];
                        maxs_new[0] = maxs[0] - 8.0;
                        maxs_new[1] = maxs[1] - 8.0;
                        maxs_new[2] = maxs[2];

                        TR_TraceHullFilter(eye_position, eye_position, mins, maxs_new, MASK_ALL, TraceRayDontHitSelf);
                        if ((TR_DidHit()))
                        {
                            bool should_jump = true;
                            TR_TraceHullFilter(eyes_down, eyes_forward, mins, maxs, MASK_ALL, TraceRayDontHitSelf);
                            if (!(TR_DidHit()))
                            {
                                should_jump = false;
                            }

                            eyes_down[0] = eye_position[0] + 10.0;
                            eyes_forward[0] = eye_position[0] + 10.0;
                            TR_TraceHullFilter(eyes_down, eyes_forward, mins, maxs, MASK_ALL, TraceRayDontHitSelf);
                            if ((TR_DidHit()))
                            {
                                should_jump = false;
                            }

                            if (should_jump)
                            {
                                Format(message, sizeof(message), "hull info:jump");
                                if (specific_bot_player[client] == 1)
                                {
                                    send_socket_msg(message, strlen(message), ports[0]);
                                }
                                if (specific_bot_player[client] == 2)
                                {
                                    send_socket_msg(message, strlen(message), ports[1]);
                                }
                                if (specific_bot_player[client] == 3)
                                {
                                    send_socket_msg(message, strlen(message), ports[2]);
                                }
                                if (specific_bot_player[client] == 4)
                                {
                                    send_socket_msg(message, strlen(message), ports[3]);
                                }
                                return;
                            }
                        }

                        TR_TraceHullFilter(feet_origin, feet_origin, mins, maxs, MASK_ALL, TraceRayDontHitSelf);
                        if (!(TR_DidHit()))
                        {
                            //hit wall on one side, no need to jump just run forward
                            return;
                        }
                        if (jj == 0)
                            mins[ij] -= BOUNDINGBOX_INFLATION_OFFSET;
                        else
                            maxs[ij] += BOUNDINGBOX_INFLATION_OFFSET;
                    }
            }
            //currently detects when two walls meet and create a corner
            float move_angles[3];
            GetClientEyeAngles(client, move_angles);
            move_angles[0] = 0.0;
            move_angles[1] += 35.0;
            move_angles[2] = 0.0;
            TeleportEntity(client, NULL_VECTOR, move_angles, NULL_VECTOR);
            return;
        }
    }
}

public bool TraceRayDontHitSelf(int entity, int mask, any data)
{
	return entity != data && !(0 < entity <= MaxClients);
}

stock bool IsValidClient(int client)
{
	if (client > 0 && client <= MaxClients && IsClientConnected(client) && IsClientInGame(client))
		return true;
	return false;
}

stock bool is_client_stuck_or_afk(int client, int i)
{
    //triggers between 6-8 times per second
    target_friend_afk_counter[client][i]++;
    int human_idle_steps = 12;
    if (target_friend_afk_counter[client][i] >= human_idle_steps)
    {
        float min_distance_cap = 1.0;
        float i_own_distance = get_power_distance(i, client_old_coords[i]);
        GetEntPropVector(i, Prop_Send, "m_vecOrigin", client_old_coords[i]);
        bool not_moved = i_own_distance < min_distance_cap;
        if (!not_moved)
        {
            target_friend_afk_counter[client][i] = 0;
        }
        return not_moved;
    }
    return false;
}

public int GetClosestClient_option1(bool finding_friend, int client, int enemy)
{
    float nearestdistance = -1.0;
    int nearest = -1;
    for (int i = 1; i <= MaxClients; i++)
    if (IsValidClient(i) && IsPlayerAlive(i) && i != client)
    {
        if (!IsAbleToSee(client, i) || is_client_stuck_or_afk(client, i))
        {
            continue; 
        }
        float pos[3];
        GetEntPropVector(i, Prop_Send, "m_vecOrigin", pos);
        float dist_target = get_power_distance(client, pos);
        if ((finding_friend && GetClientTeam(i) != GetClientTeam(client)) || (!finding_friend && GetClientTeam(i) == GetClientTeam(client)))
        {
            continue;
        }

        if (GetClientTeam(client) == 3 && IsValidClient(enemy))
        {
            float enemy_pos[3];
            GetEntPropVector(enemy, Prop_Send, "m_vecOrigin", enemy_pos);
            float enemy_distance = get_power_distance(i, enemy_pos);
            //this should later be an issue at parts where zombies are on all sides around one. having to run close by them.
            if (enemy_distance <= 800.0)
            {
                dist_target = 8000000.0;
            }
        }
        if (is_bot_player[i])
        {
            //just making sure bots have lowest priority
            dist_target = 9000000.0;
        }
        if (nearestdistance < 0 || dist_target < nearestdistance)
        {
            nearest = i;
            nearestdistance = dist_target;
        }
    }
    return nearest;
}

public float get_power_distance(int target_player, float pos[3])
{
	float vec[3];
	GetClientAbsOrigin(target_player, vec);
	return GetVectorDistance(vec, pos);
}

public void OnClientPostAdminCheck(int client)
{
    is_bot_player[client] = false;
    specific_bot_player[client] = 0;
    is_autism_bot1(client);
    is_autism_bot2(client);
    is_autism_bot3(client);
    is_autism_bot4(client);
    for (int i = 1; i <= MaxClients; i++)
    {
        if (!IsValidClient(i)) continue;

        if (is_bot_player[i] && i != client)
        {
            target_friend_afk_counter[i][client] = 0;
        }
    }
    if (is_bot_player[client])
    {
        if (g_hGetRunCmdPre != INVALID_HANDLE)
            DHookEntity(g_hGetRunCmdPre, false, client);
    }
    coords_run_cmd[client][0] = 0.0;
    coords_run_cmd[client][1] = 0.0;
    coords_run_cmd[client][2] = 0.0;
    has_to_jump_bots[client] = 0;
    //checks if ze or ze2
    int i_port = GetConVarInt(FindConVar("hostport"));
    char msg[generic_length];
    if (i_port == server_ports[0])
    {
        Format(msg, sizeof(msg), "autismo connected to ze");
    }
    else
    {
        Format(msg, sizeof(msg), "autismo connected to ze2");
    }
    targetfriend_prev_coords[client][0] = 0.0;
    targetfriend_prev_coords[client][1] = 0.0;
    targetfriend_prev_coords[client][2] = 0.0;
    human_too_close[client] = false;
    bot_follow_tp[client] = false;
    char auth[64];
    GetClientAuthId(client, AuthId_Engine, auth, sizeof(auth));
    if (specific_bot_player[client] == 1)
    {
        send_socket_msg(msg, strlen(msg), ports[0]);
    }
    if (specific_bot_player[client] == 2)
    {
        send_socket_msg(msg, strlen(msg), ports[1]);
    }
    if (specific_bot_player[client] == 3)
    {
        send_socket_msg(msg, strlen(msg), ports[2]);
    }
    if (specific_bot_player[client] == 4)
    {
        send_socket_msg(msg, strlen(msg), ports[3]);
    }
    client_old_coords[client][0] = 0.0;
    client_old_coords[client][1] = 0.0;
    client_old_coords[client][2] = 0.0;
}

public void OnSocketError(Handle socket, const int errorType, const int errorNum, any args)
{
	CloseHandle(socket);
	LogError("[MR] Socket error: %d (errno %d)", errorType, errorNum);
	CreateTimer(10.0, TimerConnect, INVALID_HANDLE, TIMER_HNDL_CLOSE);
}

stock void connect_socket()
{
    if (global_socket == INVALID_HANDLE || !SocketIsConnected(global_socket))
    {
        int i_port = GetConVarInt(FindConVar("hostport"));
        int target_port = ports[4]; //default ze
        if (i_port == server_ports[1])
        {
            //ze2
            target_port = ports[5];
        }
        //socket otherwise declare in public OnConfigsExecuted(){}
        global_socket = SocketCreate(SOCKET_UDP, OnSocketError);
        SocketSetOption(global_socket, SocketReuseAddr, 1);
        SocketBind(global_socket, "127.0.0.0", target_port);
        SocketConnect(global_socket, OnSocketConnected, OnSocketReceive, OnSocketDisconnected, "127.0.0.1", target_port); //48474 on ze
    }
}

public void OnClientDisconnect(int client)
{
    client_old_coords[client][0] = 0.0;
    client_old_coords[client][1] = 0.0;
    client_old_coords[client][2] = 0.0;
    coords_run_cmd[client][0] = 0.0;
    coords_run_cmd[client][1] = 0.0;
    coords_run_cmd[client][2] = 0.0;
    targetfriend_prev_coords[client][0] = 0.0;
    targetfriend_prev_coords[client][1] = 0.0;
    targetfriend_prev_coords[client][2] = 0.0;
    human_too_close[client] = false;
    bot_follow_tp[client] = false;
    has_to_jump_bots[client] = 0;
    for (int i = 1; i <= MaxClients; i++)
    {
        if (IsValidClient(i) && is_bot_player[i] && i != client)
        {
            target_friend_afk_counter[i][client] = 0;
        }
    }
    is_bot_player[client] = false;
    specific_bot_player[client] = 0;
}

//Socket callback
public void OnSocketConnected(Handle socket, int arg)
{
	
}

//manage message
public void OnSocketReceive(Handle socket, char receiveData [3500], int dataSize, int hFile)
{
	
}

public void OnSocketDisconnected(Handle socket, int arg)
{
    CreateTimer(10.0, TimerConnect, INVALID_HANDLE, TIMER_HNDL_CLOSE);
}

public Action TimerConnect(Handle timer, any arg)
{
    connect_socket();
    return Plugin_Handled;
}

public bool IsAbleToSee(int entity, int client)
{
	float vecorigin[3]; 
	float vecEyePos[3];
	GetClientAbsOrigin(entity, vecorigin);
	GetClientEyePosition(client, vecEyePos);
	// Check if centre is visible.
	if (IsPointVisible(vecEyePos, vecorigin))
		return true;
	float vecEyePos_ent[3]; 
	float vecEyeAng[3];
	GetClientEyeAngles(entity, vecEyeAng);
	GetClientEyePosition(entity, vecEyePos_ent);
	// Check if weapon tip is visible.
	if (IsFwdVecVisible(vecEyePos, vecEyeAng, vecEyePos_ent))
		return true;
	float mins[3]; 
	float maxs[3];
	GetClientMins(client, mins);
	GetClientMaxs(client, maxs);
	// Check outer 4 corners of player.
	if (IsRectangleVisible(vecEyePos, vecorigin, mins, maxs, 1.30)) //1.30
		return true;
	// Check inner 4 corners of player.
	if (IsRectangleVisible(vecEyePos, vecorigin, mins, maxs, 0.65)) //0.65
		return true;
	return false;
}

public bool Filter_NoPlayers(int entity, int mask)
{
    return (entity > MaxClients && !(0 < GetEntPropEnt(entity, Prop_Data, "m_hOwnerEntity") <= MaxClients));
}

public bool IsPointVisible(const float start[3], const float end[3])
{
    TR_TraceRayFilter(start, end, MASK_SOLID, RayType_EndPoint, Filter_NoPlayers);
    return TR_GetFraction() == 1.0;
}

public bool IsFwdVecVisible(const float start[3], const float angles[3], const float end[3])
{
	float fwd[3];
	GetAngleVectors(angles, fwd, NULL_VECTOR, NULL_VECTOR);
	ScaleVector(fwd, 50.0); //ScaleVector(fwd, 50.0);
	AddVectors(end, fwd, fwd);
	return IsPointVisible(start, fwd);
}

public bool IsRectangleVisible(const float start[3], const float end[3], const float mins[3], const float maxs[3], float scale)
{
    float ZpozOffset = maxs[2];
    float ZnegOffset = mins[2];
    float WideOffset = ((maxs[0] - mins[0]) + (maxs[1] - mins[1])) / 4.0;
    // This rectangle is just a point!
    if (ZpozOffset == 0.0 && ZnegOffset == 0.0 && WideOffset == 0.0)
        return IsPointVisible(start, end);
    // Adjust to scale.
    ZpozOffset *= scale;
    ZnegOffset *= scale;
    WideOffset *= scale;
    // Prepare rotation matrix.
    float angles[3]; 
    float fwd[3];
    float right[3];
    SubtractVectors(start, end, fwd);
    NormalizeVector(fwd, fwd);
    GetVectorAngles(fwd, angles);
    GetAngleVectors(angles, fwd, right, NULL_VECTOR);
    float vRectangle[4][3];
    float vTemp[3];
    // If the player is on the same level as us, we can optimize by only rotating on the z-axis.
    if (FloatAbs(fwd[2]) <= 0.7071)
    {
        ScaleVector(right, WideOffset);
        
        // Corner 1, 2
        vTemp = end;
        vTemp[2] += ZpozOffset;
        AddVectors(vTemp, right, vRectangle[0]);
        SubtractVectors(vTemp, right, vRectangle[1]);
        
        // Corner 3, 4
        vTemp = end;
        vTemp[2] += ZnegOffset;
        AddVectors(vTemp, right, vRectangle[2]);
        SubtractVectors(vTemp, right, vRectangle[3]);
        
    }
    else if (fwd[2] > 0.0) // Player is below us.
    {
        fwd[2] = 0.0;
        NormalizeVector(fwd, fwd);
        ScaleVector(fwd, scale);
        ScaleVector(fwd, WideOffset);
        ScaleVector(right, WideOffset);
        // Corner 1
        vTemp = end;
        vTemp[2] += ZpozOffset;
        AddVectors(vTemp, right, vTemp);
        SubtractVectors(vTemp, fwd, vRectangle[0]);
        // Corner 2
        vTemp = end;
        vTemp[2] += ZpozOffset;
        SubtractVectors(vTemp, right, vTemp);
        SubtractVectors(vTemp, fwd, vRectangle[1]);
        // Corner 3
        vTemp = end;
        vTemp[2] += ZnegOffset;
        AddVectors(vTemp, right, vTemp);
        AddVectors(vTemp, fwd, vRectangle[2]);
        // Corner 4
        vTemp = end;
        vTemp[2] += ZnegOffset;
        SubtractVectors(vTemp, right, vTemp);
        AddVectors(vTemp, fwd, vRectangle[3]);
    }
    else // Player is above us.
    {
        fwd[2] = 0.0;
        NormalizeVector(fwd, fwd);
        ScaleVector(fwd, scale);
        ScaleVector(fwd, WideOffset);
        ScaleVector(right, WideOffset);
        // Corner 1
        vTemp = end;
        vTemp[2] += ZpozOffset;
        AddVectors(vTemp, right, vTemp);
        AddVectors(vTemp, fwd, vRectangle[0]);
        // Corner 2
        vTemp = end;
        vTemp[2] += ZpozOffset;
        SubtractVectors(vTemp, right, vTemp);
        AddVectors(vTemp, fwd, vRectangle[1]);
        // Corner 3
        vTemp = end;
        vTemp[2] += ZnegOffset;
        AddVectors(vTemp, right, vTemp);
        SubtractVectors(vTemp, fwd, vRectangle[2]);
        // Corner 4
        vTemp = end;
        vTemp[2] += ZnegOffset;
        SubtractVectors(vTemp, right, vTemp);
        SubtractVectors(vTemp, fwd, vRectangle[3]);
    }
    // Run traces on all corners.
    for (int i = 0; i < 4; i++)
        if (IsPointVisible(start, vRectangle[i]))
            return true;
    return false;
}
