require 'lib.moonloader' local imgui = require 'mimgui' local sampev = require 'lib.samp.events' local vkeys = require 'vkeys' local encoding = require 'encoding' local inicfg = require 'inicfg' local ffi = require 'ffi' local faicons = require 'fAwesome6' local dlstatus = require('moonloader').download_status local lfs = require("lfs") -- Конфигурация скрипта local CONFIG = { iniFilename = 'RepFlowCFG.ini', scriptVersion = "3.7.1 | Premium", defaultKeyBind = 0x5A, defaultKeyBindName = 'Z', afkCooldown = 30, tag = "{1E90FF} [RepFlow]: {FFFFFF}", tagInfo = "{1E90FF} [Информация]: {FFFFFF}", } -- Утилиты ImGui local new = imgui.new -- Данные ChangeLog local changelogEntries = { { version = "3.7.2", date = "2025-03-20", description = "- Новые функции:\n" .. " - Добавлена возможность копировать текст изменений в буфер обмена прямо из вкладки ChangeLog.\n" .. " - В меню настроек добавлена опция 'Тёмная тема по умолчанию' для новых пользователей.\n" .. "- Улучшения:\n" .. " - Переработан дизайн вкладки ChangeLog: категории изменений теперь выделены цветами и иконками.\n" .. " - Оптимизирована работа с `u8` в циклических функциях для снижения нагрузки.\n" .. " - Улучшена читаемость текста: добавлены отступы и автоматическая переноска строк.\n" .. " - Обновлена система автообновлений: теперь используется библиотека `requests` вместо `downloadUrlToFile`.\n" .. "- Исправления:\n" .. " - Исправлена ошибка с некорректным отображением длинных строк в ChangeLog.\n" .. " - Устранён баг с сохранением пользовательских цветов при переключении тем.\n" .. "- Примечания:\n" .. " - Рекомендуется проверить настройки темы после обновления.\n" .. " - Благодарность всем пользователям за обратную связь!" }, { version = "3.7.1", date = "2025-03-10", description = "- Новые функции:\n" .. " - Добавлена настройка 'Автоматическое обновление при запуске' в меню 'Настройки'.\n" .. "- Улучшения:\n" .. " - Улучшена команда `/update`: добавлена индикация прогресса и до трёх повторных попыток при сбоях.\n" .. " - Оптимизирована обработка ошибок при загрузке `update.ini`: добавлено логирование ошибок.\n" .. " - Улучшена синхронизация интерфейса ImGui с состоянием обновлений.\n" .. "- Исправления:\n" .. " - Исправлена ошибка загрузки `update.ini` (cannot open file) при использовании временного файла.\n" .. " - Исправлена неработающая кнопка 'Установить обновление' из-за несинхронизации состояния.\n" .. "- Примечания:\n" .. " - Рекомендуется перезапустить MoonLoader после успешного обновления.\n" .. " - Проверьте настройки автообновления для удобства." }, { version = "3.7.0", date = "2025-02-28", description = "- Новые функции:\n" .. " - Добавлена расширенная статистика: отображение среднего времени между попытками `/ot`.\n" .. " - В меню настроек добавлены всплывающие подсказки для всех опций.\n" .. "- Улучшения:\n" .. " - Оптимизирована производительность: снижено потребление ресурсов при длительной работе.\n" .. " - Настройки теперь сохраняются автоматически при изменении (убрана кнопка 'Сохранить профиль').\n" .. " - Улучшено выравнивание текста в информационном окне.\n" .. " - Обновлён дизайн кнопки включения/выключения: добавлена анимация переключения.\n" .. " - Вкладка 'Информация' обновлена: добавлена актуальная дата и больше деталей.\n" .. "- Исправления:\n" .. " - Исправлена проблема с повторным включением ловли после ручного выключения.\n" .. " - Устранён конфликт автостарта с ручным отключением.\n" .. " - Улучшена стабильность обработки диалогов при высокой нагрузке.\n" .. " - Исправлен спам сообщений в чате при запуске скрипта.\n" .. "- Примечания:\n" .. " - Обновление включает значительные улучшения производительности." }, { version = "3.6.0", date = "2025-02-15", description = "- Новые функции:\n" .. " - Добавлены кликабельные ссылки на blast.hk и GitHub в меню 'Информация'.\n" .. "- Улучшения:\n" .. " - Оптимизирована работа скрипта: снижено потребление памяти.\n" .. " - Улучшена обработка настроек: исправлены проблемы с сохранением.\n" .. "- Исправления:\n" .. " - Удалены CEF-уведомления Аризоны, которые могли вызывать конфликты.\n" .. "- Примечания:\n" .. " - Проверьте настройки после обновления." }, { version = "3.5.0", date = "2025-02-01", description = "- Новые функции:\n" .. " - Добавлена кнопка сброса всех настроек в меню 'Настройки'.\n" .. " - Версия скрипта теперь отображается в заголовке окна ImGui.\n" .. "- Улучшения:\n" .. " - Улучшена читаемость ChangeLog: добавлены отступы и структурирование.\n" .. " - Оптимизирована производительность: минимизированы вызовы `u8` в циклических функциях.\n" .. "- Примечания:\n" .. " - Обновление направлено на улучшение пользовательского опыта." }, } -- Переменные для авто-обновлений local update_state = false local update_found = false local script_vers = 3.71 local script_vers_text = "3.7.1" local update_url = "https://raw.githubusercontent.com/Zorahm/repflow/main/update.ini?t=" .. os.time() local update_path = getWorkingDirectory() .. "/update.ini" local script_url = "https://raw.githubusercontent.com/Zorahm/repflow/main/!RepFlow.lua" local script_path = thisScript().path -- Функция проверки обновлений с использованием временного файла function check_update(callback) local maxRetries = 3 local retryCount = 0 local downloadInProgress = false local tempFilePath = os.tmpname() local function attemptDownload() if downloadInProgress then return end downloadInProgress = true logToFile("Попытка загрузки update.ini с URL: " .. update_url) downloadUrlToFile(update_url, tempFilePath, function(id, status) if status == dlstatus.STATUS_ENDDOWNLOADDATA then logToFile("update.ini успешно загружен, путь: " .. tempFilePath) local fileSize = lfs and lfs.attributes(tempFilePath, "size") or 1 logToFile("Размер загруженного файла: " .. (fileSize or "н/д")) if fileSize and fileSize > 0 then local updateIni = inicfg.load(nil, tempFilePath) if updateIni then logToFile("update.ini успешно распарсен") if updateIni.info and updateIni.info.vers and updateIni.info.vers_text then local remoteVers = tonumber(updateIni.info.vers) logToFile("Прочитано из update.ini: vers=" .. updateIni.info.vers .. ", vers_text=" .. updateIni.info.vers_text) logToFile("Сравнение версий: локальная=" .. script_vers .. ", удалённая=" .. (remoteVers or "н/д")) if remoteVers and remoteVers > script_vers then sampAddChatMessage(CONFIG.tag .. "{FFFFFF}Доступна новая версия: {32CD32}" .. updateIni.info.vers_text .. ". {FFFFFF}Введите /update.", -1) update_found = true logToFile("Найдена новая версия: " .. updateIni.info.vers_text) else logToFile("Текущая версия (" .. script_vers .. ") актуальна или новее, чем удалённая (" .. (remoteVers or "н/д") .. ")") end else logToFile("Ошибка: update.ini имеет неверную структуру или отсутствуют поля vers/vers_text") sampAddChatMessage(CONFIG.tag .. "{FF0000}Ошибка: Неверный формат файла обновлений.", -1) end else logToFile("Ошибка: Не удалось распарсить update.ini") sampAddChatMessage(CONFIG.tag .. "{FF0000}Ошибка: Не удалось прочитать файл обновлений.", -1) end else logToFile("Ошибка: Загружен пустой или повреждённый update.ini") sampAddChatMessage(CONFIG.tag .. "{FF0000}Ошибка: Файл обновлений пуст или повреждён.", -1) end os.remove(tempFilePath) downloadInProgress = false if callback then callback(update_found) end elseif status == dlstatus.STATUS_DOWNLOADERROR then retryCount = retryCount + 1 if retryCount < maxRetries then logToFile("Ошибка загрузки update.ini. Повторная попытка #" .. retryCount) wait(1000) attemptDownload() else logToFile("Не удалось загрузить update.ini после " .. maxRetries .. " попыток") sampAddChatMessage(CONFIG.tag .. "{FF0000}Не удалось проверить обновления после " .. maxRetries .. " попыток.", -1) os.remove(tempFilePath) downloadInProgress = false if callback then callback(false) end end elseif status == dlstatus.STATUS_CONNECTING or status == dlstatus.STATUS_REDIRECTING then logToFile("Загрузка update.ini: " .. (status == dlstatus.STATUS_CONNECTING and "подключение" or "перенаправление")) end end) end attemptDownload() end -- Глобальные состояния local STATE = { keyBind = CONFIG.defaultKeyBind, keyBindName = CONFIG.defaultKeyBindName, lastDialogId = nil, reportActive = false, lastOtTime = 0, active = false, startTime = 0, gameMinimized = false, wasActiveBeforePause = false, afkExitTime = 0, changingKey = false, moveWidget = false, reportAnsweredCount = 0, lastDialogTime = os.clock(), manualDisable = false, reportAttempts = 0, floodCooldown = 0, scriptStartTime = os.clock(), floodCount = 0, initialized = false, } -- Настройки интерфейса и поведения local SETTINGS = { otInterval = new.int(10), dialogTimeout = new.int(600), floodPause = new.int(10), otIntervalBuffer = new.char[5](tostring(10)), dialogTimeoutBuffer = new.char[5](tostring(600)), floodPauseBuffer = new.char[5](tostring(10)), useMilliseconds = new.bool(false), hideFloodMsg = new.bool(true), autoStartEnabled = new.bool(true), dialogHandlerEnabled = new.bool(true), infoWindowVisible = false, cursorVisible = false, mainWindowState = new.bool(false), infoWindowState = new.bool(false), activeState = new.bool(false), disableAutoStartOnToggle = false, selectedTheme = new.int(0), useFloodPause = new.bool(true), autoUpdateEnabled = new.bool(true), logActionsEnabled = new.bool(true), pauseOnFlood = new.bool(false), activeTab = new.int(0), selectedProfile = new.int(0), playSoundOnReport = new.bool(true), } -- Предопределённые цветовые темы local COLOR_THEMES = { { name = "Космос", leftPanel = imgui.ImVec4(10 / 255, 15 / 255, 30 / 255, 1.0), -- Глубокий тёмно-синий rightPanel = imgui.ImVec4(15 / 255, 20 / 255, 40 / 255, 1.0), -- Чуть светлее синий childPanel = imgui.ImVec4(5 / 255, 10 / 255, 25 / 255, 1.0), -- Ещё темнее для контраста hover = imgui.ImVec4(50 / 255, 60 / 255, 100 / 255, 1.0), -- Лёгкий голубой для акцента button = imgui.ImVec4(30 / 255, 40 / 255, 80 / 255, 1.0), -- Цвет кнопок buttonHovered = imgui.ImVec4(50 / 255, 60 / 255, 100 / 255, 1.0), -- Цвет кнопок при наведении buttonActive = imgui.ImVec4(70 / 255, 80 / 255, 120 / 255, 1.0), -- Цвет кнопок при нажатии checkMark = imgui.ImVec4(150 / 255, 200 / 255, 255 / 255, 1.0), -- Цвет галочки в чекбоксе frameBg = imgui.ImVec4(20 / 255, 25 / 255, 50 / 255, 1.0), -- Фон комбобокса и текстовых полей frameBgHovered = imgui.ImVec4(40 / 255, 45 / 255, 70 / 255, 1.0), -- Фон при наведении frameBgActive = imgui.ImVec4(60 / 255, 65 / 255, 90 / 255, 1.0), -- Фон при активации text = imgui.ImVec4(200 / 255, 220 / 255, 255 / 255, 1.0), -- Цвет текста }, { name = "Закат", leftPanel = imgui.ImVec4(50 / 255, 20 / 255, 10 / 255, 1.0), -- Тёмный бордовый rightPanel = imgui.ImVec4(70 / 255, 30 / 255, 20 / 255, 1.0), -- Тёплый красно-оранжевый childPanel = imgui.ImVec4(40 / 255, 15 / 255, 5 / 255, 1.0), -- Глубокий тёмно-красный hover = imgui.ImVec4(120 / 255, 60 / 255, 40 / 255, 1.0), -- Яркий оранжевый для выделения button = imgui.ImVec4(100 / 255, 40 / 255, 30 / 255, 1.0), -- Цвет кнопок buttonHovered = imgui.ImVec4(120 / 255, 60 / 255, 40 / 255, 1.0), -- Цвет кнопок при наведении buttonActive = imgui.ImVec4(140 / 255, 80 / 255, 50 / 255, 1.0), -- Цвет кнопок при нажатии checkMark = imgui.ImVec4(255 / 255, 150 / 255, 100 / 255, 1.0), -- Цвет галочки в чекбоксе frameBg = imgui.ImVec4(80 / 255, 30 / 255, 20 / 255, 1.0), -- Фон комбобокса и текстовых полей frameBgHovered = imgui.ImVec4(100 / 255, 50 / 255, 30 / 255, 1.0), -- Фон при наведении frameBgActive = imgui.ImVec4(120 / 255, 70 / 255, 40 / 255, 1.0), -- Фон при активации text = imgui.ImVec4(255 / 255, 200 / 255, 180 / 255, 1.0), -- Цвет текста }, { name = "Неон", leftPanel = imgui.ImVec4(20 / 255, 40 / 255, 20 / 255, 1.0), -- Тёмный зелёный фон rightPanel = imgui.ImVec4(30 / 255, 50 / 255, 30 / 255, 1.0), -- Чуть светлее зелёный childPanel = imgui.ImVec4(15 / 255, 30 / 255, 15 / 255, 1.0), -- Контрастный тёмный hover = imgui.ImVec4(0 / 255, 200 / 255, 150 / 255, 1.0), -- Яркий циан для кнопок button = imgui.ImVec4(40 / 255, 80 / 255, 40 / 255, 1.0), -- Цвет кнопок buttonHovered = imgui.ImVec4(0 / 255, 200 / 255, 150 / 255, 1.0), -- Цвет кнопок при наведении buttonActive = imgui.ImVec4(0 / 255, 220 / 255, 170 / 255, 1.0), -- Цвет кнопок при нажатии checkMark = imgui.ImVec4(0 / 255, 255 / 255, 200 / 255, 1.0), -- Цвет галочки в чекбоксе frameBg = imgui.ImVec4(30 / 255, 60 / 255, 30 / 255, 1.0), -- Фон комбобокса и текстовых полей frameBgHovered = imgui.ImVec4(40 / 255, 80 / 255, 40 / 255, 1.0), -- Фон при наведении frameBgActive = imgui.ImVec4(50 / 255, 100 / 255, 50 / 255, 1.0), -- Фон при активации text = imgui.ImVec4(180 / 255, 255 / 255, 220 / 255, 1.0), -- Цвет текста }, { name = "Лаванда", leftPanel = imgui.ImVec4(40 / 255, 30 / 255, 60 / 255, 1.0), -- Тёмный лавандовый rightPanel = imgui.ImVec4(50 / 255, 40 / 255, 80 / 255, 1.0), -- Мягкий фиолетовый childPanel = imgui.ImVec4(30 / 255, 20 / 255, 50 / 255, 1.0), -- Тёмный для панелей hover = imgui.ImVec4(100 / 255, 80 / 255, 140 / 255, 1.0), -- Светлый лавандовый акцент button = imgui.ImVec4(60 / 255, 50 / 255, 100 / 255, 1.0), -- Цвет кнопок buttonHovered = imgui.ImVec4(100 / 255, 80 / 255, 140 / 255, 1.0), -- Цвет кнопок при наведении buttonActive = imgui.ImVec4(120 / 255, 100 / 255, 160 / 255, 1.0), -- Цвет кнопок при нажатии checkMark = imgui.ImVec4(180 / 255, 150 / 255, 220 / 255, 1.0), -- Цвет галочки в чекбоксе frameBg = imgui.ImVec4(40 / 255, 30 / 255, 70 / 255, 1.0), -- Фон комбобокса и текстовых полей frameBgHovered = imgui.ImVec4(60 / 255, 50 / 255, 90 / 255, 1.0), -- Фон при наведении frameBgActive = imgui.ImVec4(80 / 255, 70 / 255, 110 / 255, 1.0), -- Фон при активации text = imgui.ImVec4(220 / 255, 200 / 255, 255 / 255, 1.0), -- Цвет текста }, { name = "Графит", leftPanel = imgui.ImVec4(30 / 255, 30 / 255, 30 / 255, 1.0), -- Тёмно-серый rightPanel = imgui.ImVec4(40 / 255, 40 / 255, 40 / 255, 1.0), -- Средний серый childPanel = imgui.ImVec4(20 / 255, 20 / 255, 20 / 255, 1.0), -- Очень тёмный серый hover = imgui.ImVec4(80 / 255, 80 / 255, 80 / 255, 1.0), -- Светло-серый для кнопок button = imgui.ImVec4(50 / 255, 50 / 255, 50 / 255, 1.0), -- Цвет кнопок buttonHovered = imgui.ImVec4(80 / 255, 80 / 255, 80 / 255, 1.0), -- Цвет кнопок при наведении buttonActive = imgui.ImVec4(100 / 255, 100 / 255, 100 / 255, 1.0), -- Цвет кнопок при нажатии checkMark = imgui.ImVec4(180 / 255, 180 / 255, 180 / 255, 1.0), -- Цвет галочки в чекбоксе frameBg = imgui.ImVec4(30 / 255, 30 / 255, 30 / 255, 1.0), -- Фон комбобокса и текстовых полей frameBgHovered = imgui.ImVec4(50 / 255, 50 / 255, 50 / 255, 1.0), -- Фон при наведении frameBgActive = imgui.ImVec4(70 / 255, 70 / 255, 70 / 255, 1.0), -- Фон при активации text = imgui.ImVec4(200 / 255, 200 / 255, 200 / 255, 1.0), -- Цвет текста }, } -- Текущая цветовая схема (по умолчанию - первая тема) local COLORS = COLOR_THEMES[1] -- Разрешение экрана local sw, sh = getScreenResolution() -- Кодировка encoding.default = 'CP1251' local u8 = encoding.UTF8 -- Конфигурация по умолчанию local defaultConfig = { main = { keyBind = string.format("0x%X", CONFIG.defaultKeyBind), keyBindName = CONFIG.defaultKeyBindName, otInterval = 10, useMilliseconds = false, dialogTimeout = 600, floodPause = 10, dialogHandlerEnabled = true, autoStartEnabled = true, otklflud = false, selectedTheme = 0, useFloodPause = true, autoUpdateEnabled = true, logActionsEnabled = true, selectedProfile = 0, playSoundOnReport = true, }, widget = { posX = 400, posY = 400, } } -- Загрузка и применение конфигурации local ini = inicfg.load(defaultConfig, CONFIG.iniFilename) STATE.keyBind = tonumber(ini.main.keyBind) or CONFIG.defaultKeyBind STATE.keyBindName = ini.main.keyBindName or CONFIG.defaultKeyBindName SETTINGS.otInterval[0] = tonumber(ini.main.otInterval) or 10 SETTINGS.useMilliseconds[0] = ini.main.useMilliseconds == true SETTINGS.dialogTimeout[0] = tonumber(ini.main.dialogTimeout) or 600 SETTINGS.floodPause[0] = tonumber(ini.main.floodPause) or 10 SETTINGS.dialogHandlerEnabled[0] = ini.main.dialogHandlerEnabled == true SETTINGS.autoStartEnabled[0] = ini.main.autoStartEnabled == true SETTINGS.hideFloodMsg[0] = ini.main.otklflud == true SETTINGS.selectedTheme[0] = tonumber(ini.main.selectedTheme) or 0 COLORS = COLOR_THEMES[SETTINGS.selectedTheme[0] + 1] -- Применяем загруженную тему SETTINGS.useFloodPause[0] = ini.main.useFloodPause == true SETTINGS.autoUpdateEnabled[0] = ini.main.autoUpdateEnabled == true SETTINGS.logActionsEnabled[0] = ini.main.logActionsEnabled == true SETTINGS.selectedProfile[0] = ini.main.selectedProfile or 0 SETTINGS.playSoundOnReport[0] = ini.main.playSoundOnReport == true SETTINGS.selectedTheme[0] = ini.main.selectedTheme or 0 -- Основной цикл скрипта function main() if not isSampLoaded() or not isSampfuncsLoaded() then return end while not isSampAvailable() do wait(100) end if STATE.initialized then return end STATE.initialized = true -- Загружаем звуковой файл local soundPath = getWorkingDirectory() .. "\\report_sound.mp3" local soundUrl = "https://github.com/Zorahm/repflow/raw/refs/heads/main/report_sound.mp3" -- Замени на свой URL if doesFileExist(soundPath) then reportSound = loadAudioStream(soundPath) if reportSound then sampAddChatMessage(CONFIG.tag .. "Звуковой файл успешно загружен.", -1) logToFile("Звуковой файл успешно загружен: " .. soundPath) setAudioStreamVolume(reportSound, 1.0) else sampAddChatMessage(CONFIG.tag .. "{FF0000}Ошибка: Не удалось загрузить звуковой файл: " .. soundPath, -1) logToFile("Ошибка: Не удалось загрузить звуковой файл: " .. soundPath) end else sampAddChatMessage(CONFIG.tag .. "Звуковой файл report_sound.mp3 не найден. Пытаемся скачать...", -1) logToFile("Звуковой файл report_sound.mp3 не найден, пытаемся скачать с " .. soundUrl) -- Пытаемся скачать файл downloadUrlToFile(soundUrl, soundPath, function(id, status) if status == dlstatus.STATUS_ENDDOWNLOADDATA then -- Проверяем, что файл действительно скачан if doesFileExist(soundPath) then reportSound = loadAudioStream(soundPath) if reportSound then sampAddChatMessage(CONFIG.tag .. "Звуковой файл успешно скачан и загружен:" , -1) logToFile("Звуковой файл успешно скачан и загружен: " .. soundPath) setAudioStreamVolume(reportSound, 1.0) else sampAddChatMessage(CONFIG.tag .. "{FF0000}Ошибка: Не удалось загрузить скачанный звуковой файл: " .. soundPath, -1) logToFile("Ошибка: Не удалось загрузить скачанный звуковой файл: " .. soundPath) end else sampAddChatMessage(CONFIG.tag .. "{FF0000}Ошибка: Файл не был скачан. Поместите report_sound.mp3 в папку moonloader вручную.", -1) logToFile("Ошибка: Файл не был скачан по URL " .. soundUrl) end STATE.soundDownloadProgress = 0 -- Сбрасываем прогресс elseif status == dlstatus.STATUS_DOWNLOADERROR then sampAddChatMessage(CONFIG.tag .. "{FF0000}Ошибка: Не удалось скачать звуковой файл. Поместите report_sound.mp3 в папку moonloader вручную.", -1) logToFile("Ошибка: Не удалось скачать звуковой файл с " .. soundUrl) STATE.soundDownloadProgress = 0 -- Сбрасываем прогресс elseif status == dlstatus.STATUS_DOWNLOADINGDATA then STATE.soundDownloadProgress = (id.progress * 100) sampAddChatMessage(CONFIG.tag .. "Загрузка звукового файла: " .. math.floor(STATE.soundDownloadProgress) .. "%", -1) end end) end sampRegisterChatCommand("arep", cmd_arep) sampRegisterChatCommand("update", cmd_update) sampRegisterChatCommand("showinfo", showInfoWindow) sampRegisterChatCommand("hideinfo", showInfoWindowOff) sampAddChatMessage(CONFIG.tag .. "Скрипт {00FF00}загружен.{FFFFFF} Меню: {00FF00}/arep{FFFFFF}", -1) logToFile("Скрипт загружен") loadSettingsFromProfile() -- Проверка обновлений при старте if SETTINGS.autoUpdateEnabled[0] then sampAddChatMessage(CONFIG.tag .. "Проверка обновлений при запуске...", -1) logToFile("Запущена проверка обновлений при старте") check_update(function(found) if not found then sampAddChatMessage(CONFIG.tag .. "Обновления не найдены или проверка завершена с ошибкой.", -1) logToFile("Проверка обновлений завершена: обновлений нет или ошибка") end end) end local lastMoveCheck = 0 local moveCheckInterval = 50 while true do wait(0) checkPauseAndDisableAutoStart() checkAutoStart() imgui.Process = SETTINGS.mainWindowState[0] and not STATE.gameMinimized if update_state then downloadUrlToFile(script_url, script_path, function(id, status) if status == dlstatus.STATUS_ENDDOWNLOADDATA then STATE.updateProgress = 100 sampAddChatMessage(CONFIG.tag .. "{FFFFFF}Скрипт {32CD32}успешно {FFFFFF}обновлён. Перезапустите MoonLoader.", -1) logToFile("Скрипт успешно обновлён") thisScript():reload() elseif status == dlstatus.STATUS_DOWNLOADERROR then STATE.updateProgress = 0 sampAddChatMessage(CONFIG.tag .. "{FF0000}Ошибка: Не удалось скачать обновление.", -1) logToFile("Ошибка при скачивании обновления") update_state = false elseif status == dlstatus.STATUS_DOWNLOADINGDATA then STATE.updateProgress = (id.progress * 100) sampAddChatMessage(CONFIG.tag .. "Обновление: " .. math.floor(STATE.updateProgress) .. "%", -1) end end) end if STATE.moveWidget then local cursorX, cursorY = getCursorPos() ini.widget.posX = cursorX ini.widget.posY = cursorY local currentTime = os.clock() * 1000 if currentTime - lastMoveCheck >= moveCheckInterval then if isKeyJustPressed(0x20) then STATE.moveWidget = false sampToggleCursor(false) saveWindowSettings() end lastMoveCheck = currentTime end end if STATE.active or STATE.moveWidget then showInfoWindow() else showInfoWindowOff() end if not STATE.changingKey and isKeyJustPressed(STATE.keyBind) and not isSampfuncsConsoleActive() and not sampIsChatInputActive() and not sampIsDialogActive() and not isPauseMenuActive() then onToggleActive() end if STATE.active and STATE.floodCooldown < os.clock() * 1000 then local currentTime = os.clock() * 1000 local interval = SETTINGS.useMilliseconds[0] and SETTINGS.otInterval[0] or (SETTINGS.otInterval[0] * 1000) if currentTime - STATE.lastOtTime >= interval then STATE.reportAttempts = STATE.reportAttempts + 1 sampSendChat('/ot') logToFile("Отправка /ot, попытка #" .. STATE.reportAttempts) STATE.lastOtTime = currentTime end else STATE.startTime = os.clock() end end end -- Сброс ввода ImGui function resetIO() local io = imgui.GetIO() for i = 0, 511 do io.KeysDown[i] = false end for i = 0, 4 do io.MouseDown[i] = false end io.KeyCtrl = false io.KeyShift = false io.KeyAlt = false io.KeySuper = false end function imgui.ShowHelpMarker(desc) imgui.TextDisabled("(?)") if imgui.IsItemHovered() then imgui.BeginTooltip() imgui.PushTextWrapPos(imgui.GetFontSize() * 35.0) imgui.TextUnformatted(desc) imgui.PopTextWrapPos() imgui.EndTooltip() end end -- Активация режима перемещения окна function startMovingWindow() STATE.moveWidget = true showInfoWindow() sampToggleCursor(true) SETTINGS.mainWindowState[0] = false sampAddChatMessage(CONFIG.tagInfo .. '{FFFF00}Режим перемещения окна активирован. Нажмите "Пробел" для подтверждения.', -1) end -- Инициализация ImGui imgui.OnInitialize(function() imgui.GetIO().IniFilename = nil local config = imgui.ImFontConfig() config.MergeMode = true config.PixelSnapH = true local iconRanges = new.ImWchar[3](faicons.min_range, faicons.max_range, 0) imgui.GetIO().Fonts:AddFontFromMemoryCompressedBase85TTF(faicons.get_font_data_base85('solid'), 14, config, iconRanges) decor() end) -- Настройка стилей ImGui function decor() imgui.SwitchContext() local style = imgui.GetStyle() style.WindowPadding = imgui.ImVec2(12, 12) style.WindowRounding = 12.0 style.ChildRounding = 10.0 style.FramePadding = imgui.ImVec2(8, 6) style.FrameRounding = 10.0 style.ItemSpacing = imgui.ImVec2(10, 10) style.ItemInnerSpacing = imgui.ImVec2(10, 10) style.ScrollbarSize = 12.0 style.ScrollbarRounding = 10.0 style.GrabRounding = 10.0 style.PopupRounding = 10.0 style.WindowTitleAlign = imgui.ImVec2(0.5, 0.5) style.ButtonTextAlign = imgui.ImVec2(0.5, 0.5) end -- Обработка сообщений сервера function sampev.onServerMessage(color, text) if text:find('%[(%W+)%] от (%w+_%w+)%[(%d+)%]:') and STATE.active then sampSendChat('/ot') end return filterFloodMessage(text) end -- Переключение состояния автоловли function onToggleActive() STATE.active = not STATE.active STATE.manualDisable = not STATE.active SETTINGS.disableAutoStartOnToggle = not STATE.active local status = STATE.active and '{00FF00}включена' or '{FF0000}выключена' local statusArz = STATE.active and 'включена' or 'выключена' logToFile("Ловля " .. statusArz) if SETTINGS.activeState then SETTINGS.activeState[0] = STATE.active end end function onKeyDown(key) if not STATE.changingKey and key == STATE.keyBind and not isSampfuncsConsoleActive() and not sampIsChatInputActive() and not sampIsDialogActive() and not isPauseMenuActive() then onToggleActive() end end -- Сохранение настроек окна function saveWindowSettings() ini.widget.posX = ini.widget.posX or 400 ini.widget.posY = ini.widget.posY or 400 inicfg.save(ini, CONFIG.iniFilename) sampAddChatMessage(CONFIG.tagInfo .. '{00FF00}Положение окна сохранено!', -1) end function sampev.onShowDialog(dialogId, style, title, button1, button2, text) if dialogId == 1334 then if SETTINGS.dialogHandlerEnabled[0] then STATE.lastDialogTime = os.clock() -- Сброс таймера при появлении диалога STATE.reportAnsweredCount = STATE.reportAnsweredCount + 1 -- Увеличиваем счетчик -- Воспроизводим звук, если настройка включена if SETTINGS.playSoundOnReport[0] and reportSound then setAudioStreamState(reportSound, 1) -- Воспроизводим звук logToFile("Воспроизведён звук при принятии репорта") elseif SETTINGS.playSoundOnReport[0] and not reportSound then sampAddChatMessage(CONFIG.tag .. "{FF0000}Ошибка: Звуковой файл не загружен (ожидается " .. getWorkingDirectory() .. "\\report_sound.mp3).", -1) logToFile("Ошибка: Звуковой файл не загружен (ожидается " .. getWorkingDirectory() .. "\\report_sound.mp3)") end sampAddChatMessage(CONFIG.tag .. '{00FF00}Репорт принят. Отвечено репорта: ' .. STATE.reportAnsweredCount, -1) logToFile("Репорт принят, всего: " .. STATE.reportAnsweredCount) -- Логируем информацию о репорте local playerName = text:match("(%w+_%w+)%[") or "Неизвестный" logReport(playerName, text) if STATE.active then STATE.active = false sampAddChatMessage(CONFIG.tag .. "Ловля отключена из-за принятия репорта", -1) logToFile("Ловля отключена из-за принятия репорта") end else -- Логируем, что диалог проигнорирован, но ловля продолжается logToFile("Диалог 1334 проигнорирован (обработка отключена), ловля активна: " .. tostring(STATE.active)) sampAddChatMessage(CONFIG.tag .. "Диалог репорта обнаружен, но обработка отключена. Ловля продолжается.", -1) end end end -- Проверка автостарта ловли function checkAutoStart() local currentTime = os.clock() if SETTINGS.autoStartEnabled[0] and not STATE.active and not STATE.gameMinimized and (STATE.afkExitTime == 0 or currentTime - STATE.afkExitTime >= CONFIG.afkCooldown) then if not SETTINGS.disableAutoStartOnToggle and (currentTime - STATE.lastDialogTime) > SETTINGS.dialogTimeout[0] then STATE.active = true sampAddChatMessage(CONFIG.tag .. "Ловля включена по тайм-ауту", -1) logToFile("Ловля включена по таймауту") end end end -- Сохранение общих настроек function saveSettings() -- Сохраняем настройки в ini.main ini.main.otInterval = SETTINGS.otInterval[0] ini.main.dialogTimeout = SETTINGS.dialogTimeout[0] ini.main.floodPause = SETTINGS.floodPause[0] ini.main.useMilliseconds = SETTINGS.useMilliseconds[0] == true ini.main.dialogHandlerEnabled = SETTINGS.dialogHandlerEnabled[0] == true ini.main.autoStartEnabled = SETTINGS.autoStartEnabled[0] == true ini.main.otklflud = SETTINGS.hideFloodMsg[0] == true ini.main.selectedTheme = SETTINGS.selectedTheme[0] ini.main.useFloodPause = SETTINGS.useFloodPause[0] == true ini.main.autoUpdateEnabled = SETTINGS.autoUpdateEnabled[0] == true ini.main.logActionsEnabled = SETTINGS.logActionsEnabled[0] == true ini.main.selectedProfile = SETTINGS.selectedProfile[0] ini.main.pauseOnFlood = SETTINGS.pauseOnFlood[0] == true ini.main.playSoundOnReport = SETTINGS.playSoundOnReport[0] == true -- Сохраняем в общий файл настроек inicfg.save(ini, CONFIG.iniFilename) -- Автоматически сохраняем в текущий профиль local profileName = "profile_" .. (SETTINGS.selectedProfile[0] + 1) inicfg.save(ini, "moonloader/" .. profileName .. ".ini") logToFile("Настройки автоматически сохранены в профиль: Профиль " .. (SETTINGS.selectedProfile[0] + 1)) end -- Отрисовка ссылки function imgui.Link(link, text, customColors, icon, isActive) -- Устанавливаем значения по умолчанию text = text or link isActive = isActive == nil and true or isActive -- По умолчанию ссылка активна customColors = customColors or { isActive and COLORS.text or imgui.ImVec4(0.6, 0.6, 0.6, 1.0), -- Серый цвет для неактивной ссылки isActive and COLORS.hover or imgui.ImVec4(0.6, 0.6, 0.6, 1.0) -- Без изменения цвета при наведении для неактивной } icon = icon or faicons('link') -- Иконка по умолчанию -- Вычисляем размеры текста local tSize = imgui.CalcTextSize(text) local iconSize = icon and imgui.CalcTextSize(icon) or imgui.ImVec2(0, 0) local totalSize = imgui.ImVec2(tSize.x + (icon and (iconSize.x + 5) or 0), tSize.y) -- Получаем текущую позицию курсора и DrawList local p = imgui.GetCursorScreenPos() local DL = imgui.GetWindowDrawList() -- Плавная анимация цвета при наведении (только для активной ссылки) local isHovered = isActive and imgui.IsItemHovered() or false local colorTransition = imgui.GetTime() * 5 -- Скорость анимации -- Извлекаем компоненты цвета local r1, g1, b1, a1 local r2, g2, b2, a2 -- Проверяем, что передали в customColors if type(customColors[1]) == "number" then r1, g1, b1, a1 = imgui.ColorConvertU32ToFloat4(customColors[1]) else r1, g1, b1, a1 = customColors[1].x, customColors[1].y, customColors[1].z, customColors[1].w end if type(customColors[2]) == "number" then r2, g2, b2, a2 = imgui.ColorConvertU32ToFloat4(customColors[2]) else r2, g2, b2, a2 = customColors[2].x, customColors[2].y, customColors[2].z, customColors[2].w end -- Вычисляем анимированный цвет local t = isHovered and (0.5 + math.sin(colorTransition) * 0.5) or (0.5 - math.sin(colorTransition) * 0.5) local r = r1 + (r2 - r1) * t local g = g1 + (g2 - g1) * t local b = b1 + (b2 - b1) * t local a = a1 + (a2 - a1) * t -- Создаём ImVec4 для передачи в ColorConvertFloat4ToU32 local animatedColorVec = imgui.ImVec4(r, g, b, a) local animatedColor = imgui.ColorConvertFloat4ToU32(animatedColorVec) -- Создаём невидимую кнопку для взаимодействия (только если ссылка активна) if isActive then if imgui.InvisibleButton('##' .. link, totalSize) then -- Пытаемся открыть ссылку local success, err = pcall(function() os.execute('explorer "' .. link .. '"') end) if not success then sampAddChatMessage(CONFIG.tag .. "{FF0000}Ошибка: Не удалось открыть ссылку: " .. link, -1) logToFile("Ошибка открытия ссылки: " .. link .. " | Ошибка: " .. tostring(err)) else logToFile("Открыта ссылка: " .. link) end end else -- Для неактивной ссылки всё равно создаём невидимую область, чтобы сохранить выравнивание imgui.Dummy(totalSize) end -- Отображаем иконку, если она есть if icon then DL:AddText(p, animatedColor, icon) p.x = p.x + iconSize.x + 5 -- Смещаем текст после иконки end -- Отображаем текст ссылки DL:AddText(p, animatedColor, text) -- Подчёркивание DL:AddLine( imgui.ImVec2(p.x - (icon and (iconSize.x + 5) or 0), p.y + tSize.y), imgui.ImVec2(p.x + tSize.x, p.y + tSize.y), animatedColor, 1.0 ) -- Подсказка if isActive and imgui.IsItemHovered() then imgui.BeginTooltip() imgui.Text(u8"Нажмите, чтобы открыть\nПКМ для копирования") imgui.EndTooltip() elseif not isActive and imgui.IsItemHovered() then imgui.BeginTooltip() imgui.Text(u8"Ссылка неактивна") imgui.EndTooltip() end -- Контекстное меню для копирования ссылки (только для активной ссылки) if isActive then if imgui.BeginPopupContextItem("LinkContextMenu##" .. link) then if imgui.Selectable(u8"Скопировать ссылку") then -- Заменяем MenuItem на Selectable imgui.SetClipboardText(link) sampAddChatMessage(CONFIG.tag .. "Ссылка скопирована в буфер обмена: " .. link, -1) logToFile("Ссылка скопирована в буфер обмена: " .. link) end imgui.EndPopup() end -- Открываем контекстное меню при правом клике if imgui.IsItemHovered() and imgui.IsMouseClicked(1) then imgui.OpenPopup("LinkContextMenu##" .. link) end end end -- Команда открытия меню function cmd_arep(arg) SETTINGS.mainWindowState[0] = not SETTINGS.mainWindowState[0] imgui.Process = SETTINGS.mainWindowState[0] end function cmd_update(arg) if update_state then sampAddChatMessage(CONFIG.tag .. "{FF0000}Обновление уже выполняется. Подождите завершения.", -1) return end if not update_found then sampAddChatMessage(CONFIG.tag .. "Проверка доступных обновлений...", -1) logToFile("Пользователь инициировал проверку обновлений через /update") check_update(function(found) update_found = found if found then sampAddChatMessage(CONFIG.tag .. "{FFFFFF}Найдена новая версия. Начинается обновление...", -1) logToFile("Обновление найдено, запуск performUpdate") performUpdate() else sampAddChatMessage(CONFIG.tag .. "{FF0000}Обновлений нет или проверка завершилась с ошибкой.", -1) logToFile("Обновления не найдены или ошибка проверки") end end) else sampAddChatMessage(CONFIG.tag .. "{FFFFFF}Начинается обновление...", -1) logToFile("Пользователь запустил обновление") performUpdate() end end -- Функция выполнения обновления function performUpdate() update_state = true STATE.updateProgress = 0 local maxRetries = 3 local retryCount = 0 local function attemptDownload() downloadUrlToFile(script_url, script_path, function(id, status) if status == dlstatus.STATUS_ENDDOWNLOADDATA then STATE.updateProgress = 100 sampAddChatMessage(CONFIG.tag .. "{FFFFFF}Скрипт {32CD32}успешно {FFFFFF}обновлён. Перезапустите MoonLoader.", -1) logToFile("Скрипт успешно обновлён") update_state = false update_found = false -- Сбрасываем после успешного обновления thisScript():reload() elseif status == dlstatus.STATUS_DOWNLOADERROR then retryCount = retryCount + 1 if retryCount < maxRetries then sampAddChatMessage(CONFIG.tag .. "Ошибка загрузки. Повторная попытка #" .. retryCount, -1) logToFile("Ошибка загрузки обновления, попытка #" .. retryCount) wait(1000) attemptDownload() else STATE.updateProgress = 0 sampAddChatMessage(CONFIG.tag .. "{FF0000}Не удалось обновить после " .. maxRetries .. " попыток.", -1) logToFile("Не удалось обновить после " .. maxRetries .. " попыток") update_state = false end elseif status == dlstatus.STATUS_DOWNLOADINGDATA then STATE.updateProgress = (id.progress * 100) sampAddChatMessage(CONFIG.tag .. "Обновление: " .. math.floor(STATE.updateProgress) .. "%", -1) end end) end attemptDownload() end -- Вкладка "Флудер" function drawMainTab() local panelColor = COLORS.childPanel imgui.Text(faicons('gear') .. u8" Настройки / " .. faicons('message') .. u8" Флудер") imgui.Separator() imgui.PushStyleColor(imgui.Col.ChildBg, panelColor) if imgui.BeginChild("Flooder", imgui.ImVec2(0, 150), true) then imgui.PushItemWidth(100) if imgui.Checkbox(u8'Использовать миллисекунды', SETTINGS.useMilliseconds) then ini.main.useMilliseconds = SETTINGS.useMilliseconds[0] saveSettings() -- Сохраняем все настройки end imgui.PopItemWidth() imgui.Text(u8'Интервал отправки /ot (' .. (SETTINGS.useMilliseconds[0] and u8'мс' or u8'сек') .. '):') imgui.Text(u8'Текущий: ' .. SETTINGS.otInterval[0]) imgui.PushItemWidth(45) imgui.InputText(u8'##otIntervalInput', SETTINGS.otIntervalBuffer, ffi.sizeof(SETTINGS.otIntervalBuffer)) imgui.SameLine() if imgui.Button(faicons('floppy_disk') .. u8" Сохранить") then local newValue = tonumber(ffi.string(SETTINGS.otIntervalBuffer)) if newValue then SETTINGS.otInterval[0] = newValue saveSettings() -- Сохраняем все настройки sampAddChatMessage(CONFIG.tagInfo .. "Интервал сохранён: {32CD32}" .. newValue, -1) else sampAddChatMessage(CONFIG.tagInfo .. "Ошибка: {32CD32}Введите число.", -1) end end imgui.PopItemWidth() end imgui.EndChild() imgui.PopStyleColor() imgui.PushStyleColor(imgui.Col.ChildBg, panelColor) if imgui.BeginChild("FloodPause", imgui.ImVec2(0, 100), true) then imgui.Text(u8'Пауза после флуда (сек):') imgui.Text(u8'Текущая: ' .. SETTINGS.floodPause[0]) imgui.PushItemWidth(45) imgui.InputText(u8'##floodPauseInput', SETTINGS.floodPauseBuffer, ffi.sizeof(SETTINGS.floodPauseBuffer)) imgui.SameLine() if imgui.Button(faicons('floppy_disk') .. u8" Сохранить паузу") then local newValue = tonumber(ffi.string(SETTINGS.floodPauseBuffer)) if newValue and newValue >= 1 and newValue <= 60 then SETTINGS.floodPause[0] = newValue saveSettings() -- Сохраняем все настройки sampAddChatMessage(CONFIG.tagInfo .. "Пауза сохранена: {32CD32}" .. newValue .. " сек", -1) else sampAddChatMessage(CONFIG.tagInfo .. "Ошибка: {32CD32}1-60 сек.", -1) end end imgui.PopItemWidth() end imgui.EndChild() imgui.PopStyleColor() imgui.PushStyleColor(imgui.Col.ChildBg, panelColor) if imgui.BeginChild("InfoFlooder", imgui.ImVec2(0, 65), true) then imgui.Text(u8'Скрипт также ищет надпись в чате [Репорт] от Имя_Фамилия.') imgui.Text(u8'Флудер нужен для дополнительного способа ловли репорта.') end imgui.EndChild() imgui.PopStyleColor() end -- Вкладка "Настройки" function drawSettingsTab() local panelColor = COLORS.childPanel imgui.Text(faicons('gear') .. u8" Настройки / " .. faicons('sliders') .. u8" Основные настройки") imgui.Separator() -- Клавиша активации imgui.PushStyleColor(imgui.Col.ChildBg, panelColor) if imgui.BeginChild("KeyBind", imgui.ImVec2(0, 60), true) then imgui.Text(u8'Текущая клавиша активации:') imgui.SameLine() if imgui.Button(u8'' .. STATE.keyBindName) then STATE.changingKey = true sampAddChatMessage(CONFIG.tag .. "Нажмите новую клавишу активации", -1) end imgui.SameLine() imgui.ShowHelpMarker(u8"Клавиша для включения/выключения ловли. Нажмите кнопку, чтобы изменить.") end imgui.EndChild() imgui.PopStyleColor() -- Обработка диалогов imgui.PushStyleColor(imgui.Col.ChildBg, panelColor) if imgui.BeginChild("DialogOptions", imgui.ImVec2(0, 110), true) then imgui.Text(u8"Обработка диалогов") if imgui.Checkbox(u8'Обрабатывать диалоги', SETTINGS.dialogHandlerEnabled) then saveSettings() -- Сохраняем все настройки end imgui.SameLine() imgui.ShowHelpMarker(u8"Если включено, скрипт будет обрабатывать диалог репорта (ID 1334) и отключать ловлю при его появлении.") if imgui.Checkbox(u8'Автостарт ловли', SETTINGS.autoStartEnabled) then saveSettings() -- Сохраняем все настройки end imgui.SameLine() imgui.ShowHelpMarker(u8"Автоматически включает ловлю после тайм-аута, если нет активности.") end imgui.EndChild() imgui.PopStyleColor() -- Настройки флуда imgui.PushStyleColor(imgui.Col.ChildBg, panelColor) if imgui.BeginChild("FloodOptions", imgui.ImVec2(0, 110), true) then imgui.Text(u8"Настройки флуда") if imgui.Checkbox(u8'Скрыть "Не флуди"', SETTINGS.hideFloodMsg) then saveSettings() -- Сохраняем все настройки end imgui.SameLine() imgui.ShowHelpMarker(u8"Скрывает сообщения о флуде в чате и активирует паузу при обнаружении флуда.") if imgui.Checkbox(u8'Использовать паузу после флуда', SETTINGS.useFloodPause) then saveSettings() -- Сохраняем все настройки sampAddChatMessage(CONFIG.tagInfo .. "Пауза после флуда: {32CD32}" .. (SETTINGS.useFloodPause[0] and "включена" or "выключена"), -1) end imgui.SameLine() imgui.ShowHelpMarker(u8"Приостанавливает отправку /ot на заданное время после сообщения о флуде.") end imgui.EndChild() imgui.PopStyleColor() -- Настройки звука imgui.PushStyleColor(imgui.Col.ChildBg, panelColor) if imgui.BeginChild("SoundOptions", imgui.ImVec2(0, 80), true) then imgui.Text(u8"Настройки звука") if imgui.Checkbox(u8'Воспроизводить звук при репорте', SETTINGS.playSoundOnReport) then saveSettings() -- Сохраняем все настройки sampAddChatMessage(CONFIG.tagInfo .. "Звук при репорте: {32CD32}" .. (SETTINGS.playSoundOnReport[0] and "включён" or "выключен"), -1) end imgui.SameLine() imgui.ShowHelpMarker(u8"Воспроизводит звук (report_sound.mp3) при принятии репорта. Файл должен быть в папке moonloader.") end imgui.EndChild() imgui.PopStyleColor() -- Обновления imgui.PushStyleColor(imgui.Col.ChildBg, panelColor) if imgui.BeginChild("UpdateOptions", imgui.ImVec2(0, 150), true) then imgui.Text(u8"Обновления") if imgui.Checkbox(u8'Автообновление при запуске', SETTINGS.autoUpdateEnabled) then saveSettings() sampAddChatMessage(CONFIG.tagInfo .. "Автообновление: {32CD32}" .. (SETTINGS.autoUpdateEnabled[0] and "включено" or "выключено"), -1) end imgui.SameLine() imgui.ShowHelpMarker(u8"Проверяет наличие обновлений при запуске скрипта.") imgui.Text(u8'Ручная проверка:') imgui.SameLine() if imgui.Button(u8'Проверить') then sampAddChatMessage(CONFIG.tag .. "Проверка обновлений...", -1) logToFile("Ручная проверка обновлений инициирована") check_update(function(found) update_found = found -- Синхронизируем состояние imgui.Process = true -- Принудительная перерисовка if not found then sampAddChatMessage(CONFIG.tag .. "{FF0000}Обновления не найдены или произошла ошибка.", -1) logToFile("Проверка обновлений завершена: обновлений нет или ошибка") end end) end imgui.SameLine() imgui.ShowHelpMarker(u8"Проверяет обновления прямо сейчас.") if update_state then imgui.Text(u8"Прогресс обновления: " .. math.floor(STATE.updateProgress) .. "%") imgui.ProgressBar(STATE.updateProgress / 100, imgui.ImVec2(-1, 15), u8"") elseif update_found then if imgui.Button(u8"Установить обновление") then sampAddChatMessage(CONFIG.tag .. "{FFFFFF}Начинается обновление...", -1) logToFile("Пользователь инициировал установку обновления") cmd_update("") end else imgui.Text(u8"Обновления не найдены.") end end imgui.EndChild() imgui.PopStyleColor() -- Логирование imgui.PushStyleColor(imgui.Col.ChildBg, panelColor) if imgui.BeginChild("LoggingOptions", imgui.ImVec2(0, 80), true) then imgui.Text(u8"Логирование") if imgui.Checkbox(u8'Логировать действия', SETTINGS.logActionsEnabled) then saveSettings() -- Сохраняем все настройки sampAddChatMessage(CONFIG.tagInfo .. "Логирование действий: {32CD32}" .. (SETTINGS.logActionsEnabled[0] and "включено" or "выключено"), -1) end imgui.SameLine() imgui.ShowHelpMarker(u8"Записывает все действия скрипта в файл repflow.log.") end imgui.EndChild() imgui.PopStyleColor() -- Тайм-аут автостарта imgui.PushStyleColor(imgui.Col.ChildBg, panelColor) if imgui.BeginChild("AutoStartTimeout", imgui.ImVec2(0, 100), true) then imgui.Text(u8'Тайм-аут автостарта (сек):') imgui.SameLine() imgui.ShowHelpMarker(u8"Время ожидания перед автоматическим включением ловли после диалога.") imgui.Text(u8'Текущий: ' .. SETTINGS.dialogTimeout[0]) imgui.PushItemWidth(45) imgui.InputText(u8'##dialogTimeoutInput', SETTINGS.dialogTimeoutBuffer, ffi.sizeof(SETTINGS.dialogTimeoutBuffer)) imgui.SameLine() if imgui.Button(faicons('floppy_disk') .. u8" Сохранить") then local newValue = tonumber(ffi.string(SETTINGS.dialogTimeoutBuffer)) if newValue and newValue >= 1 and newValue <= 9999 then SETTINGS.dialogTimeout[0] = newValue saveSettings() -- Сохраняем все настройки sampAddChatMessage(CONFIG.tagInfo .. "Тайм-аут сохранён: {32CD32}" .. newValue .. " сек", -1) else sampAddChatMessage(CONFIG.tagInfo .. "Ошибка: {32CD32}1-9999.", -1) end end imgui.PopItemWidth() end imgui.EndChild() imgui.PopStyleColor() -- Положение окна imgui.PushStyleColor(imgui.Col.ChildBg, panelColor) if imgui.BeginChild("WindowPosition", imgui.ImVec2(0, 60), true) then imgui.Text(u8'Положение инфо-окна:') imgui.SameLine() if imgui.Button(u8'Изменить') then startMovingWindow() end imgui.SameLine() imgui.ShowHelpMarker(u8"Позволяет переместить информационное окно на экране.") end imgui.EndChild() imgui.PopStyleColor() -- Профиль настроек imgui.PushStyleColor(imgui.Col.ChildBg, panelColor) if imgui.BeginChild("ProfileOptions", imgui.ImVec2(0, 75), true) then imgui.Text(u8'Профиль настроек:') imgui.SameLine() imgui.ShowHelpMarker(u8"Выберите профиль для загрузки настроек. Изменения автоматически сохраняются в текущий профиль.") local profiles = { u8"Профиль 1", u8"Профиль 2", u8"Профиль 3" } local cProfiles = ffi.new("const char*[?]", #profiles) for i, name in ipairs(profiles) do cProfiles[i - 1] = name end if imgui.Combo(u8'##ProfileSelector', SETTINGS.selectedProfile, cProfiles, #profiles) then ini.main.selectedProfile = SETTINGS.selectedProfile[0] saveSettings() -- Сохраняем настройки и в профиль loadSettingsFromProfile() sampAddChatMessage(CONFIG.tag .. "Загружен профиль: Профиль " .. (SETTINGS.selectedProfile[0] + 1), -1) end end imgui.EndChild() imgui.PopStyleColor() -- Цветовая схема imgui.PushStyleColor(imgui.Col.ChildBg, panelColor) if imgui.BeginChild("ColorScheme", imgui.ImVec2(0, 100), true) then imgui.Text(u8'Цветовая схема:') imgui.SameLine() imgui.ShowHelpMarker(u8"Выберите тему оформления интерфейса.") local themeNames = {} for i, theme in ipairs(COLOR_THEMES) do themeNames[i] = u8(theme.name) end local cThemeNames = ffi.new("const char*[?]", #themeNames) for i, name in ipairs(themeNames) do cThemeNames[i - 1] = name end if imgui.Combo(u8'##ThemeSelector', SETTINGS.selectedTheme, cThemeNames, #themeNames) then COLORS = COLOR_THEMES[SETTINGS.selectedTheme[0] + 1] saveSettings() -- Сохраняем все настройки sampAddChatMessage(CONFIG.tagInfo .. "Тема изменена: {32CD32}" .. COLOR_THEMES[SETTINGS.selectedTheme[0] + 1].name, -1) logToFile("Тема изменена на: " .. COLOR_THEMES[SETTINGS.selectedTheme[0] + 1].name) end end imgui.EndChild() imgui.PopStyleColor() end sampRegisterChatCommand("arepstats", function() local elapsedTime = os.clock() - STATE.scriptStartTime local successRate = STATE.reportAttempts > 0 and (STATE.reportAnsweredCount / STATE.reportAttempts * 100) or 0 sampAddChatMessage(CONFIG.tag .. "Статистика RepFlow:", -1) sampAddChatMessage(CONFIG.tag .. "Время работы: " .. string.format("%.2f сек", elapsedTime), -1) sampAddChatMessage(CONFIG.tag .. "Попыток: " .. STATE.reportAttempts, -1) sampAddChatMessage(CONFIG.tag .. "Принято репортов: " .. STATE.reportAnsweredCount, -1) sampAddChatMessage(CONFIG.tag .. "Успешность: " .. string.format("%.1f%%", successRate), -1) sampAddChatMessage(CONFIG.tag .. "Обнаружено флудов: " .. STATE.floodCount, -1) logToFile("Пользователь запросил статистику через /arepstats") end) function logReport(playerName, reportText) local reportFile = io.open("moonloader/repflow_reports.log", "a") if reportFile then local timestamp = os.date("%Y-%m-%d %H:%M:%S") reportFile:write(string.format("[%s] Игрок: %s | Репорт: %s\n", timestamp, playerName, reportText)) reportFile:close() end end function filterFloodMessage(text) if SETTINGS.hideFloodMsg[0] and text:find("%[Ошибка%] {FFFFFF}Не флуди!") then STATE.floodCount = STATE.floodCount + 1 if SETTINGS.useFloodPause[0] then STATE.floodCooldown = os.clock() * 1000 + (SETTINGS.floodPause[0] * 1000) sampAddChatMessage(CONFIG.tag .. "Флуд, пауза на " .. SETTINGS.floodPause[0] .. " сек", -1) logToFile("Флуд, пауза на " .. SETTINGS.floodPause[0] .. " сек") end if SETTINGS.pauseOnFlood[0] then STATE.active = false -- Отключаем ловлю при флуде SETTINGS.activeState[0] = false sampAddChatMessage(CONFIG.tag .. "{FF0000}Ловля отключена из-за флуда!", -1) logToFile("Ловля отключена из-за флуда") end return false end return true end -- Проверка сворачивания игры function checkPauseAndDisableAutoStart() if isPauseMenuActive() then if not STATE.gameMinimized then STATE.wasActiveBeforePause = STATE.active if STATE.active then STATE.active = false end STATE.gameMinimized = true end else if STATE.gameMinimized then STATE.gameMinimized = false STATE.afkExitTime = os.clock() if STATE.wasActiveBeforePause then sampAddChatMessage(CONFIG.tag .. '{FFFFFF}Вы вышли из паузы. Ловля отключена из-за AFK!', -1) logToFile("Выход из паузы, ловля отключена") end end end end function drawInfoTab() imgui.CenterText((faicons('star') .. u8" RepFlow / " .. faicons('circle_info') .. u8" Информация"), { color = COLORS.text }) imgui.Separator() local panelColor = COLORS.childPanel imgui.PushStyleColor(imgui.Col.ChildBg, panelColor) if imgui.BeginChild("Info", imgui.ImVec2(0, -1), true) then -- Основная информация imgui.Text(u8"RepFlow v" .. CONFIG.scriptVersion .. " by Matthew_McLaren[18]") imgui.Text(u8"Скрипт для автоматической ловли репортов на серверах Arizona RP.") -- Функции imgui.Separator() imgui.Text(u8"Основные функции:") imgui.BulletText(u8"Автоматическая отправка команды /ot с настраиваемым интервалом.") imgui.BulletText(u8"Обнаружение репортов в чате по ключевой фразе '[Репорт] от Имя_Фамилия'.") imgui.BulletText(u8"Автоматическая обработка диалога 1334 для принятия репортов.") imgui.BulletText(u8"Звуковое уведомление при принятии репорта (настраивается в 'Настройки').") imgui.BulletText(u8"Подробная статистика: попытки, принятые репорты, флуды.") imgui.BulletText(u8"Поддержка профилей настроек и цветовых тем.") -- Инструкция по звуку imgui.Separator() imgui.Text(u8"Настройка звука:") imgui.TextWrapped(u8"Для звукового уведомления поместите файл report_sound.mp3 в папку moonloader. Включите опцию 'Воспроизводить звук при репорте' в настройках.") -- Инструкция по логам и профилям imgui.Separator() imgui.Text(u8"Логирование и профили:") imgui.TextWrapped(u8"Логи: moonloader/repflow.log (основной лог), moonloader/repflow_reports.log (репорты). Профили: moonloader/config/profile_X.ini.") -- Ссылки imgui.Separator() imgui.Text(u8"Ссылки:") imgui.Link("https://blast.hk/threads/12345", u8"Тема на blast.hk", nil, faicons('CLOUD_EXCLAMATION'), false) -- Неактивная ссылка imgui.Link("https://github.com/Zorahm/RepFlow", u8"GitHub-репозиторий", nil, faicons('MOON_STARS'), true) -- Активная ссылка -- Благодарности imgui.Separator() imgui.Text(u8"Благодарности:") imgui.TextWrapped(u8"Тестеры: Carl_Mort[18], Sweet_Lemonte[18], Balenciaga_Collins[18].") -- Связь с автором imgui.Text(u8"Связь с автором:") imgui.SameLine() imgui.Link("https://t.me/zorahm", u8"Telegram", nil, faicons('PAPER_PLANE'), true) -- Актуальность imgui.Text(u8"Скрипт активно поддерживается. Следите за обновлениями!") end imgui.EndChild() imgui.PopStyleColor() end function drawStatsTab() local panelColor = COLORS.childPanel imgui.Text(faicons('star') .. u8" RepFlow / " .. faicons('chart_bar') .. u8" Статистика") -- Изменили иконку на более подходящую imgui.Separator() imgui.PushStyleColor(imgui.Col.ChildBg, panelColor) if imgui.BeginChild("Stats", imgui.ImVec2(0, -1), true) then -- Общее время работы imgui.Text(faicons('clock') .. u8" Общее время работы: " .. string.format("%.2f", os.clock() - STATE.scriptStartTime) .. u8" сек") -- Попытки отправки /ot imgui.Text(faicons('paper_plane') .. u8" Попыток отправки /ot: " .. STATE.reportAttempts) -- Принято репортов imgui.Text(faicons('circle_check') .. u8" Принято репортов: " .. STATE.reportAnsweredCount) -- Обнаружено флудов imgui.Text(faicons('triangle_exclamation') .. u8" Обнаружено флудов: " .. STATE.floodCount) -- Среднее время между попытками if STATE.reportAttempts > 0 then local totalTime = os.clock() - STATE.scriptStartTime local avgTime = totalTime / STATE.reportAttempts imgui.Text(faicons('square_poll_horizontal') .. u8" Среднее время между /ot: " .. string.format("%.2f", avgTime) .. u8" сек") else imgui.Text(faicons('square_poll_horizontal') .. u8" Среднее время между /ot: N/A") end -- Кнопка сброса статистики if imgui.Button(u8"Сбросить статистику", imgui.ImVec2(-1, 30)) then -- Ширина кнопки на всю доступную область STATE.reportAttempts = 0 STATE.reportAnsweredCount = 0 STATE.floodCount = 0 STATE.scriptStartTime = os.clock() sampAddChatMessage(CONFIG.tag .. "Статистика сброшена", -1) logToFile("Статистика сброшена") end imgui.Dummy(imgui.ImVec2(0, 10)) end imgui.EndChild() imgui.PopStyleColor() end function drawChangeLogTab() imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.0, 0.8, 1.0, 1.0)) imgui.CenterText(faicons('star') .. u8" RepFlow " .. faicons('bolt') .. u8" ChangeLog") imgui.PopStyleColor() imgui.Dummy(imgui.ImVec2(0, 5)) imgui.Text(u8"Полный список изменений доступен на GitHub:") imgui.SameLine() imgui.Link("https://github.com/Zorahm/RepFlow/blob/main/CHANGELOG.md", faicons('github') .. u8" Открыть", nil, nil, true) imgui.Separator() local panelColor = imgui.ImVec4(0.07, 0.07, 0.07, 1.0) imgui.PushStyleColor(imgui.Col.ChildBg, panelColor) if imgui.BeginChild("ChangeLog", imgui.ImVec2(0, -1), true) then for i, entry in ipairs(changelogEntries) do imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.9, 0.9, 0.9, 1.0)) local headerText = faicons('tag') .. u8" Версия " .. u8(entry.version) .. u8" (" .. u8(entry.date) .. u8")" local isOpen = imgui.CollapsingHeader(headerText, nil, imgui.TreeNodeFlags.DefaultOpen) imgui.PopStyleColor() if isOpen then imgui.SameLine(imgui.GetWindowWidth() - 60) if imgui.SmallButton(faicons('copy') .. u8"##copy_" .. i) then imgui.SetClipboardText(entry.description) sampAddChatMessage(CONFIG.tag .. "Изменения версии " .. entry.version .. " скопированы в буфер обмена!", 0x1E90FF) end imgui.ShowHelpMarker(u8"Копировать текст изменений в буфер обмена") imgui.PushTextWrapPos(imgui.GetWindowWidth() - 20) local categories = { newFeatures = {}, improvements = {}, bugFixes = {}, notes = {} } local currentCategory = nil for line in entry.description:gmatch("[^\n]+") do line = line:match("^%s*(.-)%s*$") if line:match("^%- Новые функции:") then currentCategory = categories.newFeatures elseif line:match("^%- Улучшения:") then currentCategory = categories.improvements elseif line:match("^%- Исправления:") then currentCategory = categories.bugFixes elseif line:match("^%- Примечания:") then currentCategory = categories.notes elseif line:match("^%s*-") and currentCategory then table.insert(currentCategory, line:match("^%s*-(.+)%s*$")) end end local function drawCategory(categoryName, categoryData, icon, color) if #categoryData > 0 then imgui.PushStyleColor(imgui.Col.Text, color) imgui.Text(icon .. u8" " .. u8(categoryName)) imgui.PopStyleColor() for _, item in ipairs(categoryData) do imgui.BulletText(faicons('circle') .. u8" " .. u8(item)) end imgui.Dummy(imgui.ImVec2(0, 3)) end end drawCategory("Новые функции", categories.newFeatures, faicons('plus'), imgui.ImVec4(0.2, 0.8, 0.2, 1.0)) drawCategory("Улучшения", categories.improvements, faicons('arrow-up'), imgui.ImVec4(0.0, 0.6, 1.0, 1.0)) drawCategory("Исправления", categories.bugFixes, faicons('bug'), imgui.ImVec4(1.0, 0.3, 0.3, 1.0)) drawCategory("Примечания", categories.notes, faicons('info-circle'), imgui.ImVec4(0.8, 0.8, 0.0, 1.0)) imgui.PopTextWrapPos() imgui.Dummy(imgui.ImVec2(0, 5)) end end end imgui.EndChild() imgui.PopStyleColor() end -- Отрисовка главного окна imgui.OnFrame(function() return SETTINGS.mainWindowState[0] end, function(player) imgui.SetNextWindowSize(imgui.ImVec2(800, 500), imgui.Cond.FirstUseEver) imgui.SetNextWindowPos(imgui.ImVec2(sw / 2, sh / 2), imgui.Cond.FirstUseEver, imgui.ImVec2(0.5, 0.5)) -- Применяем стили для всех элементов imgui.PushStyleColor(imgui.Col.WindowBg, COLORS.rightPanel) imgui.PushStyleColor(imgui.Col.Text, COLORS.text) imgui.PushStyleColor(imgui.Col.Button, COLORS.button) imgui.PushStyleColor(imgui.Col.ButtonHovered, COLORS.buttonHovered) imgui.PushStyleColor(imgui.Col.ButtonActive, COLORS.buttonActive) imgui.PushStyleColor(imgui.Col.CheckMark, COLORS.checkMark) imgui.PushStyleColor(imgui.Col.FrameBg, COLORS.frameBg) imgui.PushStyleColor(imgui.Col.FrameBgHovered, COLORS.frameBgHovered) imgui.PushStyleColor(imgui.Col.FrameBgActive, COLORS.frameBgActive) resetIO() if imgui.Begin(faicons('bolt') .. u8' RepFlow | Premium', SETTINGS.mainWindowState, imgui.WindowFlags.NoResize + imgui.WindowFlags.NoCollapse) then imgui.PushStyleColor(imgui.Col.ChildBg, COLORS.leftPanel) if imgui.BeginChild("left_panel", imgui.ImVec2(125, -1), false) then local tabNames = { "Флудер", "Настройки", "Информация", "Статистика", "ChangeLog" } for i, name in ipairs(tabNames) do if i - 1 == SETTINGS.activeTab[0] then imgui.PushStyleColor(imgui.Col.Button, COLORS.hover) else imgui.PushStyleColor(imgui.Col.Button, COLORS.leftPanel) end imgui.PushStyleColor(imgui.Col.ButtonHovered, COLORS.hover) imgui.PushStyleColor(imgui.Col.ButtonActive, COLORS.hover) if imgui.Button(u8(name), imgui.ImVec2(125, 40)) then SETTINGS.activeTab[0] = i - 1 end imgui.PopStyleColor(3) end end imgui.EndChild() imgui.PopStyleColor() imgui.SameLine() imgui.PushStyleColor(imgui.Col.ChildBg, COLORS.rightPanel) if imgui.BeginChild("right_panel", imgui.ImVec2(-1, 0), false) then if SETTINGS.activeTab[0] == 0 then drawMainTab() elseif SETTINGS.activeTab[0] == 1 then drawSettingsTab() elseif SETTINGS.activeTab[0] == 2 then drawInfoTab() elseif SETTINGS.activeTab[0] == 3 then drawStatsTab() elseif SETTINGS.activeTab[0] == 4 then drawChangeLogTab() end end imgui.EndChild() imgui.PopStyleColor() end imgui.End() -- Убираем все стили imgui.PopStyleColor(9) end) -- Обработка смены клавиши function onWindowMessage(msg, wparam, lparam) if STATE.changingKey then if msg == 0x100 or msg == 0x101 then STATE.keyBind = wparam STATE.keyBindName = vkeys.id_to_name(STATE.keyBind) STATE.changingKey = false ini.main.keyBind = string.format("0x%X", STATE.keyBind) ini.main.keyBindName = STATE.keyBindName inicfg.save(ini, CONFIG.iniFilename) sampAddChatMessage(string.format(CONFIG.tag .. '{FFFFFF}Новая клавиша: {00FF00}%s', STATE.keyBindName), -1) logToFile("Новая клавиша: " .. STATE.keyBindName) return false end end end -- Центрирование текста function imgui.CenterText(text, options) -- Устанавливаем значения по умолчанию для опций options = options or {} local color = options.color or nil -- Цвет текста (ImVec4 или nil) local wrap = options.wrap or false -- Оборачивать текст (true/false) local icon = options.icon or nil -- Иконка перед текстом (например, faicons('icon')) -- Получаем размеры окна local windowWidth = imgui.GetWindowWidth() -- Разбиваем текст на строки, если он многострочный local lines = {} for line in text:gmatch("[^\n]+") do table.insert(lines, line) end -- Если есть иконка, добавляем её в первую строку if icon then lines[1] = icon .. " " .. lines[1] end -- Отрисовка текста for _, line in ipairs(lines) do local lineSize = imgui.CalcTextSize(line) imgui.SetCursorPosX((windowWidth - lineSize.x) / 2) if color then if wrap then imgui.PushTextWrapPos(windowWidth) imgui.TextColored(color, line) imgui.PopTextWrapPos() else imgui.TextColored(color, line) end else if wrap then imgui.PushTextWrapPos(windowWidth) imgui.Text(line) imgui.PopTextWrapPos() else imgui.Text(line) end end end end function loadSettingsFromProfile() local profileName = "profile_" .. (SETTINGS.selectedProfile[0] + 1) local profileIni = inicfg.load(defaultConfig, "moonloader/" .. profileName .. ".ini") SETTINGS.otInterval[0] = profileIni.main.otInterval or 10 SETTINGS.dialogTimeout[0] = profileIni.main.dialogTimeout or 600 SETTINGS.floodPause[0] = profileIni.main.floodPause or 10 SETTINGS.useMilliseconds[0] = profileIni.main.useMilliseconds == true SETTINGS.hideFloodMsg[0] = profileIni.main.otklflud == true SETTINGS.autoStartEnabled[0] = profileIni.main.autoStartEnabled == true SETTINGS.dialogHandlerEnabled[0] = profileIni.main.dialogHandlerEnabled == true SETTINGS.selectedTheme[0] = profileIni.main.selectedTheme or 0 SETTINGS.useFloodPause[0] = profileIni.main.useFloodPause == true SETTINGS.autoUpdateEnabled[0] = profileIni.main.autoUpdateEnabled == true SETTINGS.playSoundOnReport[0] = profileIni.main.playSoundOnReport == true SETTINGS.logActionsEnabled[0] = profileIni.main.logActionsEnabled == true SETTINGS.selectedTheme[0] = profileIni.main.selectedTheme or 0 COLORS = COLOR_THEMES[SETTINGS.selectedTheme[0] + 1] ini.widget.posX = profileIni.widget.posX or 400 ini.widget.posY = profileIni.widget.posY or 400 -- Синхронизируем ini.main с текущими настройками профиля ini.main = profileIni.main or {} ini.main.otInterval = SETTINGS.otInterval[0] ini.main.dialogTimeout = SETTINGS.dialogTimeout[0] ini.main.floodPause = SETTINGS.floodPause[0] ini.main.useMilliseconds = SETTINGS.useMilliseconds[0] ini.main.dialogHandlerEnabled = SETTINGS.dialogHandlerEnabled[0] ini.main.autoStartEnabled = SETTINGS.autoStartEnabled[0] ini.main.otklflud = SETTINGS.hideFloodMsg[0] ini.main.selectedTheme = SETTINGS.selectedTheme[0] ini.main.useFloodPause = SETTINGS.useFloodPause[0] ini.main.autoUpdateEnabled = SETTINGS.autoUpdateEnabled[0] ini.main.logActionsEnabled = SETTINGS.logActionsEnabled[0] ini.main.selectedProfile = SETTINGS.selectedProfile[0] ini.main.pauseOnFlood = SETTINGS.pauseOnFlood[0] ini.main.playSoundOnReport = SETTINGS.playSoundOnReport[0] inicfg.save(ini, CONFIG.iniFilename) sampAddChatMessage(CONFIG.tag .. "Загружен профиль: Профиль " .. (SETTINGS.selectedProfile[0] + 1), -1) logToFile("Загружен профиль: Профиль " .. (SETTINGS.selectedProfile[0] + 1)) end imgui.OnFrame(function() return SETTINGS.infoWindowState[0] end, function(self) self.HideCursor = true imgui.SetNextWindowSize(imgui.ImVec2(260, 350), imgui.Cond.FirstUseEver) -- Увеличиваем высоту до 350 imgui.SetNextWindowPos(imgui.ImVec2(ini.widget.posX, ini.widget.posY), imgui.Cond.Always) imgui.Begin(faicons('star') .. u8" | RepFlow ", SETTINGS.infoWindowState, imgui.WindowFlags.NoResize + imgui.WindowFlags.NoCollapse) -- Заголовок imgui.PushStyleColor(imgui.Col.Text, COLORS.text) imgui.Text(faicons('bolt') .. u8" Статус") imgui.PopStyleColor() imgui.SameLine() imgui.SetCursorPosX(imgui.GetWindowWidth() - 30) -- Позиция для HelpMarker imgui.ShowHelpMarker(u8"Текущий статус ловли и статистика.") -- Статус с анимацией и кликабельностью local statusColor = STATE.active and imgui.ImVec4(0.0, 1.0, 0.0, 1.0) or imgui.ImVec4(1.0, 0.0, 0.0, 1.0) if STATE.active then local alpha = 0.8 + math.sin(os.clock() * 4) * 0.2 statusColor.w = alpha end imgui.PushStyleColor(imgui.Col.Text, statusColor) if imgui.Button(u8(STATE.active and 'Включена' or 'Выключена'), imgui.ImVec2(imgui.GetWindowWidth() - 40, 30)) then STATE.active = not STATE.active if SETTINGS.activeState then -- Проверяем, существует ли activeState SETTINGS.activeState[0] = STATE.active end if STATE.active then STATE.startTime = os.clock() STATE.lastOtTime = os.clock() * 1000 sampAddChatMessage(CONFIG.tag .. "Ловля активирована", -1) logToFile("Ловля активирована") else sampAddChatMessage(CONFIG.tag .. "Ловля деактивирована", -1) logToFile("Ловля деактивирована") end end imgui.SetCursorPosX((imgui.GetWindowWidth() - imgui.CalcTextSize(u8(STATE.active and 'Включена' or 'Выключена')).x) / 2) imgui.PopStyleColor() -- Время работы local style = imgui.GetStyle() imgui.SetCursorPosX(style.WindowPadding.x) -- Выравниваем с учётом отступа окна local elapsedTime = os.clock() - STATE.startTime imgui.Text(faicons('clock') .. u8" Время работы: " .. string.format(u8'%.2f сек', elapsedTime)) -- Статистика с иконками imgui.Text(faicons('file') .. u8" Попыток: " .. STATE.reportAttempts) imgui.Text(faicons('circle_check') .. u8" Принято: " .. STATE.reportAnsweredCount) -- Процент успешных репортов local successRate = STATE.reportAttempts > 0 and (STATE.reportAnsweredCount / STATE.reportAttempts * 100) or 0 imgui.Text(faicons('percent') .. u8" Успешность: " .. string.format(u8'%.1f%%', successRate)) imgui.Dummy(imgui.ImVec2(0, 10)) -- Дополнительная информация imgui.Separator() imgui.Text(faicons('window_maximize') .. u8" Диалоги: " .. (SETTINGS.dialogHandlerEnabled[0] and u8'Вкл' or u8'Выкл')) imgui.Text(faicons('circle_play') .. u8" Автостарт: " .. (SETTINGS.autoStartEnabled[0] and u8'Вкл' or u8'Выкл')) imgui.Text(faicons('clock') .. u8" Интервал: " .. SETTINGS.otInterval[0] .. u8' мс') -- Пауза с прогресс-баром if STATE.floodCooldown > os.clock() * 1000 and SETTINGS.useFloodPause[0] then local remainingTime = math.max(0, (STATE.floodCooldown - os.clock() * 1000) / 1000) local totalTime = SETTINGS.floodPause[0] local progress = 1.0 - (remainingTime / totalTime) imgui.PushStyleColor(imgui.Col.PlotHistogram, imgui.ImVec4(1.0, 0.5, 0.0, 1.0)) imgui.Text(faicons('circle_pause') .. u8" Пауза: " .. string.format(u8'%.1f сек', remainingTime)) imgui.ProgressBar(progress, imgui.ImVec2(-1, 15), u8"") imgui.PopStyleColor() end imgui.End() end) -- Управление видимостью информационного окна function showInfoWindow() if not SETTINGS.infoWindowState[0] then SETTINGS.infoWindowState[0] = true ini.widget.visible = true inicfg.save(ini, CONFIG.iniFilename) end end function showInfoWindowOff() if SETTINGS.infoWindowState[0] then SETTINGS.infoWindowState[0] = false ini.widget.visible = false inicfg.save(ini, CONFIG.iniFilename) end end -- Функция логирования function logToFile(message) if not SETTINGS.logActionsEnabled[0] and not message:find("Ошибка") then return -- Не логируем, если отключено, кроме ошибок end local logFile = io.open("moonloader/repflow.log", "a") if logFile then local timestamp = os.date("%Y-%m-%d %H:%M:%S") logFile:write(string.format("[%s] %s\n", timestamp, message)) logFile:close() end end