script_name('Ultimate Chat Helper') script_version('2.1') script_version_number(3) script_moonloader(026) script_author('Reverse') script_description('Помощь при вводе, грамматика, автозамена ошибок') local ffi = require("ffi") require 'lib.moonloader' require 'lib.sampfuncs' local mem = require "memory" local sampev = require('lib.samp.events') local inicfg = require 'inicfg' local imgui = require 'mimgui' local encoding = require 'encoding' encoding.default = 'CP1251' local u8 = encoding.UTF8 -- ==================== СТРОКИ CP1251 ==================== -- Все русские символы заданы байтами во избежание проблем с кодировкой в SAMP local L = { loaded = string.char(0xE7,0xE0,0xE3,0xF0,0xF3,0xE6,0xE5,0xED,0x2E,0x20,0xC0,0xE2,0xF2,0xEE,0xF0,0x3A), -- "загружен. Автор:" saved = string.char(0xCD,0xE0,0xF1,0xF2,0xF0,0xEE,0xE9,0xEA,0xE8,0x20,0xF3,0xF1,0xEF,0xE5,0xF8,0xED,0xEE,0x20,0xF1,0xEE,0xF5,0xF0,0xE0,0xED,0xE5,0xED,0xFB,0x21), -- "Настройки успешно сохранены!" input_helper = "Input Helper: " .. string.char(0xE2,0xF1,0xE5,0xE3,0xE4,0xE0,0x20,0xE2,0xEA,0xEB,0xFE,0xF7,0xE5,0xED), -- "всегда включен" smart_spaces = string.char(0xD3,0xEC,0xED,0xFB,0xE5,0x20,0xEF,0xF0,0xEE,0xE1,0xE5,0xEB,0xFB,0x20,0xEF,0xEE,0xF1,0xEB,0xE5,0x20,0xE7,0xED,0xE0,0xEA,0xEE,0xE2), clean_spaces = string.char(0xD3,0xE4,0xE0,0xEB,0xE5,0xED,0xE8,0xE5,0x20,0xEB,0xE8,0xF8,0xED,0xE8,0xF5,0x20,0xEF,0xF0,0xEE,0xE1,0xE5,0xEB,0xEE,0xE2), zhishi = string.char(0xC0,0xE2,0xF2,0xEE,0xE7,0xE0,0xEC,0xE5,0xED,0xE0,0x20,0xE6,0xE8,0x2F,0xF8,0xE8,0x2C,0x20,0xF7,0xE0,0x2F,0xF9,0xE0,0x2C,0x20,0xF7,0xF3,0x2F,0xF9,0xF3), fix_errors = string.char(0xD0,0xE0,0xF1,0xF8,0xE8,0xF0,0xE5,0xED,0xED,0xEE,0xE5,0x20,0xE8,0xF1,0xEF,0xF0,0xE0,0xE2,0xEB,0xE5,0xED,0xE8,0xE5,0x20,0xEE,0xF8,0xE8,0xE1,0xEE,0xEA), format_numbers = string.char(0xD4,0xEE,0xF0,0xEC,0xE0,0xF2,0xE8,0xF0,0xEE,0xE2,0xE0,0xED,0xE8,0xE5,0x20,0xF7,0xE8,0xF1,0xE5,0xEB,0x20,0x28,0x31,0x35,0x30,0x30,0x20,0x2D,0x3E,0x20,0x31,0x2E,0x35,0x30,0x30,0x29), dashes = string.char(0xC0,0xE2,0xF2,0xEE,0xE7,0xE0,0xEC,0xE5,0xED,0xE0,0x20,0xE4,0xE5,0xF4,0xE8,0xF1,0xEE,0xE2), quotes = string.char(0xD3,0xEC,0xED,0xFB,0xE5,0x20,0xEA,0xE0,0xE2,0xFB,0xF7,0xEA,0xE8), capitalize = string.char(0xC7,0xE0,0xE3,0xEB,0xE0,0xE2,0xED,0xE0,0xFF,0x20,0xE1,0xF3,0xEA,0xE2,0xE0,0x20,0xE2,0x20,0xED,0xE0,0xF7,0xE0,0xEB,0xE5), punctuation = string.char(0xD2,0xEE,0xF7,0xEA,0xE0,0x20,0xE2,0x20,0xEA,0xEE,0xED,0xF6,0xE5), save_btn = string.char(0xD1,0xEE,0xF5,0xF0,0xE0,0xED,0xE8,0xF2,0xFC,0x20,0xED,0xE0,0xF1,0xF2,0xF0,0xEE,0xE9,0xEA,0xE8), window_title = "Ultimate Chat Helper - " .. string.char(0xCD,0xE0,0xF1,0xF2,0xF0,0xEE,0xE9,0xEA,0xE8) } -- Настройки по умолчанию local config_file = 'UltimateChatHelper.ini' local default_cfg = { settings = { input_helper = true, smart_spaces = true, clean_spaces = true, zhishi = true, fix_errors = true, format_numbers = true, dashes = true, quotes = true, capitalize = true, punctuation = true } } local cfg = inicfg.load(default_cfg, config_file) or default_cfg if not cfg.settings then cfg.settings = default_cfg.settings end for k, v in pairs(default_cfg.settings) do if cfg.settings[k] == nil then cfg.settings[k] = v end end local function saveAllConfig() local save_data = { settings = cfg.settings } inicfg.save(save_data, config_file) end local renderWindow = imgui.new.bool(false) local cbSmartSpaces = imgui.new.bool(cfg.settings.smart_spaces) local cbCleanSpaces = imgui.new.bool(cfg.settings.clean_spaces) local cbZhishi = imgui.new.bool(cfg.settings.zhishi) local cbFixErrors = imgui.new.bool(cfg.settings.fix_errors) local cbFormatNumbers = imgui.new.bool(cfg.settings.format_numbers) local cbDashes = imgui.new.bool(cfg.settings.dashes) local cbQuotes = imgui.new.bool(cfg.settings.quotes) local cbCapitalize = imgui.new.bool(cfg.settings.capitalize) local cbPunctuation = imgui.new.bool(cfg.settings.punctuation) function onScriptTerminate(scr, quitGame) if scr == script.this then renderWindow[0] = false end end ffi.cdef[[ short GetKeyState(int nVirtKey); bool GetKeyboardLayoutNameA(char* pwszKLID); int GetLocaleInfoA(int Locale, int LCType, char* lpLCData, int cchData); ]] local BuffSize = 32 local KeyboardLayoutName = ffi.new("char[?]", BuffSize) local LocalInfo = ffi.new("char[?]", BuffSize) local chars = { [string.char(0xE9)] = "q", [string.char(0xF6)] = "w", [string.char(0xF3)] = "e", [string.char(0xEA)] = "r", [string.char(0xE5)] = "t", [string.char(0xED)] = "y", [string.char(0xE3)] = "u", [string.char(0xF8)] = "i", [string.char(0xF9)] = "o", [string.char(0xE7)] = "p", [string.char(0xF5)] = "[", [string.char(0xFA)] = "]", [string.char(0xF4)] = "a", [string.char(0xFB)] = "s", [string.char(0xE2)] = "d", [string.char(0xE0)] = "f", [string.char(0xEF)] = "g", [string.char(0xF0)] = "h", [string.char(0xEE)] = "j", [string.char(0xEB)] = "k", [string.char(0xE4)] = "l", [string.char(0xE6)] = ";", [string.char(0xFD)] = "'", [string.char(0xFF)] = "z", [string.char(0xF7)] = "x", [string.char(0xF1)] = "c", [string.char(0xEC)] = "v", [string.char(0xE8)] = "b", [string.char(0xF2)] = "n", [string.char(0xFC)] = "m", [string.char(0xE1)] = ",", [string.char(0xFE)] = ".", [string.char(0xC9)] = "Q", [string.char(0xD6)] = "W", [string.char(0xD3)] = "E", [string.char(0xCA)] = "R", [string.char(0xC5)] = "T", [string.char(0xCD)] = "Y", [string.char(0xC3)] = "U", [string.char(0xD8)] = "I", [string.char(0xD9)] = "O", [string.char(0xC7)] = "P", [string.char(0xD5)] = "{", [string.char(0xDA)] = "}", [string.char(0xD4)] = "A", [string.char(0xDB)] = "S", [string.char(0xC2)] = "D", [string.char(0xC0)] = "F", [string.char(0xCF)] = "G", [string.char(0xD0)] = "H", [string.char(0xCE)] = "J", [string.char(0xCB)] = "K", [string.char(0xC4)] = "L", [string.char(0xC6)] = ":", [string.char(0xDD)] = "\"", [string.char(0xDF)] = "Z", [string.char(0xD7)] = "X", [string.char(0xD1)] = "C", [string.char(0xCC)] = "V", [string.char(0xC8)] = "B", [string.char(0xD2)] = "N", [string.char(0xDC)] = "M", [string.char(0xC1)] = "<", [string.char(0xDE)] = ">", } local inputHelpText = nil local oldText = nil -- Список команд для полной обработки (как обычный чат) local full_processing_commands = { ["s"] = true, ["w"] = true, ["wto"] = true, ["do"] = true, ["b"] = true, ["ab"] = true, ["pm"] = true, ["f"] = true, ["r"] = true, ["ri"] = true, ["rr"] = true, ["rl"] = true, ["rrl"] = true } local function isLetter(byte) if not byte then return false end if (byte >= 0x41 and byte <= 0x5A) or (byte >= 0x61 and byte <= 0x7A) then return true end if byte >= 0xC0 and byte <= 0xFF then return true end if byte == 0xA8 or byte == 0xB8 then return true end return false end local function isDigit(byte) return byte ~= nil and byte >= 0x30 and byte <= 0x39 end local function isUpperByte(b) if not b then return false end return (b >= 0xC0 and b <= 0xDF) or b == 0xA8 or (b >= 0x41 and b <= 0x5A) end local function toLowerByte(b) if not b then return b end if b >= 0xC0 and b <= 0xDF then return b + 32 end if b == 0xA8 then return 0xB8 end if b >= 0x41 and b <= 0x5A then return b + 32 end return b end local function toUpperByte(b) if not b then return b end if b >= 0xE0 and b <= 0xFF then return b - 32 end if b == 0xB8 then return 0xA8 end if b >= 0x61 and b <= 0x7A then return b - 32 end return b end local function toLowerStr(str) local res = {} for i = 1, #str do table.insert(res, string.char(toLowerByte(str:byte(i)))) end return table.concat(res) end local function toUpperStr(str) local res = {} for i = 1, #str do table.insert(res, string.char(toUpperByte(str:byte(i)))) end return table.concat(res) end local function isStrAllCaps(str) local hasLetter = false for i = 1, #str do local b = str:byte(i) if isLetter(b) then hasLetter = true if not isUpperByte(b) then return false end end end return hasLetter end local function applyCaseMatch(original, replacement) if #original == 0 or #replacement == 0 then return replacement end if isStrAllCaps(original) then return toUpperStr(replacement) elseif isUpperByte(original:byte(1)) then local first = replacement:byte(1) local rest = replacement:sub(2) return string.char(toUpperByte(first)) .. rest else return replacement end end -- =========================== ФУНКЦИИ ОБРАБОТКИ =========================== local placeholders = {} local placeholder_count = 0 local function hideUrls(text) placeholders = {} placeholder_count = 0 text = text:gsub("[%a%d%.%_%-]+@[%a%d%.%-]+", function(match) placeholder_count = placeholder_count + 1 local ph = "%%URLPLACEHOLDER" .. placeholder_count .. "%%" placeholders[ph] = match return ph end) text = text:gsub("https?://%S+", function(match) placeholder_count = placeholder_count + 1 local ph = "%%URLPLACEHOLDER" .. placeholder_count .. "%%" placeholders[ph] = match return ph end) text = text:gsub("www%.%S+", function(match) placeholder_count = placeholder_count + 1 local ph = "%%URLPLACEHOLDER" .. placeholder_count .. "%%" placeholders[ph] = match return ph end) return text end local function restoreUrls(text) text = text:gsub("%%%%URLPLACEHOLDER%d+%%%%", function(match) return placeholders[match] or match end) return text end local function smartSpaces(text) if not text or #text == 0 then return text end local result = "" local len = #text for i = 1, len do local char = text:sub(i, i) result = result .. char if char:match("[,%.!%?%;%:]") then local nextChar = i < len and text:sub(i + 1, i + 1) or "" local prevChar = i > 1 and text:sub(i - 1, i - 1) or "" local skip = false if char == "." and prevChar:match("%d") and nextChar:match("%d") then skip = true end if nextChar:match("[,%.!%?%;%:]") then skip = true end if nextChar == "" or nextChar == " " then skip = true end -- Не ставим пробел, если следом идет кавычка (прямая, одинарная или елочка) if nextChar == '"' or nextChar == "'" or nextChar == string.char(0xAB) or nextChar == string.char(0xBB) then skip = true end if not skip then result = result .. " " end end end return result end local function cleanSpaces(text) if not text or #text == 0 then return text end text = text:gsub("^%s+", ""):gsub("%s+$", "") text = text:gsub("%s+", " ") text = text:gsub(" ([%,%.%!%?%;%:])", "%1") return text end local function replaceZhishi(text) text = text:gsub(string.char(0xE6, 0xFB), string.char(0xE6, 0xE8)) text = text:gsub(string.char(0xC6, 0xFB), string.char(0xC6, 0xE8)) text = text:gsub(string.char(0xC6, 0xDB), string.char(0xC6, 0xC8)) text = text:gsub(string.char(0xF8, 0xFB), string.char(0xF8, 0xE8)) text = text:gsub(string.char(0xD8, 0xFB), string.char(0xD8, 0xE8)) text = text:gsub(string.char(0xD8, 0xDB), string.char(0xD8, 0xC8)) text = text:gsub(string.char(0xF7, 0xFF), string.char(0xF7, 0xE0)) text = text:gsub(string.char(0xD7, 0xFF), string.char(0xD7, 0xE0)) text = text:gsub(string.char(0xD7, 0xDF), string.char(0xD7, 0xC0)) text = text:gsub(string.char(0xF9, 0xFF), string.char(0xF9, 0xE0)) text = text:gsub(string.char(0xD9, 0xFF), string.char(0xD9, 0xE0)) text = text:gsub(string.char(0xD9, 0xDF), string.char(0xD9, 0xC0)) text = text:gsub(string.char(0xF7, 0xFE), string.char(0xF7, 0xF3)) text = text:gsub(string.char(0xD7, 0xFE), string.char(0xD7, 0xF3)) text = text:gsub(string.char(0xD7, 0xDE), string.char(0xD7, 0xD3)) text = text:gsub(string.char(0xF9, 0xFE), string.char(0xF9, 0xF3)) text = text:gsub(string.char(0xD9, 0xFE), string.char(0xD9, 0xF3)) text = text:gsub(string.char(0xD9, 0xDE), string.char(0xD9, 0xD3)) return text end local error_corrections = { { err = string.char(0xE2,0xEE,0xEE,0xE1,0xF9,0xE5,0xEC), fix = string.char(0xE2,0x20,0xEE,0xE1,0xF9,0xE5,0xEC) }, { err = string.char(0xEA,0xE0,0xEA,0xE1,0xF3,0xE4,0xF2,0xEE), fix = string.char(0xEA,0xE0,0xEA,0x20,0xE1,0xF3,0xE4,0xF2,0xEE) }, { err = string.char(0xE2,0xEF,0xF0,0xE8,0xED,0xF6,0xE8,0xEF,0xE5), fix = string.char(0xE2,0x20,0xEF,0xF0,0xE8,0xED,0xF6,0xE8,0xEF,0xE5) }, { err = string.char(0xE2,0xF2,0xE5,0xF7,0xE5,0xED,0xE8,0xE5), fix = string.char(0xE2,0x20,0xF2,0xE5,0xF7,0xE5,0xED,0xE8,0xE5) }, { err = string.char(0xE8,0xE7,0xE7,0xE0), fix = string.char(0xE8,0xE7,0x2D,0xE7,0xE0) }, { err = string.char(0xED,0xE5,0xE7,0xED,0xE0,0xFE), fix = string.char(0xED,0xE5,0x20,0xE7,0xED,0xE0,0xFE) }, { err = string.char(0xED,0xE5,0xEF,0xEE,0xEB,0xF3,0xF7,0xE8,0xF2,0xF1,0xFF), fix = string.char(0xED,0xE5,0x20,0xEF,0xEE,0xEB,0xF3,0xF7,0xE8,0xF2,0xF1,0xFF) }, { err = string.char(0xF1,0xEA,0xE5,0xEC), fix = string.char(0xF1,0x20,0xEA,0xE5,0xEC) }, { err = string.char(0xE8,0xF5,0xED,0xE8,0xE9), fix = string.char(0xE8,0xF5) }, { err = string.char(0xE5,0xE2,0xEE,0xED,0xFB,0xE9), fix = string.char(0xE5,0xE3,0xEE) }, { err = string.char(0xE5,0xE9,0xED,0xFB,0xE9), fix = string.char(0xE5,0xB8) }, { err = string.char(0xED,0xE0,0xE2,0xE5,0xF0,0xED,0xEE), fix = string.char(0xED,0xE0,0xE2,0xE5,0xF0,0xED,0xEE,0xE5) }, } local function replaceErrorWords(text) if not text or #text == 0 then return text end local lowerText = toLowerStr(text) for _, entry in ipairs(error_corrections) do local err, fix, errLen, pos = entry.err, entry.fix, #entry.err, 1 while true do local foundStart = lowerText:find(err, pos, true) if not foundStart then break end local foundEnd = foundStart + errLen - 1 local prevByte = (foundStart > 1) and text:byte(foundStart - 1) or nil local nextByte = (foundEnd < #text) and text:byte(foundEnd + 1) or nil local validBoundary = (not isLetter(prevByte) and not isDigit(prevByte)) and (not isLetter(nextByte) and not isDigit(nextByte)) if validBoundary then local replaced = applyCaseMatch(text:sub(foundStart, foundEnd), fix) text = text:sub(1, foundStart - 1) .. replaced .. text:sub(foundEnd + 1) lowerText = toLowerStr(text) pos = foundStart + #replaced else pos = foundStart + 1 end end end return text end local function formatNumbers(text) if not text or #text == 0 then return text end text = text:gsub("(%d+)%$", "$%1") local result, i, len = "", 1, #text while i <= len do local s, e = text:find("%d+", i) if not s then result = result .. text:sub(i) break end result = result .. text:sub(i, s - 1) local match = text:sub(s, e) local prevChar = s > 1 and text:sub(s - 1, s - 1) or "" local nextChar = e < len and text:sub(e + 1, e + 1) or "" if #match >= 4 and not prevChar:match("[%a%.]") and not nextChar:match("[%a%.]") then result = result .. match:reverse():gsub("(%d%d%d)", "%1."):gsub("%.$", ""):reverse() else result = result .. match end i = e + 1 end return result end local function replaceDashes(text) if not text or #text == 0 then return text end local result = "" local len = #text local i = 1 while i <= len do local b = text:byte(i) if b == 0x2D then -- символ '-' local prevByte = (i > 1) and text:byte(i - 1) or nil local nextByte = (i < len) and text:byte(i + 1) or nil local hasLetterLeft = isLetter(prevByte) local hasLetterRight = isLetter(nextByte) local hasDigitLeft = isDigit(prevByte) local hasDigitRight = isDigit(nextByte) if hasLetterLeft or hasLetterRight then result = result .. "-" elseif hasDigitLeft or hasDigitRight then result = result .. string.char(0x96) -- короткое тире else result = result .. string.char(0x97) -- длинное тире end else result = result .. text:sub(i, i) end i = i + 1 end return result end local function replaceQuotes(text) if not text or #text == 0 then return text end local openQuote = true local result = "" for i = 1, #text do local char = text:sub(i, i) if char == '"' then if openQuote then result = result .. string.char(0xAB) else result = result .. string.char(0xBB) end openQuote = not openQuote else result = result .. char end end return result end local function capitalizeAfterPunctuation(text) local result = "" local capitalizeNext = false local firstLetterSeen = false -- Проверяем, начинается ли текст с одной или нескольких точек local startsWithDots = text:match("^%.+") ~= nil for i = 1, #text do local char = text:sub(i, i) local b = string.byte(char) if isLetter(b) then if not firstLetterSeen then firstLetterSeen = true -- Если текст начался с точек, отменяем заглавную букву для первого слова if startsWithDots then capitalizeNext = false end end end if capitalizeNext and char ~= " " then result = result .. (isLetter(b) and string.char(toUpperByte(b)) or char) capitalizeNext = false else result = result .. char end if char == "!" or char == "?" or char == "." then capitalizeNext = true end end return result end local function capitalizeFirst(text) if #text == 0 then return text end local first, b = text:sub(1, 1), text:byte(1) if isLetter(b) then first = string.char(toUpperByte(b)) end return first .. text:sub(2) end local function addEndingPunctuation(text) local trimmed = text:gsub("%s+$", "") if #trimmed == 0 then return text end local lastChar = trimmed:sub(-1) if lastChar == '.' or lastChar == '!' or lastChar == '?' then return text end return trimmed .. '.' end -- ===================== ЕДИНЫЙ ПАЙПЛАЙН ОБРАБОТКИ ===================== local function processTextPipeline(text, isCommand, commandName) if not text or #text == 0 then return text end -- 1. Защита ссылок и почты text = hideUrls(text) -- Распределение уровней обработки local isFullProcessing = false local isMeCommand = false if not isCommand then isFullProcessing = true elseif commandName then local lCmd = commandName:lower() if full_processing_commands[lCmd] then isFullProcessing = true elseif lCmd == "me" or lCmd == "ame" then isMeCommand = true end end -- 2. Умные пробелы (только для полных и /me) if (isFullProcessing or isMeCommand) and cfg.settings.smart_spaces then text = smartSpaces(text) end -- 3. Удаление лишних пробелов (для всех) if cfg.settings.clean_spaces then text = cleanSpaces(text) end -- 4. Жи/ши и исправление ошибок (для всех) if cfg.settings.zhishi then text = replaceZhishi(text) end if cfg.settings.fix_errors then text = replaceErrorWords(text) end -- 5. Форматирование чисел (только для полных и /me) if (isFullProcessing or isMeCommand) and cfg.settings.format_numbers then text = formatNumbers(text) end -- 6. Замена дефисов и кавычек (для всех) if cfg.settings.dashes then text = replaceDashes(text) end if cfg.settings.quotes then text = replaceQuotes(text) end -- 7. Капитализация if cfg.settings.capitalize then if isFullProcessing then text = capitalizeAfterPunctuation(text) text = capitalizeFirst(text) elseif isMeCommand then -- /me обрабатывается полностью, но без первой заглавной text = capitalizeAfterPunctuation(text) end end -- 8. Восстановление ссылок и почты (после капитализации) text = restoreUrls(text) -- 9. Точка в конце (только для полных и /me) if (isFullProcessing or isMeCommand) and cfg.settings.punctuation then text = addEndingPunctuation(text) end return text end -- ===================== ИНТЕРФЕЙС И СОБЫТИЯ ===================== imgui.OnFrame(function() return renderWindow[0] end, function(player) imgui.SetNextWindowPos(imgui.ImVec2(500, 400), imgui.Cond.FirstUseEver, imgui.ImVec2(0.5, 0.5)) imgui.SetNextWindowSize(imgui.ImVec2(400, 320), imgui.Cond.FirstUseEver) imgui.Begin(u8(L.window_title), renderWindow, imgui.WindowFlags.NoCollapse) imgui.Text(u8(L.input_helper)) imgui.Checkbox(u8(L.smart_spaces), cbSmartSpaces) imgui.Checkbox(u8(L.clean_spaces), cbCleanSpaces) imgui.Checkbox(u8(L.zhishi), cbZhishi) imgui.Checkbox(u8(L.fix_errors), cbFixErrors) imgui.Checkbox(u8(L.format_numbers), cbFormatNumbers) imgui.Checkbox(u8(L.dashes), cbDashes) imgui.Checkbox(u8(L.quotes), cbQuotes) imgui.Checkbox(u8(L.capitalize), cbCapitalize) imgui.Checkbox(u8(L.punctuation), cbPunctuation) imgui.SetCursorPosY(imgui.GetCursorPosY() + 10) if imgui.Button(u8(L.save_btn), imgui.ImVec2(-1, 30)) then cfg.settings.smart_spaces = cbSmartSpaces[0] cfg.settings.clean_spaces = cbCleanSpaces[0] cfg.settings.zhishi = cbZhishi[0] cfg.settings.fix_errors = cbFixErrors[0] cfg.settings.format_numbers = cbFormatNumbers[0] cfg.settings.dashes = cbDashes[0] cfg.settings.quotes = cbQuotes[0] cfg.settings.capitalize = cbCapitalize[0] cfg.settings.punctuation = cbPunctuation[0] saveAllConfig() renderWindow[0] = false sampAddChatMessage("{4766FF}[UCH] {FFFFFF}" .. L.saved, -1) end imgui.End() end) local function getStrByState(keyState) if keyState == 0 then return string.char(0xC2)..string.char(0xFB)..string.char(0xEA)..string.char(0xEB) end return string.char(0xC2)..string.char(0xEA)..string.char(0xEB) end local function translite(text) for k, v in pairs(chars) do text = string.gsub(text, k, v) end return text end local function showInputHelp() while true do if cfg.settings.input_helper then local chat = sampIsChatInputActive() if chat then local chatInfoPtr = sampGetInputInfoPtr() local chatBoxInfo = getStructElement(chatInfoPtr, 0x8, 4) local chatEditInfo = getStructElement(chatBoxInfo, 0x8, 4) local chatDrawInfo = getStructElement(chatBoxInfo, 0xC, 4) local fib = chatDrawInfo + 41 local fib2 = chatEditInfo + 10 local _, pID = sampGetPlayerIdByCharHandle(playerPed) local name = sampGetPlayerNickname(pID) local color = sampGetPlayerColor(pID) local capsState = ffi.C.GetKeyState(20) ffi.C.GetKeyboardLayoutNameA(KeyboardLayoutName) ffi.C.GetLocaleInfoA(tonumber(ffi.string(KeyboardLayoutName), 16), 0x00000002, LocalInfo, BuffSize) local localName = ffi.string(LocalInfo) local text = string.format( "%s :: {%0.6x}%s[%d] {ffffff}:: %s: %s {FFFFFF}:: %s: {ffeeaa}%s{ffffff}", os.date("%H:%M:%S"), bit.band(color,0xffffff), name, pID, string.char(0xCA,0xE0,0xEF,0xF1), getStrByState(capsState), string.char(0xDF,0xE7,0xFB,0xEA), string.match(localName, "([^%(]*)") ) renderFontDrawText(inputHelpText, text, fib2, fib, 0xD7FFFFFF) end end wait(0) end end local function inputChat() while true do if cfg.settings.input_helper and sampIsChatInputActive() then local getInput = sampGetChatInputText() if oldText ~= getInput and #getInput > 0 then local firstChar = string.sub(getInput, 1, 1) if firstChar == "." or firstChar == "/" then local cmd, text = string.match(getInput, "^([^ ]+)(.*)") if cmd then local cleanCmd = translite(string.sub(cmd, 2)) local nText = "" -- Транслитерация аргументов команды /id if cleanCmd:lower() == "id" then nText = "/" .. cleanCmd .. translite(text) else nText = "/" .. cleanCmd .. text end local chatInfoPtr = sampGetInputInfoPtr() local chatBoxInfo = getStructElement(chatInfoPtr, 0x8, 4) local lastPos = mem.getint8(chatBoxInfo + 0x11E) sampSetChatInputText(nText) mem.setint8(chatBoxInfo + 0x11E, lastPos) mem.setint8(chatBoxInfo + 0x119, lastPos) oldText = nText end end end end wait(0) end end sampev.onSendChat = function(text) if text:sub(1, 1) == "/" then return {text} end local newText = processTextPipeline(text, false, nil) return {newText} end sampev.onSendCommand = function(cmd) local spacePos = cmd:find(" ") if spacePos then local command = cmd:sub(1, spacePos - 1) local args = cmd:sub(spacePos + 1) local clean_cmd = command:lower():gsub("^/", "") args = processTextPipeline(args, true, clean_cmd) return {command .. " " .. args} else return {cmd} end end function main() if not isSampLoaded() or not isSampfuncsLoaded() then return end while not isSampAvailable() do wait(100) end -- Надежный вывод сообщения о загрузке после инициализации sampAddChatMessage("{4766FF}[Ultimate Chat Helper - /uch] {00FF00}v2.1 {FFFFFF}" .. L.loaded .. " {4766FF}Restarv", -1) inputHelpText = renderCreateFont("Arial", 9, FCR_BORDER + FCR_BOLD) sampRegisterChatCommand("uch", function() renderWindow[0] = not renderWindow[0] end) lua_thread.create(inputChat) lua_thread.create(showInputHelp) while true do wait(0) if isKeyDown(VK_T) and wasKeyPressed(VK_T) then if not sampIsChatInputActive() and not sampIsDialogActive() then sampSetChatInputEnabled(true) end end end wait(-1) end