1660 lines
76 KiB
Lua
1660 lines
76 KiB
Lua
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 |