#include <amxmodx>
#include <csdm>
#include <fakemeta>
#define IsPlayer(%1)                (1 <= %1 <= g_iMaxPlayers)
const Float:MIN_RESPAWN_TIME = 0.1
const Float:MAX_RESPAWN_TIME = 15.0
enum config_s
{
    iSectionID,
    iPluginID,
    iPluginFuncID
}
enum forwardlist_e
{
    iFwdRestartRound,
    iFwdPlayerSpawned,
    iFwdPlayerKilled,
    iFwdConfigLoad,
    iFwdInitialized,
    iFwdExecuteCVarVal,
    iFwdGamemodeChanged
}
new HookChain:g_hTraceAttack
new Array:g_aConfigData, Trie:g_tConfigSections, Trie:g_tConfigValues
new g_eCustomForwards[forwardlist_e]
new g_iIgnoreReturn, g_iMaxPlayers, g_iFwdEmitSound, g_iTotalItems
new Float:g_flRespawnDelay = MIN_RESPAWN_TIME, GameTypes:g_iGamemode
new bool:g_bShowRespawnBar, bool:g_bFreeForAll, bool:g_bBlockGunpickupSound
new bool:g_bIsBot[MAX_CLIENTS + 1], g_iNumSpawns[MAX_CLIENTS + 1]
public plugin_natives()
{
    register_library("csdm_core")
    register_native("CSDM_RegisterConfig", "native_register_config")
    register_native("CSDM_GetConfigKeyValue", "native_get_config_keyvalue")
    register_native("CSDM_GetGamemode", "native_get_gamemode")
    register_native("CSDM_SetGamemode", "native_set_gamemode")
    register_native("CSDM_GetEquipmode", "native_get_equipmode")
    register_native("CSDM_SetEquipmode", "native_set_equipmode")
}
public plugin_precache()
{
    g_aConfigData = ArrayCreate(config_s)
    g_tConfigSections = TrieCreate()
    g_tConfigValues = TrieCreate()
    g_eCustomForwards[iFwdRestartRound] = CreateMultiForward("CSDM_RestartRound", ET_IGNORE, FP_CELL)
    g_eCustomForwards[iFwdPlayerSpawned] = CreateMultiForward("CSDM_PlayerSpawned", ET_IGNORE, FP_CELL, FP_CELL, FP_CELL)
    g_eCustomForwards[iFwdPlayerKilled] = CreateMultiForward("CSDM_PlayerKilled", ET_CONTINUE, FP_CELL, FP_CELL, FP_CELL)
    g_eCustomForwards[iFwdConfigLoad] = CreateMultiForward("CSDM_ConfigurationLoad", ET_IGNORE, FP_CELL)
    g_eCustomForwards[iFwdExecuteCVarVal] = CreateMultiForward("CSDM_ExecuteCVarValues", ET_IGNORE)
    g_eCustomForwards[iFwdInitialized] = CreateMultiForward("CSDM_Initialized", ET_IGNORE, FP_STRING)
    g_eCustomForwards[iFwdGamemodeChanged] = CreateMultiForward("CSDM_GamemodeChanged", ET_STOP, FP_CELL, FP_CELL)
    LoadSettings()
}
public plugin_end()
{
    if(g_tConfigSections)
        TrieDestroy(g_tConfigSections)
    if(g_tConfigValues)
        TrieDestroy(g_tConfigValues)
}
public plugin_init()
{
    register_plugin(CSDM_PLUGIN_NAME, CSDM_VERSION_STRING, "wopox1337\Vaqtincha")
    register_cvar("csdm_version", CSDM_VERSION_STRING, FCVAR_SPONLY|FCVAR_UNLOGGED)
    RegisterHookChain(RG_CSGameRules_RestartRound, "CSGameRules_RestartRound", .post = false)
    RegisterHookChain(RG_CSGameRules_DeadPlayerWeapons, "CSGameRules_DeadPlayerWeapons", .post = false)
    RegisterHookChain(RG_RoundEnd, "RoundEnd", .post = false)
    RegisterHookChain(RG_CBasePlayer_Killed, "CSGameRules_PlayerKilled", .post = false)
    RegisterHookChain(RG_CBasePlayer_Spawn, "CBasePlayer_Spawn", .post = true)
    DisableHookChain(g_hTraceAttack = RegisterHookChain(RG_CBasePlayer_TraceAttack, "CBasePlayer_TraceAttack", .post = false))
    set_msg_block(get_user_msgid("HudTextArgs"), BLOCK_SET)
    set_msg_block(get_user_msgid("ClCorpse"), BLOCK_SET)
    g_iMaxPlayers = get_maxplayers()
    ExecuteForward(g_eCustomForwards[iFwdInitialized], g_iIgnoreReturn, CSDM_VERSION_STRING)
}
new iEquipManId, iEquipManGFuncId, iEquipManSFuncId
public plugin_cfg()
{
    CheckForwards()
    if((iEquipManId = is_plugin_loaded("CSDM Equip Manager")) > 0)
    {
        iEquipManGFuncId = get_func_id("get_equip_mode", iEquipManId)
        iEquipManSFuncId = get_func_id("set_equip_mode", iEquipManId)
    }
}
public EquipTypes:native_get_equipmode(iPlugin, iParams)
{
    if(iEquipManId <= 0 || iEquipManGFuncId <= 0)
    {
        log_error(AMX_ERR_NATIVE, "[CSDM] ERROR: Plugin ^"CSDM Equip Manager^" not loaded!")
        return EQUIP_NONE
    }
    
    if(callfunc_begin_i(iEquipManGFuncId, iEquipManId) == INVALID_HANDLE)
    {
        log_error(AMX_ERR_NATIVE, "[CSDM] ERROR: Called dynanative into a paused plugin!")
        return EQUIP_NONE
    }
    return EquipTypes:callfunc_end()
}
public native_set_equipmode(iPlugin, iParams)
{
    if(!CheckNativeParams(iParams, 1, 1))
        return
    if(iEquipManId <= 0 || iEquipManSFuncId <= 0)
    {
        log_error(AMX_ERR_NATIVE, "[CSDM] ERROR: Plugin ^"CSDM Equip Manager^" not loaded!")
        return
    }
    
    if(callfunc_begin_i(iEquipManSFuncId, iEquipManId) == INVALID_HANDLE)
    {
        log_error(AMX_ERR_NATIVE, "[CSDM] ERROR: Called dynanative into a paused plugin!")
        return
    }
    callfunc_push_int(get_param(1))     // new equip mode
    callfunc_end()
}
    
