#include <sourcemod>
#include <sdktools>
#include <unloze_zones>
#include <sdkhooks>
#include <cstrike>
#include <PlayerRankings>
#include <hlstatsx_loghelper>
#include <mapchooser_extended>
#define MAXZONES 66
#define ZONENAMELENGTH 256
int g_bBlockRespawn[MAXPLAYERS+1];
char g_cZones[MAXZONES][ZONENAMELENGTH];
int g_cZones_CT_count[MAXZONES];
int g_cZones_ZM_count[MAXZONES];
int g_client_in_zone[MAXPLAYERS + 1];
int g_iCT_Damage_in_zone[MAXPLAYERS + 1];
int g_iZone_fought_or_ct_controlled[MAXZONES];
int g_iLast_Client_In_Zone[MAXPLAYERS + 1];
int ping_ents[MAXPLAYERS + 1];
int g_iAnnounce_zone_controll = 0;
bool g_permit_zone_benefits = false;

bool g_is_zh_map = false;

bool round_start_delay;

bool ran_out_of_time = false;

//spawning the markers
float ping_coorindates[MAXZONES][3];
int zone_pings[MAXZONES];

static char g_cConfigZones[PLATFORM_MAX_PATH];
int g_zoneCount = 0;
int client_target[MAXPLAYERS + 1];
float g_fStartPos[MAXZONES][3];
float g_fEndPos[MAXZONES][3];
float g_human_damage_addition;
ConVar g_hRespawnTreshold;
ConVar g_hHealthRegenZM;
ConVar g_hHealthRegenAmount;
Handle hText;
Handle g_hZMZoneTimer = null;
Handle g_hZoneCounter = null;
Handle g_hZoneBenefits = null;

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public Plugin myinfo =
{
	name = "zombie hunting plugin",
	author = "jenz",
	description = "Disables respawning on zombie hunting maps after some deaths, adds zoning",
	version = "2.0.0",
	url = "www.unloze.com"
};

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void OnPluginStart()
{
    OnMapStart();
    g_hRespawnTreshold = CreateConVar("zh_respawn_count", "5.0", "zombie hunting respawn count", 0, true, 0.0, true, 100.0);
    g_hHealthRegenZM = CreateConVar("zh_regenMaxHealth", "2000", "zombie hunting zombie max health for regen", 0, true, 0.0, true, 5000.0);
    g_hHealthRegenAmount = CreateConVar("zh_regenAmount", "100", "zombie hunting zombie max health for regen", 0, true, 0.0, true, 5000.0);
    for (int client; client < MaxClients; client++)
        g_bBlockRespawn[client] = 0;
    
    ReadZoneFile();

    HookEvent("round_start", OnRoundStart);
    HookEvent("round_end", OnRoundEnd, EventHookMode_Pre);
    HookEvent("player_death", OnClientDeath);
    hText = CreateHudSynchronizer();

    round_start_delay = true;
    //timer for ZM zone benefits
    g_hZMZoneTimer = CreateTimer(5.0, give_zm_zone_boosts, _, TIMER_REPEAT);
    g_hZoneCounter = CreateTimer(5.0, announce_zone_controlls, _, TIMER_REPEAT);
}

public Action announce_zone_controlls(Handle hTimer)
{
    if (g_is_zh_map)
    {
        float fought_zone = 0.0;
        float ct_controlled_zone = 0.0;
        float zm_controlled_zone = 0.0;
        if (g_zoneCount == 0 || !g_permit_zone_benefits)
        {
            g_human_damage_addition = 1.0; //indicating no damage buffs
            for (int i = 0; i < MaxClients; i++)
            {
                if (IsValidClient(i) && IsPlayerAlive(i))
                {
                    SetEntityGravity(i, 1.0);
                }
            }
            return Plugin_Handled;
        }
        for (int i = 0; i < g_zoneCount; i++)
        {
            if (g_iZone_fought_or_ct_controlled[i] == 1) //fought over zone
            {
                fought_zone++;
            }
            if (g_iZone_fought_or_ct_controlled[i] == 2) //CT zone
            {
                ct_controlled_zone++;
            }
            if (g_iZone_fought_or_ct_controlled[i] == 3) //ZM zone
            {
                zm_controlled_zone++;
            }
        }
        g_iAnnounce_zone_controll++;
        int ct_control_percentage = RoundToFloor((ct_controlled_zone/ g_zoneCount) * 100);
        int zm_control_percentage = RoundToFloor((zm_controlled_zone/ g_zoneCount) * 100);
        int fought_percentage = RoundToFloor((fought_zone/ g_zoneCount) * 100);
        if (g_iAnnounce_zone_controll >= 3)
        {
            PrintToChatAll("CT controll %i%s of zones. ZM controll %i%s of zones. %i%s of zones are fought over", 
            ct_control_percentage, "%", zm_control_percentage, "%", fought_percentage, "%");
        }

        if (zm_control_percentage > 70)
        {
            for (int i = 0; i < MaxClients; i++)
            {
                if (IsValidClient(i) && IsPlayerAlive(i))
                {
                    if (GetClientTeam(i) == CS_TEAM_T)
                    {
                        SetEntityGravity(i, 0.7);
                    }
                    else
                    {
                        SetEntityGravity(i, 1.0);
                    }
                }
            }
            if (g_iAnnounce_zone_controll >= 3)
            {
                PrintToChatAll("ZM controll over 70%s of zones. Applying 30%s low grav", "%", "%");
            }
        }
        else
        {
            for (int i = 0; i < MaxClients; i++)
            {
                if (IsValidClient(i) && IsPlayerAlive(i))
                {
                    SetEntityGravity(i, 1.0);
                }
            }
        }

        if (ct_control_percentage > 90)
        {
            g_human_damage_addition = 0.6;
            if (g_iAnnounce_zone_controll >= 3)
            {
                PrintToChatAll("CT controll over 90%s of zones. Applying 60%s extra damage", "%", "%");
            }
        }
        else if (ct_control_percentage > 70)
        {
            g_human_damage_addition = 0.40;
            if (g_iAnnounce_zone_controll >= 3)
            {
                PrintToChatAll("CT controll over 70%s of zones. Applying 40%s extra damage", "%", "%");
            }
        }
        else if (ct_control_percentage > 50)
        {
            g_human_damage_addition = 0.2;
            if (g_iAnnounce_zone_controll >= 3)
            {
                PrintToChatAll("CT controll over 50%s of zones. Applying 20%s extra damage", "%", "%");
            }
        }
        else
        {
            g_human_damage_addition = 1.0; //indicating no damage buffs
        }
        if (g_iAnnounce_zone_controll >= 3)
        {
            g_iAnnounce_zone_controll = 0;
        }
    }
    return Plugin_Handled;
}

