Загрузка данных


-- AdminPunish_kdm1eHvH.lua
-- Система наказаний: mute, gag, ban

-- ============================================================
-- Платформа бана (координаты)
-- ============================================================
local BAN_POS = Vector(6035.70, -936.62, 2983.03)
local BAN_ANG = Angle(2.19, -0.45, 0.00)

-- ============================================================
-- БД
-- ============================================================
local function InitDB()
    if sql.TableExists("admin_punishments") then return end
    sql.Query([[
        CREATE TABLE admin_punishments (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            steamid TEXT,
            steamid64 TEXT,
            nick TEXT,
            type TEXT,
            reason TEXT,
            admin_steamid TEXT,
            admin_nick TEXT,
            created INTEGER,
            expires INTEGER,
            active INTEGER DEFAULT 1
        )
    ]])
    print("[AdminPunish] Таблица admin_punishments создана")
end

InitDB()

-- ============================================================
-- Парсер времени
-- ============================================================
local function parseTime(str)
    if not str or str == "" then return nil end
    str = string.lower(str)

    if str == "0" then return 0 end -- навсегда

    local num = tonumber(string.match(str, "^(%d+)"))
    if not num then return nil end

    local unit = string.match(str, "%d+([a-z]+)")
    if not unit then return num * 60 end -- по умолчанию минуты

    if unit == "m" or unit == "min" then return num * 60 end
    if unit == "h" or unit == "hour" then return num * 3600 end
    if unit == "d" or unit == "day" then return num * 86400 end
    if unit == "w" or unit == "week" then return num * 604800 end
    if unit == "mo" or unit == "month" then return num * 2592000 end
    if unit == "y" or unit == "year" then return num * 31536000 end

    return num * 60
end

local function formatTime(seconds)
    if seconds == 0 or not seconds then return "навсегда" end
    if seconds >= 31536000 then return math.floor(seconds / 31536000) .. "г" end
    if seconds >= 2592000 then return math.floor(seconds / 2592000) .. "мес" end
    if seconds >= 604800 then return math.floor(seconds / 604800) .. "н" end
    if seconds >= 86400 then return math.floor(seconds / 86400) .. "д" end
    if seconds >= 3600 then return math.floor(seconds / 3600) .. "ч" end
    return math.floor(seconds / 60) .. "м"
end

-- ============================================================
-- Утилиты
-- ============================================================
local function findPlayer(name)
    if not name or name == "" then return nil end
    local lower = string.lower(name)
    for _, p in ipairs(player.GetAll()) do
        if IsValid(p) and string.find(string.lower(p:Nick()), lower, 1, true) then
            return p
        end
    end
    return nil
end

local function notify(ply, msg)
    if IsValid(ply) then
        ply:PrintMessage(HUD_PRINTTALK, "[Админ] " .. msg)
    else
        print("[Админ] " .. msg)
    end
end

-- ============================================================
-- Активные наказания (кэш для быстрого доступа)
-- ============================================================
local activePunishments = {} -- [steamid] = { mute = exp, gag = exp, ban = exp }

local function refreshCache(steamid)
    if not steamid then return end
    activePunishments[steamid] = activePunishments[steamid] or {}

    local q = "SELECT type, expires FROM admin_punishments WHERE steamid = " .. sql.SQLStr(steamid) ..
              " AND active = 1 AND (expires = 0 OR expires > " .. os.time() .. ")"
    local res = sql.Query(q)

    activePunishments[steamid] = { mute = nil, gag = nil, ban = nil }

    if res then
        for _, row in ipairs(res) do
            local t = row.type
            local exp = tonumber(row.expires) or 0
            activePunishments[steamid][t] = exp
        end
    end
end

-- Публичный доступ
function GetPunishment(ply, type)
    if not IsValid(ply) then return nil end
    local sid = ply:SteamID()
    activePunishments[sid] = activePunishments[sid] or { mute = nil, gag = nil, ban = nil }

    local exp = activePunishments[sid][type]
    if exp and exp > 0 and exp <= os.time() then
        -- Истекло — убираем
        sql.Query("UPDATE admin_punishments SET active = 0 WHERE steamid = " .. sql.SQLStr(sid) ..
                  " AND type = " .. sql.SQLStr(type) .. " AND active = 1")
        activePunishments[sid][type] = nil
        return nil
    end
    return exp