public GameTypes:native_get_gamemode(iPlugin, iParams)
{
    return g_iGamemode
}
public bool:native_set_gamemode(iPlugin, iParams)
{
    if(!CheckNativeParams(iParams, 1, 1))
        return false
    new GameTypes:iNewGamemode = GameTypes:clamp(get_param(1), _:NORMAL_HIT, _:AUTO_HEALER)
    if(g_iGamemode == iNewGamemode)
    {
        server_print("[CSDM] Already set ^"%s^" mode!", g_szGamemodes[iNewGamemode])
        return false
    }
    new iRet
    ExecuteForward(g_eCustomForwards[iFwdGamemodeChanged], g_iIgnoreReturn, g_iGamemode, iNewGamemode)
    if(iRet == PLUGIN_HANDLED)
    {
        server_print("[CSDM] Not allowed to change ^"%s^" mode!", g_szGamemodes[iNewGamemode])
        return false
    }
    server_print("[CSDM] Gamemode ^"%s^" changed to ^"%s^"", g_szGamemodes[g_iGamemode], g_szGamemodes[iNewGamemode])
    g_iGamemode = iNewGamemode
    CheckForwards()
    return true
}
public bool:native_get_config_keyvalue(iPlugin, iParams)
{
    if(!CheckNativeParams(iParams, 1, 3))
        return false
    new szKey[MAX_KEY_LEN], szValue[MAX_VALUE_LEN]
    get_string(1, szKey, charsmax(szKey))
    if(!szKey[0])
    {
        log_error(AMX_ERR_NATIVE, "[CSDM] ERROR: Invalid key name!")
        return false
    }
    strtolower(szKey)
    if(!TrieGetString(g_tConfigValues, szKey, szValue, charsmax(szValue)))
    {
        server_print("[CSDM] ERROR: Keyname ^"%s^" was not found!", szKey)
        return false
    }
    set_string(2, szValue, get_param(3)/* iLen */)
    return true
}
public native_register_config(iPlugin, iParams)
{
    if(!CheckNativeParams(iParams, 1, 2))
        return INVALID_INDEX
    new eArrayData[config_s], szHandler[64], szSection[MAX_SECTION_LEN]
    get_string(1, szSection, charsmax(szSection))
    if(!szSection[0])
    {
        log_error(AMX_ERR_NATIVE, "[CSDM] ERROR: Invalid section name!")
        return INVALID_INDEX
    }
    strtolower(szSection)
    format(szSection, charsmax(szSection), "[%s]", szSection)
    get_string(2, szHandler, charsmax(szHandler))
    if(!szHandler[0])
    {
        log_error(AMX_ERR_NATIVE, "[CSDM] ERROR: Invalid callback specified for allowed!")
        return INVALID_INDEX
    }
    eArrayData[iPluginID] = iPlugin
    eArrayData[iPluginFuncID] = get_func_id(szHandler, iPlugin)
    if(eArrayData[iPluginFuncID] < 0)
    {
        log_error(AMX_ERR_NOTFOUND, "[CSDM] ERROR: Function ^"%s^" was not found!", szHandler)
        return INVALID_INDEX
    }
    new iItemIndex = eArrayData[iSectionID] = g_iTotalItems++
    ArrayPushArray(g_aConfigData, eArrayData)
    TrieSetCell(g_tConfigSections, szSection, iItemIndex)
    return iItemIndex
}
public client_putinserver(pPlayer)
{
    g_bIsBot[pPlayer] = bool:is_user_bot(pPlayer)
    g_iNumSpawns[pPlayer] = 0
}
public CBasePlayer_Spawn(const pPlayer)
{
    if(!is_user_alive(pPlayer))
        return
    g_iNumSpawns[pPlayer]++
    ExecuteForward(g_eCustomForwards[iFwdPlayerSpawned], g_iIgnoreReturn, pPlayer, bool:g_bIsBot[pPlayer], g_iNumSpawns[pPlayer])
}
public CSGameRules_PlayerKilled(const pPlayer, const pKiller, const iGibs)
{
    ExecuteForward(g_eCustomForwards[iFwdPlayerKilled], g_iIgnoreReturn, pPlayer, IsPlayer(pKiller) ? pKiller : 0, get_member(pPlayer, m_LastHitGroup))
    if(get_member(pPlayer, m_bHasDefuser)) {
        rg_remove_item(pPlayer, "item_thighpack")
    }
    if(g_bShowRespawnBar && g_flRespawnDelay >= 1.5 && !(Menu_ChooseTeam <= get_member(pPlayer, m_iMenu) <= Menu_ChooseAppearance)) {
        rg_send_bartime(pPlayer, floatround(g_flRespawnDelay), false)
    }
}
public CBasePlayer_TraceAttack(const pPlayer, pevAttacker, Float:flDamage, Float:vecDir[3], tracehandle, bitsDamageType)
{
    if(!IsPlayer(pevAttacker) || pPlayer == pevAttacker)
        return HC_CONTINUE
    switch(g_iGamemode)
    {
        case HEADSHOTS_ONLY:
        {
            if(get_tr2(tracehandle, TR_iHitgroup) != HIT_HEAD && get_user_weapon(pevAttacker) != CSW_KNIFE)
                return HC_SUPERCEDE
        }
        case ALWAYS_HIT_HEAD:
        {
            if(get_tr2(tracehandle, TR_iHitgroup) != HIT_HEAD && get_user_weapon(pevAttacker) != CSW_KNIFE)
                set_tr2(tracehandle, TR_iHitgroup, HIT_HEAD)
        }
        case AUTO_HEALER:
        {
            set_member(pPlayer, m_idrowndmg, 100)
            set_member(pPlayer, m_idrownrestored, 0)
            // SetHookChainArg(6, ATYPE_INTEGER, bitsDamageType | DMG_DROWNRECOVER)
        }
        default: return HC_CONTINUE
    }
    return HC_CONTINUE
}
public CSGameRules_DeadPlayerWeapons(const pPlayer)
{
    SetHookChainReturn(ATYPE_INTEGER, GR_PLR_DROP_GUN_NO)
    return HC_SUPERCEDE
}
public CSGameRules_RestartRound()
{
    new bool:bIsNewGame = bool:get_member_game(m_bCompleteReset)
    if(bIsNewGame) {
        ArraySet(g_iNumSpawns, 0)
    }
    ExecuteForward(g_eCustomForwards[iFwdRestartRound], g_iIgnoreReturn, bool:bIsNewGame)
}
public RoundEnd(WinStatus:status, ScenarioEventEndRound:event, Float:tmDelay)
{
    if(event != ROUND_GAME_COMMENCE && event != ROUND_GAME_RESTART)
    {
        SetHookChainReturn(ATYPE_INTEGER, false)
        return HC_SUPERCEDE
    }
    return HC_CONTINUE
}
public EmitSound(const pEntity, const iChannel, const szSample[], Float:fVol, Float:fAttn, iFlags, iPitch)
{
    return (iChannel == CHAN_ITEM && szSample[0] == 'i' && szSample[6] == 'g' && szSample[15] == '2') ? FMRES_SUPERCEDE : FMRES_IGNORED
    // items/gunpickup2.wav
}
public SetCVarValues()
{
    if(g_bFreeForAll) {
        set_cvar_num("mp_freeforall", 1)
        // set_cvar_num("bot_deathmatch", 1)
    }
    set_cvar_float("mp_forcerespawn", g_flRespawnDelay)
    set_cvar_num("mp_roundrespawn_time", -1)
    ExecuteForward(g_eCustomForwards[iFwdExecuteCVarVal], g_iIgnoreReturn)
}
LoadSettings(const ReadTypes:iReadAction = CFG_READ)
{
    new szLineData[MAX_LINE_LEN + 4], eArrayData[config_s], pFile, iItemIndex = INVALID_INDEX, bool:bMainSettings
    new szKey[MAX_KEY_LEN], szValue[MAX_VALUE_LEN], szSign[2]
    if(!(pFile = OpenConfigFile()))
        return
    ExecuteForward(g_eCustomForwards[iFwdConfigLoad], g_iIgnoreReturn, iReadAction)
    set_task(1.0, "SetCVarValues")
    while(!feof(pFile))
    {
        fgets(pFile, szLineData, charsmax(szLineData))
        trim(szLineData)
        if(!szLineData[0] || IsCommentLine(szLineData))
            continue
        if(szLineData[0] == '[')
        {
            bMainSettings = bool:(equali(szLineData, g_szMainSection))
            
            strtolower(szLineData)
            if(g_iTotalItems && !TrieGetCell(g_tConfigSections, szLineData, iItemIndex)) {
                iItemIndex = INVALID_INDEX
            }
            continue
        }
        if(g_iTotalItems && iItemIndex != INVALID_INDEX)
        {
            ArrayGetArray(g_aConfigData, iItemIndex, eArrayData)
            PluginCallFunc(eArrayData, szLineData)
        }
        if(!ParseConfigKey(szLineData, szKey, szSign, szValue))
            continue
        strtolower(szKey)
        TrieSetString(g_tConfigValues, szKey, szValue)
        if(bMainSettings)
        {
            if(equali(szKey, "respawn_delay"))
            {
                g_flRespawnDelay = floatclamp(str_to_float(szValue), MIN_RESPAWN_TIME, MAX_RESPAWN_TIME)
            }
            else if(equali(szKey, "show_respawn_bar"))
            {
                g_bShowRespawnBar = bool:(str_to_num(szValue))
            }
            else if(equali(szKey, "block_gunpickup_sound"))
            {
                g_bBlockGunpickupSound = bool:(str_to_num(szValue))
            }
            else if(equali(szKey, "free_for_all"))
            {
                g_bFreeForAll = bool:(str_to_num(szValue))
            }
            else if(equali(szKey, "gameplay_mode"))
            {
                g_iGamemode = GameTypes:clamp(str_to_num(szValue), _:NORMAL_HIT, _:AUTO_HEALER)
            }
        }
    }
    fclose(pFile)
}
OpenConfigFile()
{
    new szConfigDir[PLATFORM_MAX_PATH], szConfigFile[PLATFORM_MAX_PATH + 32]
    new szMapName[32], szMapPrefix[6], pFile
    get_localinfo("amxx_configsdir", szConfigDir, charsmax(szConfigDir))
    get_mapname(szMapName, charsmax(szMapName))
    if(szMapName[0] == '$') // for support: $1000$, $2000$, $3000$ ...
        copy(szMapPrefix, charsmax(szMapPrefix), "$")
    else
        copyc(szMapPrefix, charsmax(szMapPrefix), szMapName, '_')
    formatex(szConfigFile, charsmax(szConfigFile), "%s/%s/%s", szConfigDir, g_szMainDir, g_szExtraCfgDir)
    MakeDir(szConfigFile)
    formatex(szConfigFile, charsmax(szConfigFile), "%s/%s/%s/%s.ini", szConfigDir, g_szMainDir, g_szExtraCfgDir, szMapName)
    if((pFile = fopen(szConfigFile, "rt"))) // extra config
    {
        server_print("[CSDM] Extra map config successfully loaded for map ^"%s^"", szMapName)
        return pFile
    }
    formatex(szConfigFile, charsmax(szConfigFile), "%s/%s/%s/prefix_%s.ini", szConfigDir, g_szMainDir, g_szExtraCfgDir, szMapPrefix)
    if((pFile = fopen(szConfigFile, "rt"))) // prefix config
    {
        server_print("[CSDM] Prefix ^"%s^" map config successfully loaded for map ^"%s^"", szMapPrefix, szMapName)
        return pFile
    }
    formatex(szConfigFile, charsmax(szConfigFile), "%s/%s/%s", szConfigDir, g_szMainDir, g_szDefaultCfgFile)
    if((pFile = fopen(szConfigFile, "rt"))) // default config
    {
        server_print("[CSDM] Default config successfully loaded.")       
        return pFile
    }
    ExecuteForward(g_eCustomForwards[iFwdInitialized], g_iIgnoreReturn, "")
    CSDM_SetFailState("[CSDM] ERROR: Config file ^"%s^" not found!", szConfigFile)
    return 0
}
PluginCallFunc(const eArrayData[config_s], const szLineData[])
{
    if(callfunc_begin_i(eArrayData[iPluginFuncID], eArrayData[iPluginID]) == INVALID_HANDLE)
    {
        server_print("[CSDM] ERROR: Called dynanative into a paused plugin!")
        return
    }
    callfunc_push_str(szLineData, .copyback = false)
    callfunc_push_int(eArrayData[iSectionID])
    callfunc_end()
}
CheckForwards()
{
    if(g_iGamemode != NORMAL_HIT)
        EnableHookChain(g_hTraceAttack)
    else
        DisableHookChain(g_hTraceAttack)
    if(g_bBlockGunpickupSound && !g_iFwdEmitSound)
    {
        g_iFwdEmitSound = register_forward(FM_EmitSound, "EmitSound", ._post = false)
    }
    else if(!g_bBlockGunpickupSound && g_iFwdEmitSound)
    {
        unregister_forward(FM_EmitSound, g_iFwdEmitSound, .post = false)
        g_iFwdEmitSound = 0
    }
}
bool:CheckNativeParams(const iParams, const iMin, const iMax)
{
    if(iMin == iMax ? iParams != iMax : !(iMin <= iParams <= iMax)) // max params
    {
        log_error(AMX_ERR_PARAMS, "[CSDM] ERROR: Bad arg count!")
        return false
    }
    
    return true
}