repflow/!RepFlow.lua

1660 lines
76 KiB
Lua
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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