end

-- ============================================================
-- Выдать наказание
-- ============================================================
local function addPunishment(target, type, duration, reason, admin)
    if not IsValid(target) then return end

    local expires = 0
    if duration > 0 then
        expires = os.time() + duration
    end

    local adminSteam = IsValid(admin) and admin:SteamID() or "console"
    local adminNick = IsValid(admin) and admin:Nick() or "Console"

    sql.Query(string.format(
        "INSERT INTO admin_punishments (steamid, steamid64, nick, type, reason, admin_steamid, admin_nick, created, expires, active) " ..
        "VALUES (%s, %s, %s, %s, %s, %s, %s, %d, %d, 1)",
        sql.SQLStr(target:SteamID()),
        sql.SQLStr(target:SteamID64()),
        sql.SQLStr(target:Nick()),
        sql.SQLStr(type),
        sql.SQLStr(reason or "не указана"),
        sql.SQLStr(adminSteam),
        sql.SQLStr(adminNick),
        os.time(),
        expires
    ))

    refreshCache(target:SteamID())

    -- Если бан — телепорт на платформу
    if type == "ban" then
        timer.Simple(0.5, function()
            if IsValid(target) then
                target:SetPos(BAN_POS)
                target:SetEyeAngles(BAN_ANG)
                target:SetNWBool("kdm1e_Banned", true)
                target:PrintMessage(HUD_PRINTTALK, "=== ТЫ ЗАБАНЕН ===")
                target:PrintMessage(HUD_PRINTTALK, "Админ: " .. adminNick)
                target:PrintMessage(HUD_PRINTTALK, "Причина: " .. (reason or "не указана"))
                target:PrintMessage(HUD_PRINTTALK, "Срок: " .. (expires == 0 and "навсегда" or os.date("%d.%m.%Y %H:%M", expires)))
            end
        end)
    end
end

-- ============================================================
-- Автоснятие истекших наказаний (каждые 30 сек)
-- ============================================================
timer.Create("AdminPunish_Cleanup", 30, 0, function()
    local now = os.time()
    sql.Query("UPDATE admin_punishments SET active = 0 WHERE expires > 0 AND expires <= " .. now)

    for _, ply in ipairs(player.GetAll()) do
        if IsValid(ply) then
            refreshCache(ply:SteamID())
        end
    end
end)

-- ============================================================
-- Кик забаненных при заходе
-- ============================================================
hook.Add("PlayerInitialSpawn", "AdminPunish_BanCheck", function(ply)
    timer.Simple(3, function()
        if not IsValid(ply) then return end
        refreshCache(ply:SteamID())

        local banExp = activePunishments[ply:SteamID()] and activePunishments[ply:SteamID()].ban
        if banExp then
            local expireText = (banExp == 0) and "навсегда" or os.date("%d.%m.%Y %H:%M", banExp)
            ply:Kick("Ты забанен. Срок: " .. expireText)
        end
    end)
end)

-- ============================================================
-- Блокировка действий забаненных
-- ============================================================
hook.Add("PlayerNoClip", "AdminPunish_BanNoClip", function(ply)
    if GetPunishment(ply, "ban") then return false end
end)

hook.Add("PlayerSwitchWeapon", "AdminPunish_BanWeapon", function(ply)
    if GetPunishment(ply, "ban") then return true end
end)

hook.Add("PlayerSpawn", "AdminPunish_BanSpawn", function(ply)
    if GetPunishment(ply, "ban") then
        timer.Simple(0.1, function()
            if IsValid(ply) then
                ply:SetPos(BAN_POS)
                ply:SetEyeAngles(BAN_ANG)
                ply:SetNWBool("kdm1e_Banned", true)
            end
        end)
    end
end)

-- Запрет стрельбы для забаненных
hook.Add("EntityFireBullets", "AdminPunish_BanShoot", function(ent, data)
    if IsValid(ent) and ent:IsPlayer() and GetPunishment(ent, "ban") then
        return false
    end
end)