public void OnClientPutInServer(int client)
{
    if (g_is_zh_map)
    {
        SDKHook(client, SDKHook_OnTakeDamage, OnTakeDamage);
    }
}

public Action OnTakeDamage(int client, int &attacker, int &inflictor, float &damage, int &damagetype) 
{
    if (g_is_zh_map)
    {
        if (IsValidClient(attacker) && GetClientTeam(attacker) == CS_TEAM_CT && g_human_damage_addition != 1.0)
        {
            damage += (g_human_damage_addition * damage);
            return Plugin_Changed;
        }
    }

    return Plugin_Continue;
}

public Action give_zm_zone_boosts(Handle hTimer)
{
    if (!g_is_zh_map)
    {
        return Plugin_Handled;
    }

    for (int client; client < MaxClients; client++)
    {
        if (IsValidClient(client) && IsPlayerAlive(client) && GetClientTeam(client) == CS_TEAM_T)
        {
            int index = g_client_in_zone[client];
            //the player is zm, inside a zone and the zone has no humans present, so its a zm zone where he should get extra healed
            if (index != -1 && g_cZones_CT_count[index] == 0)
            {
                //giving zm inside of zm controlled zone extra health regeneration
                int client_health = GetEntData(client, FindDataMapInfo(client, "m_iHealth"));
                if (client_health < g_hHealthRegenZM.IntValue)
                {
                    SetEntProp(client, Prop_Send, "m_iHealth", client_health + g_hHealthRegenAmount.IntValue, 1);
                }
            }
        }
    }
    return Plugin_Handled;
}

