// Copyright © 2016/2020 Vaqtincha
const MAX_SPAWNS = 64
const Float:MIN_SPAWN_RADIUS = 500.0
new const CSDM_SPAWN_DIR[] = "csdm/spawns" // default: addons/amxmodx/configs/csdm/spawns
#define PL_VERSION     "0.1.4"
#include <amxmodx>
#include <fakemeta>
#include <reapi>
#if AMXX_VERSION_NUM < 183
    #include <colorchat>
#endif
#define IsVectorZero(%1)                 (%1[X] == 0.0 && %1[Y] == 0.0 && %1[Z] == 0.0)
#define IsPlayer(%1)                    (1 <= %1 <= g_iMaxPlayers)
#define Vector(%1,%2,%3)                (Float:{%1.0, %2.0, %3.0})
#define VECTOR_ZERO                     Vector(0, 0, 0)
#define FIND_ENT_IN_SPHERE(%1,%2,%3)     engfunc(EngFunc_FindEntityInSphere, %1, %2, %3)
#define REMOVE_ENTITY(%1)                 engfunc(EngFunc_RemoveEntity, %1)
#define SET_ORIGIN(%1,%2)                 engfunc(EngFunc_SetOrigin, %1, %2)
#define SET_SIZE(%1,%2,%3)                 engfunc(EngFunc_SetSize, %1, %2, %3)
#define SET_MODEL(%1,%2)                engfunc(EngFunc_SetModel, %1, %2)
const MENU_KEY_BITS = (MENU_KEY_1|MENU_KEY_2|MENU_KEY_3|MENU_KEY_4|MENU_KEY_5|MENU_KEY_6|MENU_KEY_7|MENU_KEY_9)
enum coord_e { Float:X, Float:Y, Float:Z }
enum { FAILED_CREATE, FILE_SAVED, FILE_DELETED }
enum { MODE_TEAMPLAY, MODE_DEATHMATCH }
enum spawn_s
{
    Float:Origin[coord_e],
    Float:VAngle[coord_e],
    Float:Angles[coord_e],
    TeamName:Team
}
enum player_s
{
    Float:LastOrigin[coord_e],
    Float:LastVAngle[coord_e],
    Float:LastAngles[coord_e],
    LastSpawn,
    AimedEntity,
    bool:FirstSpawn,
    TeamName:LastTeam,
    TeamName:CurTeam
}
// ======================= spawn editor settings =======================
const MAX_SEARCH_DISTANCE = 2500
const Float:ADD_Z_POSITION = 15.0
new const Float:g_flGravityValues[] = { 1.0, 0.5, 0.25, 0.15, 0.05 }
new const Float:g_vecColorTeam[any:TEAM_CT + 1][coord_e] =
{
    { 0.0, 250.0, 0.0 },    // TEAM_UNASSIGNED
    { 250.0, 0.0, 0.0 },    // TEAM_TERRORIST
    { 0.0, 0.0, 250.0 }        // TEAM_CT
}
new const SOUND_SELECT[] = "common/menu2.wav"
new const SOUND_ERROR[] = "buttons/button2.wav"
new const SOUND_SUCCESS[] = "buttons/blip2.wav"
new const g_szModels[any:TEAM_CT + 1][] =
{
    "models/player/vip/vip.mdl",    // TEAM_UNASSIGNED
    "models/player/leet/leet.mdl",    // TEAM_TERRORIST
    "models/player/gign/gign.mdl"    // TEAM_CT
}
//======================================================================
new const g_szClassName[] = "view_spawn"
new const g_szMenuTitle[] = "SpawnEditor"
new any:g_aSpot[MAX_SPAWNS][spawn_s], any:g_aPlayerData[MAX_CLIENTS + 1][player_s]
new g_szSpawnDirectory[256], g_szSpawnFile[256], g_szMapName[32]
new g_iMenuID, bool:g_bEditSpawns, bool:g_bNotSaved
new g_iGravity, g_iMaxPlayers, HookChain:g_hGetPlayerSpawnSpot, HookChain:g_hUseEmpty
new g_iTotalPoints, g_iNum[any:TEAM_CT + 1], g_iSpawnMode = MODE_TEAMPLAY
public SetSpawnerStateApi(bool:bEnabled, const iNewMode)
{
    SetSpawnerState(bEnabled, clamp(iNewMode, MODE_TEAMPLAY, MODE_DEATHMATCH))
    return g_iTotalPoints
}
public plugin_precache()
{
    for (new i = 0; i < sizeof(g_szModels); i++) {
        precache_model(g_szModels[i])    // for custom models
    }
    
    precache_sound(SOUND_SELECT)
    precache_sound(SOUND_ERROR)
    precache_sound(SOUND_SUCCESS)
}
public plugin_init()
{
    register_plugin("DeathMatch Spawn Manager", PL_VERSION, "Vaqtincha")
    register_concmd("csdm_edit_spawns", "ConCmd_EditSpawns", ADMIN_MAP, "Edits spawn configuration")
    register_clcmd("nightvision", "ClCmd_Nightvision")
    register_menucmd((g_iMenuID = register_menuid(g_szMenuTitle)), MENU_KEY_BITS, "EditorMenuHandler")
    DisableHookChain(g_hGetPlayerSpawnSpot = RegisterHookChain(RG_CSGameRules_GetPlayerSpawnSpot, "CSGameRules_GetPlayerSpawnSpot", .post = false))
    DisableHookChain(g_hUseEmpty = RegisterHookChain(RG_CBasePlayer_UseEmpty, "CBasePlayer_UseEmpty", .post = false))
    g_iMaxPlayers = get_maxplayers()
}
public plugin_cfg()
{
    new iLen = get_localinfo("amxx_configsdir", g_szSpawnDirectory, charsmax(g_szSpawnDirectory))
    formatex(g_szSpawnDirectory[iLen], charsmax(g_szSpawnDirectory) - iLen, "%s/%s", g_szSpawnDirectory[iLen], CSDM_SPAWN_DIR)
    MakeDir(g_szSpawnDirectory)
    get_mapname(g_szMapName, charsmax(g_szMapName))
    formatex(g_szSpawnFile, charsmax(g_szSpawnFile), "%s/%s.spawns.cfg", g_szSpawnDirectory, g_szMapName)
    LoadPoints()
}
public plugin_end()
{
    if (g_bEditSpawns && g_bNotSaved) // autosave
    {
        MakeDir(g_szSpawnDirectory)
        SavePoints()
    }
}
public client_connect(pPlayer)
{
    g_aPlayerData[pPlayer][FirstSpawn] = true
}
public client_putinserver(pPlayer)
{
    g_aPlayerData[pPlayer][AimedEntity] = NULLENT
    g_aPlayerData[pPlayer][LastTeam] = TEAM_UNASSIGNED
    g_aPlayerData[pPlayer][LastSpawn] = 0
    g_aPlayerData[pPlayer][FirstSpawn] = false
    g_aPlayerData[pPlayer][LastOrigin][X] = g_aPlayerData[pPlayer][LastOrigin][Y] = g_aPlayerData[pPlayer][LastOrigin][Z] = 0.0
}
public ClCmd_Nightvision(const pPlayer, const level) {
    return (!g_bEditSpawns || !is_user_alive(pPlayer) || !(get_user_flags(pPlayer) & level)) ? PLUGIN_CONTINUE : ShowEditorMenu(pPlayer)
}
public ConCmd_EditSpawns(const pPlayer, const level)
{
    if (!is_user_alive(pPlayer) || !(get_user_flags(pPlayer) & level))
        return PLUGIN_HANDLED
    
    if (g_bEditSpawns)
    {
        if (g_bNotSaved && SavePoints() == FAILED_CREATE)
        {
            console_print(pPlayer, "[CSDM] Autosave is failed! Please try again...")
            return ShowEditorMenu(pPlayer)
        }
        console_print(pPlayer, "[CSDM] Spawn editor disabled.")
        IsViewingMenu(pPlayer, true)
        RemoveAllSpotEntitys()
        g_bEditSpawns = false
        
        set_cvar_num("mp_round_infinite", 0)
        set_cvar_num("mp_forcerespawn", 0)
        set_entvar(pPlayer, var_gravity, 1.0)
        // SetSpawnerState(false)
        DisableHookChain(g_hUseEmpty)
        
        return PLUGIN_HANDLED
    }
    console_print(pPlayer, "[CSDM] Spawn editor enabled.")
    MakeAllSpotEntitys()
    g_bEditSpawns = true
    
    set_cvar_num("mp_round_infinite", 1)
    set_cvar_num("mp_forcerespawn", 1)
    set_entvar(pPlayer, var_gravity, g_flGravityValues[g_iGravity])
    SetSpawnerState(true)
    EnableHookChain(g_hUseEmpty)
    
    return ShowEditorMenu(pPlayer)
}
public CBasePlayer_UseEmpty(const pPlayer)
{
    if (IsViewingMenu(pPlayer))
    {
        EditorMenuHandler(pPlayer, 1)
        return HC_SUPERCEDE
    }
    
    return HC_CONTINUE
}
public CSGameRules_GetPlayerSpawnSpot(const pPlayer)
{
    if (g_iTotalPoints > 0 && !g_aPlayerData[pPlayer][FirstSpawn] && RandomSpawn(pPlayer))
    {
        SetHookChainReturn(ATYPE_INTEGER, pPlayer)
        return HC_SUPERCEDE
    }
    return HC_CONTINUE
}
public ShowEditorMenu(const pPlayer)
{
    new szMenu[512], bitKeys = (MENU_KEY_2|MENU_KEY_3|MENU_KEY_6|MENU_KEY_7)
    new iLen = formatex(szMenu, charsmax(szMenu), "\yРедактор точек возрожд.^n^n")
    new bool:bAddCancel = bool:(!IsVectorZero(g_aPlayerData[pPlayer][LastOrigin])), bool:bReached = bool:(g_iTotalPoints >= MAX_SPAWNS)
    static const szTeamName[any:TEAM_CT + 1][] = { "ALL", "TT", "CT" }
    if (bAddCancel)
    {
        bitKeys |= MENU_KEY_5
    }
    if (g_aPlayerData[pPlayer][AimedEntity] == NULLENT)
    {
        iLen += formatex(szMenu[iLen], charsmax(szMenu) - iLen,
            "%s^n^n\
            \y2. \wВыделить точку возрожд.^n\
            \y3. \wВыбрать команду\r: \w[\y%s\w]^n\
            \d4. Проверка точки возрожд.^n\
            %s^n^n",
            bReached ? "\d1. Добавить точку для возрожд.\w(\rМаксим. лимит точек\w)" : "\y1. \wДобавить новую точку для возрожд.",
            szTeamName[g_aPlayerData[pPlayer][CurTeam]], bAddCancel ? "\y5. \wОтменить удаление" : "\d5. Удалить точку возрожд."
        )
        if (!bReached) {
            bitKeys |= MENU_KEY_1
        }
    }
    else
    {
        iLen += formatex(szMenu[iLen], charsmax(szMenu) - iLen,
            "\y1. \wОбновить точку возрожд.^n^n\
            \y2. \wСнять выделение с точки возрожд.^n\
            \y3. \wВыбрать точку возрожд. для команд\r:    \w[\y%s\w]^n\
            \y4. \wПроверка точки возрожд.^n\
            \y5. %s^n^n", szTeamName[get_entvar(g_aPlayerData[pPlayer][AimedEntity], var_team)],
            bAddCancel ? "\wОтменить удаление" : "\rУдалить точку возрожд."
        )
        bitKeys |= (MENU_KEY_1|MENU_KEY_4|MENU_KEY_5)
    }
    iLen += formatex(szMenu[iLen], charsmax(szMenu) - iLen,
        "\y6. \wПоказать статистику^n\
        \y7. \wГравитация\r: \w[\y%0.2f\w]^n^n\
        %s^n^n\
        \wВсего точек\r: \w[\y%d\r/\y%d\w] \r(\wALL \y%d \r| \wTT \y%d \r| \wCT \y%d\r)", g_flGravityValues[g_iGravity],
        g_bNotSaved ? "\y9. \wСохранить" : "\d9. Сохранить", g_iTotalPoints, MAX_SPAWNS, g_iNum[TEAM_UNASSIGNED], g_iNum[TEAM_TERRORIST], g_iNum[TEAM_CT]
    )
    show_menu(pPlayer, bitKeys |= g_bNotSaved ? MENU_KEY_9 : bitKeys, szMenu, .title = g_szMenuTitle)
    return PLUGIN_HANDLED
}
public EditorMenuHandler(const pPlayer, iKey)
{
    if (!g_bEditSpawns)
        return PLUGIN_HANDLED
    iKey++
    switch (iKey)
    {
        case 1:
        {
            g_bNotSaved = bool:(g_aPlayerData[pPlayer][AimedEntity] == NULLENT ? AddSpawn(pPlayer) : MoveSpawn(pPlayer, g_aPlayerData[pPlayer][AimedEntity]))
            g_aPlayerData[pPlayer][LastOrigin][X] = g_aPlayerData[pPlayer][LastOrigin][Y] = g_aPlayerData[pPlayer][LastOrigin][Z] = 0.0
        }
        case 2:
        {
            if (g_aPlayerData[pPlayer][AimedEntity] == NULLENT)
            {
                if (!SetAimedEntity(pPlayer)) {
                    client_print(pPlayer, print_center, "Spawn entity not found!")
                }
                else {
                    rg_send_audio(pPlayer, SOUND_SELECT)
                }
            }
            else
            {
                ClearAimedEntity(pPlayer)
            }
        }
        case 3:
        {
            if (g_aPlayerData[pPlayer][AimedEntity] == NULLENT)
            {
                if (++g_aPlayerData[pPlayer][CurTeam] > TEAM_CT) {
                    g_aPlayerData[pPlayer][CurTeam] = TEAM_UNASSIGNED
                }
            }
            else
            {
                SetSpotTeam(g_aPlayerData[pPlayer][AimedEntity])
                g_bNotSaved = true
            }
        }
        case 4: TeleportToAimed(pPlayer, g_aPlayerData[pPlayer][AimedEntity])
        case 5: g_bNotSaved = bool:(IsVectorZero(g_aPlayerData[pPlayer][LastOrigin]) ? DeleteSpawn(pPlayer, g_aPlayerData[pPlayer][AimedEntity]) : AddSpawn(pPlayer, true))
        case 6:
        {
            new Float:vecOrigin[coord_e]
            get_entvar(pPlayer, var_origin, vecOrigin)
            client_print_color(pPlayer, print_team_grey, "Всего точек: ^4%d ^1(^3ALL ^4%d ^3TT ^4%d ^3CT ^4%d^1) Текущ. положение: ^3X ^4%0.f ^3Y ^4%0.f ^3Z ^4%0.f",
                g_iTotalPoints, g_iNum[TEAM_UNASSIGNED], g_iNum[TEAM_TERRORIST], g_iNum[TEAM_CT], vecOrigin[X], vecOrigin[Y], vecOrigin[Z])
        }
        case 7:
        {
            if (++g_iGravity >= sizeof(g_flGravityValues)) {
                g_iGravity = 0
            }
            set_entvar(pPlayer, var_gravity, g_flGravityValues[g_iGravity])
        }
        case 9:
        {
            static const szResultPrint[][] = {"Failed to create file!^rPlease try again", "Saved successfully", "File deleted"}
            client_print(pPlayer, print_center, "%s", szResultPrint[SavePoints()])
        }
    }
    return ShowEditorMenu(pPlayer)
}
bool:AddSpawn(const pPlayer, bool:bUndo = false)
{
    new Float:vecOrigin[coord_e], Float:vecAngles[coord_e], Float:vecVAngles[coord_e], pEntity = NULLENT
    
    if ((pEntity = CreateEntity()) == NULLENT)
        return false
    if (bUndo)
    {
        SetPosition(pEntity, g_aPlayerData[pPlayer][LastOrigin], g_aPlayerData[pPlayer][LastAngles], g_aPlayerData[pPlayer][LastVAngle])
        set_entvar(pEntity, var_team, g_aPlayerData[pPlayer][LastTeam])
        SET_MODEL(pEntity, g_szModels[g_aPlayerData[pPlayer][LastTeam]])
        g_iNum[g_aPlayerData[pPlayer][LastTeam]]++
        SetAimedEntity(pPlayer, pEntity, false)
    }
    else
    {
        GetPosition(pPlayer, vecOrigin, vecAngles, vecVAngles)
        vecOrigin[Z] += ADD_Z_POSITION
        if (!CheckFreeSpace(pPlayer, vecOrigin))
        {
            REMOVE_ENTITY(pEntity)
            return false
        }
        g_iNum[g_aPlayerData[pPlayer][CurTeam]]++
        SetPosition(pEntity, vecOrigin, vecAngles, vecVAngles)
        set_entvar(pEntity, var_team, g_aPlayerData[pPlayer][CurTeam])
        SET_MODEL(pEntity, g_szModels[g_aPlayerData[pPlayer][CurTeam]])
        rg_send_audio(pPlayer, SOUND_SUCCESS)
    }
    g_iTotalPoints++
    return true
}
bool:MoveSpawn(const pPlayer, const pEntity)
{
    new Float:vecOrigin[coord_e], Float:vecAngles[coord_e], Float:vecVAngles[coord_e]
    GetPosition(pPlayer, vecOrigin, vecAngles, vecVAngles)
    vecOrigin[Z] += ADD_Z_POSITION
    if (CheckFreeSpace(pPlayer, vecOrigin))
    {
        SetPosition(pEntity, vecOrigin, vecAngles, vecVAngles)
        return true
    }
    return false
}
bool:DeleteSpawn(const pPlayer, const pEntity)
{
    if (is_nullent(pEntity))
        return false
    GetPosition(pEntity, g_aPlayerData[pPlayer][LastOrigin], g_aPlayerData[pPlayer][LastAngles], g_aPlayerData[pPlayer][LastVAngle])
    g_aPlayerData[pPlayer][LastTeam] = get_entvar(pEntity, var_team)
    g_aPlayerData[pPlayer][AimedEntity] = NULLENT
    REMOVE_ENTITY(pEntity)
    g_iNum[g_aPlayerData[pPlayer][LastTeam]]--
    g_iTotalPoints--
    return true
}
TeleportToAimed(const pPlayer, const pEntity = NULLENT)
{
    if (is_nullent(pEntity))
        return
    new Float:vecOrigin[coord_e], Float:vecAngles[coord_e], Float:vecVAngles[coord_e]
    GetPosition(pEntity, vecOrigin, vecAngles, vecVAngles)
    if (CheckFreeSpace(pPlayer, vecOrigin)) {
        SetPlayerPosition(pPlayer, vecOrigin, vecVAngles)
    }
}
LoadPoints()
{
    new pFile
    if (!(pFile = fopen(g_szSpawnFile, "rt")))
    {
        server_print("[CSDM] No spawn points file found ^"%s^"", g_szMapName)
        return
    }
    new szDatas[128], szOrigin[coord_e][6], szTeam[3], szAngles[coord_e][6], szVAngles[coord_e][6]
    while (!feof(pFile))
    {
        fgets(pFile, szDatas, charsmax(szDatas))
        trim(szDatas)
        if (!szDatas[0] || (szDatas[0] == '/' && szDatas[1] == '/'))
            continue
        if (parse(szDatas, szOrigin[X], 5, szOrigin[Y], 5, szOrigin[Z], 5, szAngles[X], 5, szAngles[Y], 5, szAngles[Z], 5,
                szTeam, charsmax(szTeam), szVAngles[X], 5, szVAngles[Y], 5, szVAngles[Z], 5
            ) != 10)
        {
            continue // ignore invalid lines
        }
        if (g_iTotalPoints >= MAX_SPAWNS)
        {
            server_print("[CSDM] Max limit %d reached!", MAX_SPAWNS)
            break
        }
        g_aSpot[g_iTotalPoints][Origin][X] = str_to_float(szOrigin[X])
        g_aSpot[g_iTotalPoints][Origin][Y] = str_to_float(szOrigin[Y])
        g_aSpot[g_iTotalPoints][Origin][Z] = str_to_float(szOrigin[Z])
        // if (!IsHullVacant(g_aSpot[g_iTotalPoints][Origin], HULL_HUMAN))
        // {
            // server_print("[CSDM] Warning bad spawn detected at: [X: %0.f | Y: %0.f | Z: %0.f]", g_aSpot[g_iTotalPoints][Origin][X], g_aSpot[g_iTotalPoints][Origin][Y], g_aSpot[g_iTotalPoints][Origin][Z])
            // continue
        // }
        g_aSpot[g_iTotalPoints][Angles][X] = str_to_float(szAngles[X])
        g_aSpot[g_iTotalPoints][Angles][Y] = str_to_float(szAngles[Y])
        // g_aSpot[g_iTotalPoints][Angles][Z] = str_to_float(szAngles[Z]) // not used
        g_aSpot[g_iTotalPoints][VAngle][X] = str_to_float(szVAngles[X])
        g_aSpot[g_iTotalPoints][VAngle][Y] = str_to_float(szVAngles[Y])
        // g_aSpot[g_iTotalPoints][VAngle][Z] = str_to_float(szVAngles[Z]) // not used
        g_aSpot[g_iTotalPoints][Team] = clamp(str_to_num(szTeam), any:TEAM_UNASSIGNED, any:TEAM_CT)
        g_iNum[g_aSpot[g_iTotalPoints][Team]]++
        g_iTotalPoints++
    }
    if (g_iTotalPoints > 0)
    {
        server_print("[CSDM] Map ^"%s^" total spawns: %d [ANY: %d | TT: %d | CT: %d]", g_szMapName,
            g_iTotalPoints, g_iNum[TEAM_UNASSIGNED], g_iNum[TEAM_TERRORIST], g_iNum[TEAM_CT])
        // SetSpawnerState(true, (!g_iNum[TEAM_TERRORIST] && !g_iNum[TEAM_CT]) ? MODE_DEATHMATCH : MODE_TEAMPLAY)
    }
    fclose(pFile)
}
SavePoints()
{
    if (g_iTotalPoints <= 0)
    {
        delete_file(g_szSpawnFile)
        SetSpawnerState(false)
        return FILE_DELETED
    }
    new pFile, pEntity = NULLENT
    if (!(pFile = fopen(g_szSpawnFile, "wt")))
    {
        MakeDir(g_szSpawnDirectory, false)
        return FAILED_CREATE
    }
    fprintf(pFile, "// File generated by ^"CSDM Spawn Manager^" Version: %s^n// Total spawns: %d (ANY = %d, TT = %d, CT = %d)^n^n// Origin X:Y:Z, Angles X:Y:Z, Team, View Angles X:Y:Z^n^n",
        PL_VERSION, g_iTotalPoints, g_iNum[TEAM_UNASSIGNED], g_iNum[TEAM_TERRORIST], g_iNum[TEAM_CT])
    ClearAllArrays()
    while ((pEntity = rg_find_ent_by_class(pEntity, g_szClassName)))
    {
        if (g_iTotalPoints >= MAX_SPAWNS)
        {
            server_print("[CSDM] Max limit %d reached!", MAX_SPAWNS)
            break
        }
        GetPosition(pEntity, g_aSpot[g_iTotalPoints][Origin], g_aSpot[g_iTotalPoints][Angles], g_aSpot[g_iTotalPoints][VAngle])
        if (IsVectorZero(g_aSpot[g_iTotalPoints][Origin]))
            continue
        g_aSpot[g_iTotalPoints][Team] = get_entvar(pEntity, var_team)
        g_iNum[g_aSpot[g_iTotalPoints][Team]]++
        fprintf(pFile, "%-6.f %-6.f %-6.f %-4.f %-5.f %-2.f %-2.1d %-4.f %-5.f %-1.f^n",
            g_aSpot[g_iTotalPoints][Origin][X], g_aSpot[g_iTotalPoints][Origin][Y], g_aSpot[g_iTotalPoints][Origin][Z],
            g_aSpot[g_iTotalPoints][Angles][X], g_aSpot[g_iTotalPoints][Angles][Y], g_aSpot[g_iTotalPoints][Angles][Z],
            g_aSpot[g_iTotalPoints][Team],
            g_aSpot[g_iTotalPoints][VAngle][X], g_aSpot[g_iTotalPoints][VAngle][Y], g_aSpot[g_iTotalPoints][VAngle][Z]
        )
        g_iTotalPoints++
    }
    // SetSpawnerState(true)
    g_bNotSaved = false
    fclose(pFile)
    return FILE_SAVED
}
MakeAllSpotEntitys()
{
    for (new i = 0, pEntity = NULLENT; i < g_iTotalPoints; i++)
    {
        if (!IsVectorZero(g_aSpot[i][Origin]) && (pEntity = CreateEntity()) != NULLENT)
        {
            SetPosition(pEntity, g_aSpot[i][Origin], g_aSpot[i][Angles], g_aSpot[i][VAngle])
            set_entvar(pEntity, var_team, g_aSpot[i][Team])
            SET_MODEL(pEntity, g_szModels[g_aSpot[i][Team]])
        }
    }
}
RemoveAllSpotEntitys()
{
    new pEntity = NULLENT, pPlayer
    for (pPlayer = 1; pPlayer < g_iMaxPlayers; pPlayer++)
    {
        g_aPlayerData[pPlayer][LastOrigin][X] = g_aPlayerData[pPlayer][LastOrigin][Y] = g_aPlayerData[pPlayer][LastOrigin][Z] = 0.0
        g_aPlayerData[pPlayer][AimedEntity] = NULLENT
        g_aPlayerData[pPlayer][LastTeam] = TEAM_UNASSIGNED
    }
    while ((pEntity = rg_find_ent_by_class(pEntity, g_szClassName))) {
        REMOVE_ENTITY(pEntity)
    }
}
CreateEntity()
{
    new pEntity = rg_create_entity("info_target")
    if (is_nullent(pEntity))
    {
        abort(AMX_ERR_GENERAL, "Failed to create entity!")
        return NULLENT
    }
    set_entvar(pEntity, var_classname, g_szClassName)
    set_entvar(pEntity, var_solid, SOLID_BBOX)
    rg_animate_entity(pEntity, ACT_IDLE)
    return pEntity
}
SetPlayerPosition(const pPlayer, const Float:vecOrigin[], const Float:vecAngles[])
{
    const FORCE_VIEW_ANGLES = 1
    SET_ORIGIN(pPlayer, vecOrigin)
    set_entvar(pPlayer, var_velocity, VECTOR_ZERO)
    // set_entvar(pPlayer, var_avelocity, VECTOR_ZERO)
    set_entvar(pPlayer, var_v_angle, VECTOR_ZERO)
    set_entvar(pPlayer, var_angles, vecAngles)
    set_entvar(pPlayer, var_punchangle, VECTOR_ZERO)
    set_entvar(pPlayer, var_fixangle, FORCE_VIEW_ANGLES)
}
SetPosition(const pEntity, const Float:vecOrigin[], const Float:vecAngles[], const Float:vecVAngles[])
{
    SET_ORIGIN(pEntity, vecOrigin)
    set_entvar(pEntity, var_angles, vecAngles)
    set_entvar(pEntity, var_v_angle, vecVAngles) // temporary save
}
GetPosition(const pEntity, Float:vecOrigin[], Float:vecAngles[], Float:vecVAngles[])
{
    get_entvar(pEntity, var_origin, vecOrigin)
    get_entvar(pEntity, var_angles, vecAngles)
    get_entvar(pEntity, var_v_angle, vecVAngles)
}
SetSpotTeam(const pEntity)
{
    new TeamName:iOldTeam, TeamName:iNewTeam = iOldTeam = get_entvar(pEntity, var_team)
    if (++iNewTeam > TEAM_CT) {
        iNewTeam = TEAM_UNASSIGNED
    }
    g_iNum[iNewTeam]++
    g_iNum[iOldTeam]--
    set_entvar(pEntity, var_team, iNewTeam)
    SET_MODEL(pEntity, g_szModels[iNewTeam])
    rg_set_rendering(pEntity, kRenderFxGlowShell, g_vecColorTeam[iNewTeam], 10.0)
}
bool:SetAimedEntity(const pPlayer, pEntity = NULLENT, bool:bPrint = true)
{
    if (pEntity > 0 || (pEntity = FindEntityByAim(pPlayer)) != NULLENT)
    {
        rg_animate_entity(pEntity, ACT_RUN, 1.0)
        rg_set_rendering(pEntity, kRenderFxGlowShell, g_vecColorTeam[get_entvar(pEntity, var_team)], 10.0)
        g_aPlayerData[pPlayer][AimedEntity] = pEntity
        g_aPlayerData[pPlayer][LastOrigin][X] = g_aPlayerData[pPlayer][LastOrigin][Y] = g_aPlayerData[pPlayer][LastOrigin][Z] = 0.0
        if (bPrint) {
            client_print(pPlayer, print_center, "Aimed entity index %d", g_aPlayerData[pPlayer][AimedEntity])
        }
    
        return true
    }
    return false
}
ClearAimedEntity(const pPlayer)
{
    rg_animate_entity(g_aPlayerData[pPlayer][AimedEntity], ACT_IDLE)
    rg_set_rendering(g_aPlayerData[pPlayer][AimedEntity])
    g_aPlayerData[pPlayer][AimedEntity] = NULLENT
}
ClearAllArrays()
{
    for (new i = 0; i < MAX_SPAWNS; i++)
    {
        g_aSpot[i][Origin][X] = g_aSpot[i][Origin][Y] = g_aSpot[i][Origin][Z] = 0.0
        g_aSpot[i][VAngle][X] = g_aSpot[i][VAngle][Y] = g_aSpot[i][VAngle][Z] = 0.0
        g_aSpot[i][Angles][X] = g_aSpot[i][Angles][Y] = g_aSpot[i][Angles][Z] = 0.0
    }
    g_iNum[TEAM_UNASSIGNED] = g_iNum[TEAM_TERRORIST] = g_iNum[TEAM_CT] = 0
    g_iTotalPoints = 0
}
bool:IsViewingMenu(const pPlayer, bool:Close = false)
{
    new iMenuID, iKeys
    get_user_menu(pPlayer, iMenuID, iKeys)
    if (iMenuID == g_iMenuID)
    {
        if (Close)
        {
            // menu_cancel(pPlayer)
            show_menu(pPlayer, 0, "^n", 0)
        }
        
        return true
    }
    
    return false
}
FindEntityByAim(const pPlayer)
{
    new pEntity = NULLENT, iBody
    SetEntitysSolid(true)
    get_user_aiming(pPlayer, pEntity, iBody, MAX_SEARCH_DISTANCE)
    SetEntitysSolid(false)
    return (FClassnameIs(pEntity, g_szClassName)) ? pEntity : NULLENT
}
SetEntitysSolid(const bool:bSolid)
{
    new pEntity = NULLENT
    while ((pEntity = rg_find_ent_by_class(pEntity, g_szClassName)))
    {
        if (!bSolid)
            SET_SIZE(pEntity, VECTOR_ZERO, VECTOR_ZERO)
        else
            SET_SIZE(pEntity, Vector(-16, -16, -36), Vector(16, 16, 36))
    }
}
bool:RandomSpawn(const pPlayer)
{
    new iRand, iAttempts, iLast = g_aPlayerData[pPlayer][LastSpawn], TeamName:iTeam = get_member(pPlayer, m_iTeam)
    do
    {
        iAttempts++
        iRand = random(g_iTotalPoints)
        if (iRand != iLast && !IsVectorZero(g_aSpot[iRand][Origin]) && get_distance_fix(g_aSpot[iRand][Origin], g_aSpot[iLast][Origin]) > MIN_SPAWN_RADIUS)
        {
            if ((g_iSpawnMode == MODE_DEATHMATCH || g_aSpot[iRand][Team] == iTeam || g_aSpot[iRand][Team] == TEAM_UNASSIGNED)
                && !CheckDistance(pPlayer, g_aSpot[iRand][Origin])/* && IsHullVacant(g_aSpot[iRand][Origin], HULL_HUMAN, pPlayer) */)
            {
                SetPlayerPosition(pPlayer, g_aSpot[iRand][Origin], g_aSpot[iRand][VAngle])
                g_aPlayerData[pPlayer][LastSpawn] = iRand
                return true
            }
        }
    } while (iAttempts <= g_iTotalPoints)
    return false
}
bool:CheckDistance(const pPlayer, const Float:vecOrigin[])
{
    new pEntity = NULLENT
    while ((pEntity = FIND_ENT_IN_SPHERE(pEntity, vecOrigin, MIN_SPAWN_RADIUS)))
    {
        if (IsPlayer(pEntity) && pEntity != pPlayer && get_entvar(pEntity, var_deadflag) == DEAD_NO) {
            return true
        }
    }
    return false
}
bool:CheckFreeSpace(const pPlayer, const Float:vecOrigin[coord_e])
{
    if (!IsHullVacant(vecOrigin, HULL_HUMAN, pPlayer))
    {
        client_print(pPlayer, print_center, "No free space!")
        rg_send_audio(pPlayer, SOUND_ERROR)
        return false
    }
    return true
}
SetSpawnerState(bool:bEnabled, const iNewMode = MODE_TEAMPLAY)
{
    if (bEnabled && g_iTotalPoints > 0)
    {
        EnableHookChain(g_hGetPlayerSpawnSpot)
        g_iSpawnMode = iNewMode
    }
    else
    {
        DisableHookChain(g_hGetPlayerSpawnSpot)
    }
}
MakeDir(const szDirName[], bool:bPrint = true)
{
    if (dir_exists(szDirName))
        return
    if (bPrint) {
        server_print("[CSDM] Directory ^"%s^" not exist, will be created automatically.", szDirName)
    }
    if (mkdir(szDirName)) {
        abort(AMX_ERR_GENERAL, "[CSDM] Failed to create directory ^"%s^"", szDirName)
    }
}
// checks if a space is vacant, by VEN
stock bool:IsHullVacant(const Float:vecOrigin[], const iHullNumber, const pSkipEnt = 0)
{
    const pTr = 0 // pGlobalTrace
    engfunc(EngFunc_TraceHull, vecOrigin, vecOrigin, DONT_IGNORE_MONSTERS, iHullNumber, pSkipEnt, pTr)
    return bool:(!get_tr2(pTr, TR_StartSolid) && !get_tr2(pTr, TR_AllSolid) && get_tr2(pTr, TR_InOpen))
}
stock rg_animate_entity(const pEntity, const Activity:iSequence, const Float:flFramerate = 0.0)
{
    set_entvar(pEntity, var_sequence, iSequence)
    set_entvar(pEntity, var_framerate, flFramerate)
}
stock rg_set_rendering(const pEntity, const fx = kRenderFxNone, const Float:flColor[] = {0.0, 0.0, 0.0}, const Float:iAmount = 0.0)
{
    set_entvar(pEntity, var_renderfx, fx)
    set_entvar(pEntity, var_rendercolor, flColor)
    set_entvar(pEntity, var_renderamt, iAmount)
}
// FIX: "error 047: array sizes do not match, or destination array is too small"
stock Float:get_distance_fix(const Float:Origin1[], const Float:Origin2[])
{
    new Float:fOrigin1[coord_e], Float:fOrigin2[coord_e]
    fOrigin1[X] = Origin1[X]; fOrigin1[Y] = Origin1[Y]; fOrigin1[Z] = Origin1[Z]
    fOrigin2[X] = Origin2[X]; fOrigin2[Y] = Origin2[Y]; fOrigin2[Z] = Origin2[Z]
    return get_distance_f(fOrigin1, fOrigin2)
}