-- ============================================================
-- КОМАНДЫ
-- ============================================================

-- !mute
concommand.Add("kdm1e_mute", function(ply, cmd, args)
    if IsValid(ply) and not RANKS.HasPerm(ply, "canMute") then
        notify(ply, "Нет прав!")
        return
    end
    if #args < 2 then notify(ply, "!mute Ник время причина"); return end

    local target = findPlayer(args[1])
    if not IsValid(target) then notify(ply, "Игрок не найден"); return end

    local duration = parseTime(args[2])
    if not duration then notify(ply, "Неверный формат времени"); return end

    table.remove(args, 1); table.remove(args, 1)
    local reason = table.concat(args, " ")

    addPunishment(target, "mute", duration, reason, ply)
    notify(ply, target:Nick() .. " замучен чата на " .. formatTime(duration))
    target:PrintMessage(HUD_PRINTTALK, "Ты замучен чата: " .. reason)
end)

-- !unmute
concommand.Add("kdm1e_unmute", function(ply, cmd, args)
    if IsValid(ply) and not RANKS.HasPerm(ply, "canMute") then return end
    if #args < 1 then notify(ply, "!unmute Ник"); return end

    local target = findPlayer(args[1])
    if not IsValid(target) then notify(ply, "Игрок не найден"); return end

    sql.Query("UPDATE admin_punishments SET active = 0 WHERE steamid = " .. sql.SQLStr(target:SteamID()) .. " AND type = 'mute' AND active = 1")
    refreshCache(target:SteamID())
    notify(ply, target:Nick() .. " размучен")
end)

-- !gag
concommand.Add("kdm1e_gag", function(ply, cmd, args)
    if IsValid(ply) and not RANKS.HasPerm(ply, "canMute") then return end
    if #args < 2 then notify(ply, "!gag Ник время причина"); return end

    local target = findPlayer(args[1])
    if not IsValid(target) then notify(ply, "Игрок не найден"); return end

    local duration = parseTime(args[2])
    if not duration then notify(ply, "Неверный формат времени"); return end

    table.remove(args, 1); table.remove(args, 1)
    local reason = table.concat(args, " ")

    addPunishment(target, "gag", duration, reason, ply)
    notify(ply, target:Nick() .. " заглушён на " .. formatTime(duration))
    target:PrintMessage(HUD_PRINTTALK, "Твой голос заглушён: " .. reason)
end)

-- !ungag
concommand.Add("kdm1e_ungag", function(ply, cmd, args)
    if IsValid(ply) and not RANKS.HasPerm(ply, "canMute") then return end
    if #args < 1 then notify(ply, "!ungag Ник"); return end

    local target = findPlayer(args[1])
    if not IsValid(target) then notify(ply, "Игрок не найден"); return end

    sql.Query("UPDATE admin_punishments SET active = 0 WHERE steamid = " .. sql.SQLStr(target:SteamID()) .. " AND type = 'gag' AND active = 1")
    refreshCache(target:SteamID())
    notify(ply, target:Nick() .. " разглушён")
end)

-- !ban
concommand.Add("kdm1e_ban", function(ply, cmd, args)
    if IsValid(ply) and not RANKS.HasPerm(ply, "canBan") then
        notify(ply, "Нет прав!")
        return
    end
    if #args < 3 then notify(ply, "!ban Ник время причина"); return end

    local target = findPlayer(args[1])
    if not IsValid(target) then notify(ply, "Игрок не найден"); return end

    local duration = parseTime(args[2])
    if not duration then notify(ply, "Неверный формат времени"); return end

    table.remove(args, 1); table.remove(args, 1)
    local reason = table.concat(args, " ")

    addPunishment(target, "ban", duration, reason, ply)
    notify(ply, target:Nick() .. " забанен на " .. formatTime(duration))

    -- Телепорт на платформу
    timer.Simple(1, function()
        if IsValid(target) then
            target:SetPos(BAN_POS)
            target:SetEyeAngles(BAN_ANG)
        end
    end)
end)

