local PLUGIN = PLUGIN util.AddNetworkString("ixServerLogs_Request") util.AddNetworkString("ixServerLogs_Response") util.AddNetworkString("ixServerLogs_Clear") util.AddNetworkString("ixServerLogs_OpenMenu") -- Функция проверки доступа к логам через CAMI local function HasLogAccess(client) return AdminPrivs[client:GetUserGroup()] end -- Инициализация системы логов function PLUGIN:InitializeLogSystem() self.logs = self.logs or {} self.logFile = nil -- Создаем папку для логов if ix.config.Get("logSaveToFile", true) then local logPath = ix.config.Get("logFilePath", "helix_logs") if not file.Exists(logPath, "DATA") then file.CreateDir(logPath) end -- Создаем файл для текущей сессии local date = os.date("%Y-%m-%d_%H-%M-%S") self.logFile = logPath .. "/log_" .. date .. ".txt" file.Write(self.logFile, "=== Server Logs Started: " .. os.date("%Y-%m-%d %H:%M:%S") .. " ===\n") end end -- Добавление лога function PLUGIN:AddLog(logType, message, target, data) if not self.LOG_TYPES[logType] then print("[ServerLogs] Unknown log type: " .. tostring(logType)) return end local logEntry = { id = #self.logs + 1, type = logType, category = self.LOG_TYPES[logType].category, message = message, target = target and { name = IsValid(target) and target:Nick() or tostring(target), steamid = IsValid(target) and target:SteamID() or "N/A" } or nil, data = data or {}, timestamp = os.time(), timeString = os.date("%H:%M:%S") } table.insert(self.logs, logEntry) -- Ограничиваем количество логов в памяти local maxEntries = ix.config.Get("logMaxEntries", 1000) if #self.logs > maxEntries then table.remove(self.logs, 1) end -- Сохраняем в файл if self.logFile then local logString = string.format("[%s][%s] %s\n", logEntry.timeString, self.LOG_CATEGORIES[logEntry.category].name, message ) file.Append(self.logFile, logString) end -- Выводим в консоль MsgC(self.LOG_CATEGORIES[logEntry.category].color, "[LOG] ", Color(255, 255, 255), message .. "\n") end -- Получение логов с фильтрацией function PLUGIN:GetLogs(filters) filters = filters or {} local result = {} for i = #self.logs, 1, -1 do local log = self.logs[i] local match = true -- Фильтр по категории if filters.category and log.category ~= filters.category then match = false end -- Фильтр по типу if filters.type and log.type ~= filters.type then match = false end -- Фильтр по игроку if filters.player and log.target and log.target.steamid ~= filters.player then match = false end -- Фильтр по времени if filters.startTime and log.timestamp < filters.startTime then match = false end if filters.endTime and log.timestamp > filters.endTime then match = false end -- Фильтр по тексту if filters.search and not string.find(string.lower(log.message), string.lower(filters.search)) then match = false end if match then table.insert(result, log) -- Ограничиваем количество результатов if filters.limit and #result >= filters.limit then break end end end return result end -- Сетевая обработка запроса логов net.Receive("ixServerLogs_Request", function(len, client) if not HasLogAccess(client) then return end local filters = net.ReadTable() local logs = PLUGIN:GetLogs(filters) -- Отправляем логи порциями local chunkSize = 50 for i = 1, #logs, chunkSize do local chunk = {} for j = i, math.min(i + chunkSize - 1, #logs) do table.insert(chunk, logs[j]) end net.Start("ixServerLogs_Response") net.WriteUInt(i == 1 and 1 or 0, 2) -- Первый пакет или нет net.WriteTable(chunk) net.Send(client) end -- Отправляем финальный пакет если логов нет if #logs == 0 then net.Start("ixServerLogs_Response") net.WriteUInt(1, 2) net.WriteTable({}) net.Send(client) end end) -- Очистка логов net.Receive("ixServerLogs_Clear", function(len, client) if not client:IsSuperAdmin() then return end PLUGIN.logs = {} PLUGIN:AddLog("ADMIN_COMMAND", client:Nick() .. " очистил логи сервера", client) end) -- ====================== -- ХУКИ ДЛЯ ЛОГИРОВАНИЯ -- ====================== -- Смерть игрока function PLUGIN:PlayerDeath(victim, inflictor, attacker) local attackerName = "Мир" local attackerSteamID = "world" local weapon = "unknown" if IsValid(attacker) and attacker:IsPlayer() then attackerName = attacker:Nick() attackerSteamID = attacker:SteamID() weapon = IsValid(attacker:GetActiveWeapon()) and attacker:GetActiveWeapon():GetClass() or "unknown" end local message = "" if IsValid(attacker) and attacker:IsPlayer() then if attacker == victim then message = victim:Nick() .. " покончил с собой" else message = attackerName .. " убил " .. victim:Nick() .. " используя " .. weapon end else message = victim:Nick() .. " умер" end self:AddLog("PLAYER_DEATH", message, victim, { attacker = attackerSteamID, weapon = weapon }) end -- Получение урона function PLUGIN:PlayerHurt(victim, attacker, health, damage) if not IsValid(attacker) or not attacker:IsPlayer() or attacker == victim then return end local message = string.format("%s нанёс %d урона игроку %s (осталось %d HP)", attacker:Nick(), damage, victim:Nick(), health) self:AddLog("PLAYER_HURT", message, victim, { attacker = attacker:SteamID(), damage = damage, health = health }) end -- Подключение игрока function PLUGIN:PlayerInitialSpawn(client) local message = string.format("%s подключился к серверу (SteamID: %s)", client:Nick(), client:SteamID()) self:AddLog("PLAYER_CONNECT", message, client) end -- Отключение игрока function PLUGIN:PlayerDisconnected(client) local message = string.format("%s отключился от сервера", client:Nick()) self:AddLog("PLAYER_DISCONNECT", message, client) end -- Спавн игрока function PLUGIN:PlayerSpawn(client) if not client:GetCharacter() then return end local message = string.format("%s заспавнился", client:Nick()) self:AddLog("PLAYER_SPAWN", message, client) end -- Сообщения в чат function PLUGIN:PlayerSay(client, text, teamChat) local chatType = teamChat and "[TEAM]" or "[ALL]" local message = string.format("%s %s: %s", chatType, client:Nick(), text) self:AddLog("CHAT_MESSAGE", message, client, { text = text, team = teamChat }) end -- Создание персонажа function PLUGIN:OnCharacterCreated(client, character) local message = string.format("%s создал персонажа '%s'", client:Nick(), character:GetName()) self:AddLog("CHARACTER_CREATE", message, client, { characterID = character:GetID(), characterName = character:GetName() }) end -- Удаление персонажа function PLUGIN:PreCharacterDeleted(client, character) local message = string.format("%s удалил персонажа '%s'", client:Nick(), character:GetName()) self:AddLog("CHARACTER_DELETE", message, client, { characterID = character:GetID(), characterName = character:GetName() }) end -- Смена персонажа function PLUGIN:PlayerLoadedCharacter(client, character, oldCharacter) if oldCharacter then local message = string.format("%s сменил персонажа с '%s' на '%s'", client:Nick(), oldCharacter:GetName(), character:GetName()) self:AddLog("CHARACTER_SWITCH", message, client, { oldCharacter = oldCharacter:GetName(), newCharacter = character:GetName() }) end end -- Покупка предмета function PLUGIN:CharacterVendorTraded(client, vendor, x, y, invID, price, isSell) local action = isSell and "продал" or "купил" local message = string.format("%s %s предмет за %d", client:Nick(), action, price) self:AddLog("ITEM_PURCHASE", message, client, { vendor = vendor, price = price, sell = isSell }) end -- Использование предмета function PLUGIN:OnItemTransferred(item, curInv, inventory) if item.player then local message = string.format("%s переместил предмет '%s'", item.player:Nick(), item:GetName()) self:AddLog("INVENTORY", message, item.player, { item = item:GetName(), itemID = item:GetID() }) end end -- Инициализация при загрузке плагина function PLUGIN:InitPostEntity() self:InitializeLogSystem() self:AddLog("ADMIN_COMMAND", "Система логов инициализирована") end -- ====================== -- КОМАНДЫ HELIX -- ====================== -- Регистрация CAMI разрешения if CAMI then CAMI.RegisterPrivilege({ Name = "Helix - View Server Logs", MinAccess = "admin", Description = "Позволяет просматривать логи сервера" }) CAMI.RegisterPrivilege({ Name = "Helix - Clear Server Logs", MinAccess = "superadmin", Description = "Позволяет очищать логи сервера" }) CAMI.RegisterPrivilege({ Name = "Helix - Use Handcuffs", MinAccess = "admin", Description = "Позволяет использовать наручники" }) end -- Команда для просмотра логов ix.command.Add("ViewLogs", { description = "Открыть панель просмотра логов сервера", OnCheckAccess = function(self, client) return HasLogAccess(client) end, OnRun = function(self, client) -- Отправляем команду на открытие меню на клиенте net.Start("ixServerLogs_OpenMenu") net.Send(client) return "@serverLogsOpened" end }) -- Альтернативная команда ix.command.Add("ServerLogs", { description = "Открыть панель просмотра логов сервера", OnCheckAccess = function(self, client) return HasLogAccess(client) end, OnRun = function(self, client) -- Отправляем команду на открытие меню на клиенте net.Start("ixServerLogs_OpenMenu") net.Send(client) return "@serverLogsOpened" end }) -- Команда для очистки логов ix.command.Add("ClearLogs", { description = "Очистить все логи сервера", superAdminOnly = true, OnCheckAccess = function(self, client) -- Проверка через CAMI if CAMI then return CAMI.PlayerHasAccess(client, "Helix - Clear Server Logs", function(allowed) return allowed end) end -- Fallback на стандартную проверку суперадмина return client:IsSuperAdmin() end, OnRun = function(self, client) PLUGIN.logs = {} PLUGIN:AddLog("ADMIN_COMMAND", client:Nick() .. " очистил логи сервера", client) return "@serverLogsCleared" end }) -- Команда для просмотра последних логов в консоли ix.command.Add("LastLogs", { description = "Показать последние 20 логов в консоли", adminOnly = true, arguments = { ix.type.number, }, argumentNames = {"количество"}, OnCheckAccess = function(self, client) -- Проверка через CAMI if CAMI then return CAMI.PlayerHasAccess(client, "Helix - View Server Logs", function(allowed) return allowed end) end return client:IsAdmin() end, OnRun = function(self, client, count) count = math.Clamp(count or 20, 1, 100) local logs = PLUGIN:GetLogs({limit = count}) client:ChatPrint("=== Последние " .. #logs .. " логов ===") for i, log in ipairs(logs) do local categoryName = PLUGIN.LOG_CATEGORIES[log.category].name local targetInfo = log.target and (" [" .. log.target.name .. "]") or "" client:ChatPrint(string.format("[%s][%s]%s %s", log.timeString, categoryName, targetInfo, log.message)) end return "@serverLogsDisplayed", #logs end }) -- Команда для поиска в логах ix.command.Add("SearchLogs", { description = "Найти логи по тексту или игроку", adminOnly = true, arguments = { ix.type.text, }, argumentNames = {"поисковый запрос"}, OnCheckAccess = function(self, client) -- Проверка через CAMI if CAMI then return CAMI.PlayerHasAccess(client, "Helix - View Server Logs", function(allowed) return allowed end) end return client:IsAdmin() end, OnRun = function(self, client, search) local logs = PLUGIN:GetLogs({search = search, limit = 50}) client:ChatPrint("=== Найдено логов: " .. #logs .. " ===") for i, log in ipairs(logs) do local categoryName = PLUGIN.LOG_CATEGORIES[log.category].name local targetInfo = log.target and (" [" .. log.target.name .. "]") or "" client:ChatPrint(string.format("[%s][%s]%s %s", log.timeString, categoryName, targetInfo, log.message)) end return "@serverLogsSearchResults", #logs end })