public void OnPluginEnd()
{
    CloseHandle(hText);
    if (g_hZMZoneTimer != null)
        delete g_hZMZoneTimer;
    if (g_hZoneCounter != null)
        delete g_hZoneCounter;
    if (g_hZoneBenefits != null)
    {
        delete g_hZoneBenefits;
        g_hZoneBenefits = INVALID_HANDLE;
    }
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void OnClientDisconnect(int client)
{
    if (g_is_zh_map)
    {
        g_iCT_Damage_in_zone[client] = 0;
        client_target[client] = 0;
        g_client_in_zone[client] = -1;
        adjust_clients(); //calling GetClientTeam inside adjust_clients complaints that client already disconnected.
        g_bBlockRespawn[client] = 0;
        SDKUnhook(client, SDKHook_OnTakeDamage, OnTakeDamage);
    }
}

public void adjust_clients()
{
    if (!g_is_zh_map)
    {
        return;
    }
    for (int i = 0; i < MAXZONES; i++)
    {
        g_cZones_CT_count[i] = 0;
        g_cZones_ZM_count[i] = 0;
    }

    for (int i = 0; i <= g_zoneCount; i++)
    {
        for (int client; client < MaxClients; client++)
        {
            if (IsValidClient(client) && IsPlayerAlive(client) && g_client_in_zone[client] == i)
            {
                if (GetClientTeam(client) == CS_TEAM_CT)
                {
                    g_cZones_CT_count[i]++;
                }
                else
                {
                    g_cZones_ZM_count[i]++;
                }
            }
        }
    }
    UpdateMarkers();
}

public void OnRoundEnd(Event hEvent, const char[] sEvent, bool bDontBroadcast)
{
    if (g_is_zh_map)
    {
        if(g_hZoneBenefits != INVALID_HANDLE)
        {
            delete g_hZoneBenefits;
            g_hZoneBenefits = INVALID_HANDLE;
        }
        round_start_delay = true;
        bool found_alive_zm = false;
        for (int client = 0; client < MaxClients; client++)
        {
            if (IsValidClient(client) && GetClientTeam(client) == CS_TEAM_T && IsPlayerAlive(client))
            {
                found_alive_zm = true;
            }
        }

        if (found_alive_zm)
        {
            for (int client = 0; client < MaxClients; client++)
            {
                if (IsValidClient(client) && GetClientTeam(client) == CS_TEAM_CT)
                {
                    ForcePlayerSuicide(client);
                }
            }
            PrintToChatAll("Humans failed!");
            PrintToChatAll("Humans failed!");
            PrintToChatAll("Humans failed!");
        }
        for (int i = 0; i <= g_zoneCount; i++)
        {
            for (int j = zone_pings[i]; j < zone_pings[i + 1]; j++)
            {
                RemovePing(j);
            }
        }
    }
}

public void OnMapEnd()
{
    if (g_is_zh_map)
    {
        for (int i = 0; i <= g_zoneCount; i++)
        {
            for (int j = zone_pings[i]; j < zone_pings[i + 1]; j++)
            {
                RemovePing(j);
                /* Should hopefully help prevent these crashes
                    L 11/26/2024 - 16:59:11: (70.62%) 1447        prop_dynamic
                    Nov 26 16:59:11 unloze script[1920069]: L 11/26/2024 - 16:59:11: Total edicts: 2049
                    Nov 26 16:59:11 unloze script[1920069]: ED_Alloc: no free edicts
                    Nov 26 16:59:11 unloze script[1920069]: L 11/26/2024 - 16:59:11: Engine error: ED_Alloc: no free edicts
                */
            }
        }
    }
}

public Action permit_zone_benefits(Handle hTimer)
{
    if (!g_is_zh_map)
    {
        return Plugin_Handled;
    }
    g_permit_zone_benefits = true;
    return Plugin_Handled;
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void OnRoundStart(Event hEvent, const char[] sEvent, bool bDontBroadcast)
{
    if (g_is_zh_map)
    {
        g_permit_zone_benefits = false;
        g_hZoneBenefits = CreateTimer(35.0, permit_zone_benefits);
        g_human_damage_addition = 1.0;
        for (int client = 0; client < MaxClients; client++)
        {
            g_bBlockRespawn[client] = 0;
            if (IsValidClient(client))
            {
                g_iCT_Damage_in_zone[client] = 0;
            }
            g_client_in_zone[client] = -1;
        }

        for (int i = 0; i < MAXZONES; i++)
        {
            g_cZones_CT_count[i] = 0;
            g_cZones_ZM_count[i] = 0;
            g_iZone_fought_or_ct_controlled[i] = -1;
            g_iLast_Client_In_Zone[i] = 0;
        }
        round_start_delay = true;
        CreateTimer(5.0, enable_pings);
    }
}

public Action enable_pings(Handle timer, any data)
{
    if (!g_is_zh_map)
    {
        return Plugin_Handled;
    }
    round_start_delay = false;
    return Plugin_Handled;
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void OnClientDeath(Event hEvent, const char[] sEvent, bool bDontBroadcast)
{
    if (g_is_zh_map)
    {
        int victim = GetClientOfUserId(hEvent.GetInt("userid"));
        g_client_in_zone[victim] = -1;
        adjust_clients();
        SetEntityRenderColor(victim, 255, 255, 255, 255);
        if (g_bBlockRespawn[victim] > g_hRespawnTreshold.IntValue)
            return;
        PrintToChat(victim, "\x04[ZR]\x01 You have %i respawns left for this round.", g_hRespawnTreshold.IntValue - g_bBlockRespawn[victim]);
        g_bBlockRespawn[victim]++;
    }
}

public void ZR_OnClientInfected(int client, int attacker, bool motherInfect, bool respawnOverride, bool respawn)
{
    if (g_is_zh_map)
    {
        int index = g_client_in_zone[client];
        if (index != -1)
        {
            //client got infected inside a specific zone, reduce human count inside that specific zone by 1
            if (g_cZones_CT_count[index] > 0)
            {
                g_cZones_CT_count[index]--;
            }
            g_cZones_ZM_count[index]++;
            UpdateMarkers();
        }
    }
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------

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

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

public Action ZR_OnClientRespawn(&client, &ZR_RespawnCondition:condition)
{
    if (g_is_zh_map)
    {
        if (g_bBlockRespawn[client] > g_hRespawnTreshold.IntValue)
            return Plugin_Handled;
        find_teleport_target(client);
    }
    return Plugin_Continue;
}

public void find_teleport_target(int client)
{
    if (!g_is_zh_map)
    {
        return;
    }
    //teleport player to team members with farthest distance to enemy just.
    //checking all alive clients to determine which client has highest dist_target to its closest enemy
    float total_furthest_distance = -1.0;
    int total_nearest = -1;
    for (int i = 1; i <= MaxClients; i++)
    {
        //if player would get stuck being teleported dont teleport them to this player
        if (!IsValidClient(i) || !IsPlayerAlive(i)) 
        {
            continue;
        }
        if (IsPlayerStuck(i) != -1)
        {
            continue;
        }
        float nearestdistance = -1.0;
        int nearest = -1;
        for (int j = 1; j <= MaxClients; j++)
        if (IsValidClient(j) && IsPlayerAlive(j) && GetClientTeam(i) != GetClientTeam(j) && GetClientTeam(i) == CS_TEAM_CT)
        {
            float pos[3];
            GetEntPropVector(i, Prop_Send, "m_vecOrigin", pos);
            float dist_target = get_power_distance(j, pos);
            if (nearestdistance < 0 || dist_target < nearestdistance)
            {
                nearest = i;
                nearestdistance = dist_target;
            }
        }
        //the closest enemy to this player is further away than previous players closest enemy
        if (nearestdistance > total_furthest_distance)
        {
            total_furthest_distance = nearestdistance;
            total_nearest = nearest;
        }
    }
    if (IsValidClient(total_nearest))
    {
        client_target[client] = total_nearest;
        CreateTimer(0.0, tp_client, client);
    }
}

stock int IsPlayerStuck(int client) {
    float vecMin[3]; 
    float vecMax[3]; 
    float vecOrigin[3];
    
    GetClientMins(client, vecMin);
    GetClientMaxs(client, vecMax);
    vecMax[2] = 63.0;
    vecMin[2] += 1.0;
    GetClientAbsOrigin(client, vecOrigin);
    
    TR_TraceHullFilter(vecOrigin, vecOrigin, vecMin, vecMax, MASK_PLAYERSOLID, TraceRayDontHitPlayerAndWorld);
    return TR_GetEntityIndex();
}

public bool TraceRayDontHitPlayerAndWorld(int entityhit, int mask) {
    return entityhit>MaxClients
}
public bool TraceRayHitOnlyEnt(int entityhit, int mask, any data) {
    return entityhit==data;
}

public void OnClientPostAdminCheck(int client)
{
    if (g_is_zh_map)
    {
        g_iCT_Damage_in_zone[client] = 0;
        client_target[client] = 0;
        g_client_in_zone[client] = -1;
    }
}

public Action tp_client(Handle timer, int client)
{
    if (!g_is_zh_map)
    {
        return Plugin_Handled;
    }
    if (IsValidClient(client) && IsValidClient(client_target[client]))
    {
        float posd[3];
        GetEntPropVector(client_target[client], Prop_Send, "m_vecOrigin", posd);
        TeleportEntity(client, posd, NULL_VECTOR, NULL_VECTOR);
        PrintToChat(client, "Respawned you at player: %N", client_target[client]);
        client_target[client] = 0;
    }
    return Plugin_Handled;
}

public void unloze_zoneLeave(int client, char[] zone)
{
    if (g_is_zh_map)
    {
        int index = g_client_in_zone[client];
        if (index < 0)
        {
            return;
        }

        g_client_in_zone[client] = -1;
        g_iCT_Damage_in_zone[client] = 0;
        if (GetClientTeam(client) == CS_TEAM_CT)
        {
            if (g_cZones_CT_count[index] > 0)
            {
                g_cZones_CT_count[index]--;
            }
        }
        else
        {
            if (g_cZones_ZM_count[index] > 0)
            {
                g_cZones_ZM_count[index]--;
            }
        }
        SetEntityRenderColor(client, 255, 255, 255, 255);
        UpdateMarkers();
    }
}

public void unloze_zoneCreated()
{
    if (g_is_zh_map)
    {
        ReadZoneFile();
    }
}

public Action retry_zoneEntry(Handle timer, DataPack data)
{
    if (!g_is_zh_map)
    {
        return Plugin_Handled;
    }
    ResetPack(data);
    char zone[256]; 
    data.ReadString(zone, sizeof(zone));
    int client_serial = data.ReadCell();
    delete data;

    int client;
    if ((client = GetClientFromSerial(client_serial)) != 0)
    {
        //by the time its recalled should the player have left the previous zone already
        unloze_zoneEntry(client, zone);
    }
    return Plugin_Handled;
}

public void unloze_zoneEntry(int client, char[] zone)
{
    if (g_is_zh_map)
    {
        int index = -1;
        //unfortunately it can happen that a zone is entered before the current one is left, which can lead to -1 index
        //when leaving the zone again. 
        //example: Enter new zone before old is left -> leave old zone -> index is -1 -> leave current zone -> leave forward is called before entering new zone is called -> 
        //tries to do -- on a array index where the index is -1
        if (g_client_in_zone[client] != -1)
        {
            //the previous zone has to be left before we recognize the client entering the new zone
            //only special cases are roundstart/respawning
            DataPack hDataPack = new DataPack();
            hDataPack.WriteString(zone);
            hDataPack.WriteCell(GetClientSerial(client));
            CreateTimer(0.2, retry_zoneEntry, hDataPack);
            return;
        }
        for (int i = 0; i < g_zoneCount; i++)
        {
            if (StrEqual(g_cZones[i], zone))
            {
                index = i;
                break;
            }
        }
        g_client_in_zone[client] = index;

        g_iCT_Damage_in_zone[client] = PlayerRankings_GetClientDamage(client);
        if (GetClientTeam(client) == CS_TEAM_CT)
        {
            g_cZones_CT_count[index]++;
        }
        else
        {
            g_cZones_ZM_count[index]++;
        }
        g_iLast_Client_In_Zone[index] = client;
        display_hud_text(index, client);
        UpdateMarkers();
    }
}



public void handle_pings(int i, int pingtype)
{
    if (!g_is_zh_map)
    {
        return;
    }
    if (round_start_delay)
    {
        return; //preventing pings from spawning exactly on roundStart as it might cause too many entities
    }
    for (int j = zone_pings[i]; j < zone_pings[i + 1]; j++)
    {
        //the ping is atm not the skull, will be changed to skull.
        RemovePing(j);
        if (!ran_out_of_time)
        {
            int timeleft = 0;
            GetMapTimeLeft(timeleft);
            if (timeleft <= 0)
            {
                ran_out_of_time = true;
            }
        }
        //these jewish tricks should stop the crashing when switching map because the prop_dynamics are not around anymore.
        //its not the last round because timeleft is not 0
        //there is also atm no nextmap set for the server through mapchooser. This confirms that the mapvote did not set it and an
        //admin also did not set it yet. 
        //just checking GetNextmap will not suffice because we always have a nextmap set.
        if (!HasEndOfMapVoteFinished() && !ran_out_of_time)
        {
            SpawnPing(pingtype, ping_coorindates[j], j);
            for (int client = 0; client < MaxClients; client++)
            {
                if (IsValidClient(client) && g_client_in_zone[client] == i)
                {
                    display_hud_text(i, client);
                }
            }
        }
    }
}

public void display_hud_text(int index, int client)
{
    if (!g_is_zh_map)
    {
        return;
    }
    if (hText != INVALID_HANDLE)
    {
        char msg[256];
        if (g_cZones_CT_count[index] > 0 && g_cZones_ZM_count[index] > 0)
        {
            Format(msg, sizeof(msg), "Contested zone");
            SetHudTextParams(0.35, 0.85, 2.5, 255, 255, 255, 85);
            ShowSyncHudText(client, hText, msg);
        }
        else if (g_cZones_CT_count[index] > 0)
        {
            Format(msg, sizeof(msg), "Human controlled zone");
            SetHudTextParams(0.35, 0.85, 2.5, 0, 0, 255, 85);
            ShowSyncHudText(client, hText, msg);
            SetEntityRenderColor(client, 0, 0, 255, 255);
        }
        else
        {
            Format(msg, sizeof(msg), "Zombie controlled zone");
            SetHudTextParams(0.35, 0.85, 2.5, 255, 0, 0, 85);
            ShowSyncHudText(client, hText, msg);
            SetEntityRenderColor(client, 255, 0, 0, 255);
        }
    }
}

//----------------------------------------------------------------------------------------------------
// Purpose: Reads from file
//----------------------------------------------------------------------------------------------------
public Action ReadZoneFile()
{
    if (g_is_zh_map)
    {
        int i_zoneTemp;
        char l_cMapName[MAXZONES];
        GetCurrentMap(l_cMapName, sizeof(l_cMapName));
        Handle l_hZoneFile = INVALID_HANDLE;
        char l_cLine[ZONENAMELENGTH];
        char sPart[4][32];
        BuildPath(Path_SM, g_cConfigZones, sizeof(g_cConfigZones), "configs/unloze_zones/%s.zones.txt", l_cMapName);
        l_hZoneFile = OpenFile(g_cConfigZones, "r");
        if (l_hZoneFile == INVALID_HANDLE)
        {
            Handle kv = CreateKeyValues("Zones");
            KeyValuesToFile(kv, g_cConfigZones);
            CloseHandle(kv);
            delete l_hZoneFile;
            return Plugin_Handled;
        }
        while (!IsEndOfFile(l_hZoneFile) && ReadFileLine(l_hZoneFile, l_cLine, sizeof(l_cLine)))
        {
            if (StrContains(l_cLine, "name", false) > -1)
            {
                ReplaceStrings(l_cLine, "name");
                Format(g_cZones[i_zoneTemp], sizeof(g_cZones), l_cLine);
            }
            if (StrContains(l_cLine, "cordinate_a", false) >= 0)
            {
                ReplaceString(l_cLine, sizeof(l_cLine), "\"", "", true);
                ReplaceString(l_cLine, sizeof(l_cLine), "cordinate_a", "", true);
                ExplodeString(l_cLine, " ", sPart, 4, 32);
                g_fStartPos[i_zoneTemp][0] = StringToFloat(sPart[0]);//reading first vector
                g_fStartPos[i_zoneTemp][1] = StringToFloat(sPart[1]);//reading second vector
                g_fStartPos[i_zoneTemp][2] = StringToFloat(sPart[2]);//reading third vector
            }
            if (StrContains(l_cLine, "cordinate_b", false) >= 0)
            {
                ReplaceString(l_cLine, sizeof(l_cLine), "\"", "", true);
                ReplaceString(l_cLine, sizeof(l_cLine), "cordinate_b", "", true);
                ExplodeString(l_cLine, " ", sPart, 4, 32);
                g_fEndPos[i_zoneTemp][0] = StringToFloat(sPart[0]);
                g_fEndPos[i_zoneTemp][1] = StringToFloat(sPart[1]);
                g_fEndPos[i_zoneTemp][2] = StringToFloat(sPart[2]);
                i_zoneTemp++;
            }
        }
        g_zoneCount = i_zoneTemp;
        delete l_hZoneFile;
    }
    return Plugin_Handled;
}

public void OnMapStart()
{
    ran_out_of_time = false;
    g_is_zh_map = false;
    char sConfigFile[PLATFORM_MAX_PATH];
    BuildPath(Path_SM, sConfigFile, sizeof(sConfigFile), "configs/zh_maps.cfg");
    if(!FileExists(sConfigFile))
    {
        LogMessage("Could not find config: \"%s\"", sConfigFile);
        return;
    }
    char map[PLATFORM_MAX_PATH];
    GetCurrentMap(map, PLATFORM_MAX_PATH);

    new Handle:fileHandle = OpenFile(sConfigFile, "r" );
    char lineBuffer[256];
    while( !IsEndOfFile( fileHandle ) && ReadFileLine( fileHandle, lineBuffer, sizeof( lineBuffer ) ) )
    {
        TrimString( lineBuffer );
        if (StrEqual(lineBuffer, map, false))
        {
            g_is_zh_map = true;
            break;
        }
    }
    CloseHandle( fileHandle );

    if (!g_is_zh_map)
    {
        return;
    }
    ReadZoneFile();
    

    g_human_damage_addition = 1.0;
    //resetting coordinates and setup.
    for (int i = 0; i < MAXZONES; i++)
    {
        ping_coorindates[i][0] = 0.0;
        ping_coorindates[i][1] = 0.0;
        ping_coorindates[i][2] = 0.0;
        zone_pings[i] = 0;
    }

    char g_cMapname[ZONENAMELENGTH];
    GetCurrentMap(g_cMapname, sizeof(g_cMapname));
    if (StrEqual(g_cMapname, "zm_cbble_b3", false))
    {
        ping_coorindates[0][0] = -1321.18;
        ping_coorindates[0][1] = 635.93;
        ping_coorindates[0][2] = 15.00;
        ping_coorindates[1][0] = -1228.51;
        ping_coorindates[1][1] = 1097.70;
        ping_coorindates[1][2] = 15.00;
        zone_pings[1] = 2; //first zone thats iterated.

        ping_coorindates[2][0] = -1313.29;
        ping_coorindates[2][1] = 481.83;
        ping_coorindates[2][2] = 15.00;
        ping_coorindates[3][0] = -1026.88;
        ping_coorindates[3][1] = -543.23;
        ping_coorindates[3][2] = 15.00 + 48.0; //higher terrain.
        zone_pings[2] = 4; //second zone thats iterated, and so on and so on.

        ping_coorindates[4][0] = -857.17;
        ping_coorindates[4][1] = -549.33;
        ping_coorindates[4][2] = 15.00 + 48.0;
        ping_coorindates[5][0] = -319.23;
        ping_coorindates[5][1] = -1091.23;
        ping_coorindates[5][2] = 15.00 + 48.0; //higher terrain.
        zone_pings[3] = 6; 

        ping_coorindates[6][0] = -178.41;
        ping_coorindates[6][1] = -1096.69;
        ping_coorindates[6][2] = 15.00 + 48.0;
        ping_coorindates[7][0] = 135.89;
        ping_coorindates[7][1] = 262.61;
        ping_coorindates[7][2] = 15.00 + 48.0; //higher terrain.
        zone_pings[4] = 8; 

        ping_coorindates[8][0] = 149.52;
        ping_coorindates[8][1] = 477.08;
        ping_coorindates[8][2] = 15.00 + 48.0;
        ping_coorindates[9][0] = 144.99;
        ping_coorindates[9][1] = 927.54;
        ping_coorindates[9][2] = 15.00 + 48.0; //higher terrain.
        zone_pings[5] = 10; 

        ping_coorindates[10][0] = 146.94;
        ping_coorindates[10][1] = 1083.88;
        ping_coorindates[10][2] = 15.00 + 48.0;
        ping_coorindates[11][0] = -1074.73;
        ping_coorindates[11][1] = 1087.19;
        ping_coorindates[11][2] = 15.00;
        zone_pings[6] = 12; 
    }
    else if (StrEqual(g_cMapname, "cs_office", false))
    {
        //not the actual values atm
        ping_coorindates[0][0] = 1454.83;
        ping_coorindates[0][1] = 585.43;
        ping_coorindates[0][2] = -160.96; //-95.00
        zone_pings[1] = 1;

        ping_coorindates[1][0] = 1405.29;
        ping_coorindates[1][1] = 927.91;
        ping_coorindates[1][2] = -160.96; //-95.00
        ping_coorindates[2][0] = 890.47;
        ping_coorindates[2][1] = 953.11;
        ping_coorindates[2][2] = -160.96; //-95.00
        zone_pings[2] = 3;

        ping_coorindates[3][0] = 710.38;
        ping_coorindates[3][1] = 958.05;
        ping_coorindates[3][2] = -160.96; //-95.00
        ping_coorindates[4][0] = 661.59;
        ping_coorindates[4][1] = 117.65;
        ping_coorindates[4][2] = -160.96; //-95.00
        zone_pings[3] = 5;

        ping_coorindates[5][0] = 108.23;
        ping_coorindates[5][1] = 92.75;
        ping_coorindates[5][2] = -160.96; //-95.00
        ping_coorindates[6][0] = 96.99;
        ping_coorindates[6][1] = -330.89;
        ping_coorindates[6][2] = -160.96; //-95.00
        zone_pings[4] = 7;

        ping_coorindates[7][0] = -224.44;
        ping_coorindates[7][1] = -499.20;
        ping_coorindates[7][2] = -160.96; //-95.00
        ping_coorindates[8][0] = 96.99;
        ping_coorindates[8][1] = -501.89;
        ping_coorindates[8][2] = -160.96; //-95.00
        ping_coorindates[9][0] = 670.37;
        ping_coorindates[9][1] = -512.63;
        ping_coorindates[9][2] = -160.96; //-95.00
        ping_coorindates[10][0] = 957.84;
        ping_coorindates[10][1] = -506.23;
        ping_coorindates[10][2] = -160.96; //-95.00
        zone_pings[5] = 11;

        ping_coorindates[11][0] = 673.70;
        ping_coorindates[11][1] = -212.05;
        ping_coorindates[11][2] = -160.96; //-95.00
        zone_pings[6] = 12;

        ping_coorindates[12][0] = 972.64;
        ping_coorindates[12][1] = -151.94;
        ping_coorindates[12][2] = -160.96; //-95.00
        ping_coorindates[13][0] = 972.64;
        ping_coorindates[13][1] = 98.05;
        ping_coorindates[13][2] = -160.96; //-95.00
        zone_pings[7] = 14;

        ping_coorindates[14][0] = 1215.49;
        ping_coorindates[14][1] = 526.93;
        ping_coorindates[14][2] = -160.96; //-95.00
        zone_pings[8] = 15;

        ping_coorindates[15][0] = 1374.12;
        ping_coorindates[15][1] = 171.48;
        ping_coorindates[15][2] = -160.96; //-95.00
        zone_pings[9] = 16;

        ping_coorindates[16][0] = 1775.58;
        ping_coorindates[16][1] = 151.76;
        ping_coorindates[16][2] = -160.96; //-95.00
        ping_coorindates[17][0] = 1769.61;
        ping_coorindates[17][1] = -462.40;
        ping_coorindates[17][2] = -160.96; //-95.00
        zone_pings[10] = 18;

        ping_coorindates[18][0] = 1461.20;
        ping_coorindates[18][1] = -480.59;
        ping_coorindates[18][2] = -160.96; //-95.00
        zone_pings[11] = 19;

        ping_coorindates[19][0] = 1388.60;
        ping_coorindates[19][1] = -178.14;
        ping_coorindates[19][2] = -160.96; //-95.00
        zone_pings[12] = 20;

        ping_coorindates[20][0] = 966.46;
        ping_coorindates[20][1] = -893.44;
        ping_coorindates[20][2] = -160.96; //-95.00
        zone_pings[13] = 21;

        ping_coorindates[21][0] = 945.39;
        ping_coorindates[21][1] = -1136.16;
        ping_coorindates[21][2] = -190.96; //-95.00
        ping_coorindates[22][0] = 716.53;
        ping_coorindates[22][1] = -1882.94;
        ping_coorindates[22][2] = -325.00; //-95.00
        ping_coorindates[23][0] = 645.86;
        ping_coorindates[23][1] = -1426.71;
        ping_coorindates[23][2] = -280.00; //-95.00
        zone_pings[14] = 24;

        ping_coorindates[24][0] = 426.91;
        ping_coorindates[24][1] = -891.43;
        ping_coorindates[24][2] = -225.00; //-95.00
        zone_pings[15] = 25;

        ping_coorindates[25][0] = 421.71;
        ping_coorindates[25][1] = -1408.24;
        ping_coorindates[25][2] = -280.00; //-95.00
        zone_pings[16] = 26;

        ping_coorindates[26][0] = -1211.93;
        ping_coorindates[26][1] = -1842.44;
        ping_coorindates[26][2] = -335.00; //-95.00
        ping_coorindates[27][0] = 401.20;
        ping_coorindates[27][1] = -1872.70;
        ping_coorindates[27][2] = -335.00; //-95.00
        zone_pings[17] = 28;

        ping_coorindates[28][0] = -1197.14;
        ping_coorindates[28][1] = -1292.59;
        ping_coorindates[28][2] = -335.00; //-95.00
        zone_pings[18] = 29;

        ping_coorindates[29][0] = -1603.39;
        ping_coorindates[29][1] = -1383.98;
        ping_coorindates[29][2] = -325.00; //-95.00
        zone_pings[19] = 30;

        ping_coorindates[30][0] = -1565.33;
        ping_coorindates[30][1] = -648.76;
        ping_coorindates[30][2] = -240.00; //-95.00
        ping_coorindates[31][0] = -1578.84;
        ping_coorindates[31][1] = -234.01;
        ping_coorindates[31][2] = -240.00; //-95.00
        zone_pings[20] = 32;

        ping_coorindates[32][0] = -896.58;
        ping_coorindates[32][1] = -1404.99;
        ping_coorindates[32][2] = -325.00; //-95.00
        zone_pings[21] = 33;

        ping_coorindates[33][0] = -974.14;
        ping_coorindates[33][1] = 249.19;
        ping_coorindates[33][2] = -175.00; //-95.00
        ping_coorindates[34][0] = -1061.58;
        ping_coorindates[34][1] = -30.77;
        ping_coorindates[34][2] = -335.00; //-95.00
        ping_coorindates[35][0] = -1174.68;
        ping_coorindates[35][1] = -738.16;
        ping_coorindates[35][2] = -325.00; //-95.00
        zone_pings[22] = 36;

        ping_coorindates[36][0] = -319.48;
        ping_coorindates[36][1] = -790.84;
        ping_coorindates[36][2] = -280.00; //-95.00
        ping_coorindates[37][0] = 91.64;
        ping_coorindates[37][1] = -843.01;
        ping_coorindates[37][2] = -225.00; //-95.00
        zone_pings[23] = 38;

        ping_coorindates[38][0] = -561.65;
        ping_coorindates[38][1] = -267.70;
        ping_coorindates[38][2] = -160.00; //-95.00
        ping_coorindates[39][0] = -184.50;
        ping_coorindates[39][1] = 100.97;
        ping_coorindates[39][2] = -160.00; //-95.00
        ping_coorindates[40][0] = -855.69;
        ping_coorindates[40][1] = 250.49;
        ping_coorindates[40][2] = -160.00; //-95.00
        zone_pings[24] = 41;

        ping_coorindates[41][0] = -589.94;
        ping_coorindates[41][1] = 614.19;
        ping_coorindates[41][2] = -160.00; //-95.00
        zone_pings[25] = 42;

        ping_coorindates[42][0] = -1131.05;
        ping_coorindates[42][1] =  533.00;
        ping_coorindates[42][2] = -325.00; //-95.00
        ping_coorindates[43][0] = -960.03;
        ping_coorindates[43][1] = 555.42;
        ping_coorindates[43][2] = -175.00; //-95.00
        zone_pings[26] = 44;

        ping_coorindates[44][0] = -728.83;
        ping_coorindates[44][1] = 535.22;
        ping_coorindates[44][2] = -360.00; //-95.00
        ping_coorindates[45][0] = -728.83;
        ping_coorindates[45][1] = -32.00;
        ping_coorindates[45][2] = -360.00; //-95.00
        zone_pings[27] = 46;
    }
}

stock void ReplaceStrings(char[] str, char[] strReplace)
{
	char l_cstrFix[MAXZONES];
	Format(l_cstrFix, sizeof(l_cstrFix), str);
	ReplaceString(l_cstrFix, sizeof(l_cstrFix), "\"", "");
	ReplaceString(l_cstrFix, sizeof(l_cstrFix), strReplace, "");
	TrimString(l_cstrFix);
	Format(str, sizeof(l_cstrFix), l_cstrFix);
}

public void SpawnPing(int skin, float pos[3], int index)
{
    if (!g_is_zh_map)
    {
        return;
    }
    int Ent = CreateEntityByName("prop_dynamic");
    SetEntityModel(Ent, "models/unloze/unloze_ping.mdl");
    DispatchKeyValue(Ent, "modelscale", "1.5");

    if (skin == 1) //fought over (skull)
        SetVariantString("1");
    else if (skin == 2) //CT controlled (moon)
        SetVariantString("2");
    else if (skin == 3) //ZM controlled (cross)
        SetVariantString("3");

    AcceptEntityInput(Ent, "Skin");
    DispatchSpawn(Ent);
    TeleportEntity(Ent, pos, NULL_VECTOR, NULL_VECTOR);
    ping_ents[index] = Ent;
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void RemovePing(int index)
{
    if (!g_is_zh_map)
    {
        return;
    }
	if (ping_ents[index] != -1 && IsValidEdict(ping_ents[index]))
	{
		char m_szClassname[64];
		GetEdictClassname(ping_ents[index], m_szClassname, sizeof(m_szClassname));
		if (strcmp("prop_dynamic", m_szClassname)==0)
        {
            AcceptEntityInput(ping_ents[index], "Kill");
        }
	}
	ping_ents[index] = -1;
}

public void UpdateMarkers()
{
    if (!g_is_zh_map)
    {
        return;
    }
    //this only works because of knowing that order of ping coordinates match with zone order.
    for (int i = 0; i <= g_zoneCount; i++)
    {
        //PrintToChatAll("g_cZones_CT_count[i]: %i", g_cZones_CT_count[i]);
        //PrintToChatAll("g_cZones_ZM_count[i]: %i", g_cZones_ZM_count[i]);
        if (g_cZones_CT_count[i] > 0 && g_cZones_ZM_count[i] > 0)
        {
            //both humans and ZM in zone.
            if (g_iZone_fought_or_ct_controlled[i] != 1)
            {
                handle_pings(i, 1);
                g_iZone_fought_or_ct_controlled[i] = 1; //indicating zone is fought over
            }
        }
        else if (g_cZones_CT_count[i] > 0)
        {
            //only humans in zone
            //the zone just went from white (humans and zm in zone) to blue (only humans now in zone)
            if (g_iZone_fought_or_ct_controlled[i] != 2)
            {
                g_iZone_fought_or_ct_controlled[i] = 2;
                reward_ct_zone_points(i);
                handle_pings(i, 2);
            }
        }
        else if (g_cZones_ZM_count[i] > 0)
        {
            if (g_iZone_fought_or_ct_controlled[i] != 3)
            {
                g_iZone_fought_or_ct_controlled[i] = 3;
                //zm controlled zone
                reward_zm_zone_points(i);
                handle_pings(i, 3);
            }
        }
        else
        {
            if (g_iLast_Client_In_Zone[i] != 0)
            {
                int last_client_in_zone = g_iLast_Client_In_Zone[i];
                if (IsValidClient(last_client_in_zone) && IsPlayerAlive(last_client_in_zone) && GetClientTeam(last_client_in_zone) == CS_TEAM_CT)
                {
                    //last guy was a CT
                    g_iZone_fought_or_ct_controlled[i] = 2;
                    handle_pings(i, 2);
                }
                else if (IsValidClient(last_client_in_zone) && IsPlayerAlive(last_client_in_zone) && GetClientTeam(last_client_in_zone) == CS_TEAM_T)
                {
                    //last guy was a ZM
                    g_iZone_fought_or_ct_controlled[i] = 3;
                    handle_pings(i, 3);
                }
                else
                {
                    //the client who was last here might have left or went spectate.
                    handle_pings(i, 1);
                    g_iZone_fought_or_ct_controlled[i] = 1; //indicating zone is fought over
                }
            }
            else
            {
                //no client was in the zone in this round yet. setting it to the skull ping.
                handle_pings(i, 1);
                g_iZone_fought_or_ct_controlled[i] = 1; //indicating zone is fought over
            }
        }
    }
}

public void reward_zm_zone_points(int i)
{
    if (!g_is_zh_map)
    {
        return;
    }
    for (int j = 0; j < MaxClients; j++)
    {
        //is validclient, is ct, is alive, is inside the zone that just changed from fought to T controlled
        if (IsValidClient(j) && GetClientTeam(j) == CS_TEAM_T && IsPlayerAlive(j) && g_client_in_zone[j] == i)
        {
            LH_LogPlayerEvent(j, "triggered", "zh_h_zone_take_over_simple", false);
            //rewarding empty zone take over with at least 1 frag
            int frags = GetClientFrags(j);
            SetEntProp(j, Prop_Data, "m_iFrags", frags + 1);
        }
    }
}

public void reward_ct_zone_points(int i)
{
    if (!g_is_zh_map)
    {
        return;
    }
    for (int j = 0; j < MaxClients; j++)
    {
        //is validclient, is ct, is alive, is inside the zone that just changed from fought to CT controlled
        if (IsValidClient(j) && GetClientTeam(j) == CS_TEAM_CT && IsPlayerAlive(j) && g_client_in_zone[j] == i)
        {
            int damage_done_inside_fought_zone = PlayerRankings_GetClientDamage(j) - g_iCT_Damage_in_zone[j];
            g_iCT_Damage_in_zone[j] = PlayerRankings_GetClientDamage(j); 
            //so uh, lets say the zone becomes CT, then fought over again, then CT again without the guy ever leaving it.
            //this should ensure it only counts for what the guy did in damage since the takeover
            if (damage_done_inside_fought_zone > 0)
            {
                int frags = GetClientFrags(j);
                // Damage 0-1000 inside Zone
                if (damage_done_inside_fought_zone < 200)
                {
                    LH_LogPlayerEvent(j, "triggered", "zh_h_zone_take_over_1", false);
                    SetEntProp(j, Prop_Data, "m_iFrags", frags + 5);
                }
                else if (damage_done_inside_fought_zone < 400)
                {
                    LH_LogPlayerEvent(j, "triggered", "zh_h_zone_take_over_2", false);
                    SetEntProp(j, Prop_Data, "m_iFrags", frags + 10);
                }
                else if (damage_done_inside_fought_zone < 800)
                {
                    LH_LogPlayerEvent(j, "triggered", "zh_h_zone_take_over_3", false);
                    SetEntProp(j, Prop_Data, "m_iFrags", frags + 15);
                }
                else if (damage_done_inside_fought_zone < 1200)
                {
                    LH_LogPlayerEvent(j, "triggered", "zh_h_zone_take_over_4", false);
                    SetEntProp(j, Prop_Data, "m_iFrags", frags + 20);
                }
                else if (damage_done_inside_fought_zone < 1500)
                {
                    LH_LogPlayerEvent(j, "triggered", "zh_h_zone_take_over_5", false);
                    SetEntProp(j, Prop_Data, "m_iFrags", frags + 25);
                }
                else
                {
                    LH_LogPlayerEvent(j, "triggered", "zh_h_zone_take_over_6", false);
                    SetEntProp(j, Prop_Data, "m_iFrags", frags + 30);
                }
            }
            //this change might be very bad. it might overspam people with points if a zone constantly switches from controlled by CT
            //to instead be fought over back and forth. but at least it makes it worth it to take over zones.
            LH_LogPlayerEvent(j, "triggered", "zh_h_zone_take_over_simple", false);
            //rewarding empty zone take over with at least 1 frag
            int frags = GetClientFrags(j);
            SetEntProp(j, Prop_Data, "m_iFrags", frags + 1);
        }
    }
}