-- !unban
concommand.Add("kdm1e_unban", function(ply, cmd, args)
    if IsValid(ply) and not RANKS.HasPerm(ply, "canUnban") then
        notify(ply, "Нет прав!")
        return
    end
    if #args < 1 then notify(ply, "!unban SteamID"); return end

    local sid = args[1]
    sql.Query("UPDATE admin_punishments SET active = 0 WHERE (steamid = " .. sql.SQLStr(sid) ..
              " OR steamid64 = " .. sql.SQLStr(sid) .. ") AND type = 'ban' AND active = 1")

    notify(ply, "Игрок " .. sid .. " разбанен")

    -- Если онлайн — снять с платформы
    for _, p in ipairs(player.GetAll()) do
        if IsValid(p) and (p:SteamID() == sid or p:SteamID64() == sid) then
            p:SetNWBool("kdm1e_Banned", false)
            refreshCache(p:SteamID())
            p:PrintMessage(HUD_PRINTTALK, "Ты разбанен!")
        end
    end
end)

-- !banlist
concommand.Add("kdm1e_banlist", function(ply, cmd, args)
    if IsValid(ply) and not RANKS.HasPerm(ply, "canBan") then return end

    local res = sql.Query("SELECT nick, steamid, reason, expires FROM admin_punishments WHERE type = 'ban' AND active = 1 ORDER BY created DESC LIMIT 20")
    if not res or #res == 0 then
        notify(ply, "Список банов пуст")
        return
    end

    notify(ply, "=== СПИСОК БАНОВ ===")
    for _, row in ipairs(res) do
        local timeText = (tonumber(row.expires) == 0) and "навсегда" or os.date("%d.%m %H:%M", tonumber(row.expires))
        notify(ply, string.format("%s | %s | %s | до %s", row.nick, row.steamid, row.reason, timeText))
    end
end)

-- !kick
concommand.Add("kdm1e_kick", function(ply, cmd, args)
    if IsValid(ply) and not RANKS.HasPerm(ply, "canKick") then
        notify(ply, "Нет прав!")
        return
    end
    if #args < 2 then notify(ply, "!kick Ник причина"); return end

    local target = findPlayer(args[1])
    if not IsValid(target) then notify(ply, "Игрок не найден"); return end

    table.remove(args, 1)
    local reason = table.concat(args, " ")

    target:Kick("Кик: " .. reason)
    notify(ply, target:Nick() .. " кикнут: " .. reason)
end)

-- !slay
concommand.Add("kdm1e_slay", function(ply, cmd, args)
    if IsValid(ply) and not RANKS.HasPerm(ply, "canSlay") then
        notify(ply, "Нет прав!")
        return
    end
    if #args < 1 then notify(ply, "!slay Ник"); return end

    local target = findPlayer(args[1])
    if not IsValid(target) then notify(ply, "Игрок не найден"); return end

    target:Kill()
    notify(ply, target:Nick() .. " убит")
end)

-- !respawn
concommand.Add("kdm1e_respawn", function(ply, cmd, args)
    if IsValid(ply) and not RANKS.HasPerm(ply, "canSlay") then return end
    if #args < 1 then notify(ply, "!respawn Ник"); return end

    local target = findPlayer(args[1])
    if not IsValid(target) then notify(ply, "Игрок не найден"); return end

    if target:Alive() then target:KillSilent() end
    target:Spawn()
    notify(ply, target:Nick() .. " заспавнен")
end)

-- ============================================================
-- Чат-команды с !
-- ============================================================
hook.Add("PlayerSay", "AdminPunish_ChatCommands", function(ply, text, team)
    if not IsValid(ply) then return end
    local lower = string.lower(text)

    local cmds = { "mute", "unmute", "gag", "ungag", "ban", "unban", "banlist", "kick", "slay" }

    for _, c in ipairs(cmds) do
        if lower == "!" .. c or lower == "/" .. c then
            ply:ConCommand("kdm1e_" .. c)
            return ""
        end
        if string.StartWith(lower, "!" .. c .. " ") or string.StartWith(lower, "/" .. c .. " ") then
            local args = string.Explode(" ", text)
            table.remove(args, 1)
            ply:ConCommand("kdm1e_" .. c .. " " .. table.concat(args, " "))
            return ""
        end
    end
end)

print("[AdminPunish] Система наказаний загружена")