script_name('Ultimate Chat Helper') script_version_number(1) 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') 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) 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)] = ">", } inputHelpText = nil oldText = nil local russian_characters = { [168] = string.char(0xA8), [184] = string.char(0xB8), [192] = string.char(0xC0), [193] = string.char(0xC1), [194] = string.char(0xC2), [195] = string.char(0xC3), [196] = string.char(0xC4), [197] = string.char(0xC5), [198] = string.char(0xC6), [199] = string.char(0xC7), [200] = string.char(0xC8), [201] = string.char(0xC9), [202] = string.char(0xCA), [203] = string.char(0xCB), [204] = string.char(0xCC), [205] = string.char(0xCD), [206] = string.char(0xCE), [207] = string.char(0xCF), [208] = string.char(0xD0), [209] = string.char(0xD1), [210] = string.char(0xD2), [211] = string.char(0xD3), [212] = string.char(0xD4), [213] = string.char(0xD5), [214] = string.char(0xD6), [215] = string.char(0xD7), [216] = string.char(0xD8), [217] = string.char(0xD9), [218] = string.char(0xDA), [219] = string.char(0xDB), [220] = string.char(0xDC), [221] = string.char(0xDD), [222] = string.char(0xDE), [223] = string.char(0xDF), [224] = string.char(0xE0), [225] = string.char(0xE1), [226] = string.char(0xE2), [227] = string.char(0xE3), [228] = string.char(0xE4), [229] = string.char(0xE5), [230] = string.char(0xE6), [231] = string.char(0xE7), [232] = string.char(0xE8), [233] = string.char(0xE9), [234] = string.char(0xEA), [235] = string.char(0xEB), [236] = string.char(0xEC), [237] = string.char(0xED), [238] = string.char(0xEE), [239] = string.char(0xEF), [240] = string.char(0xF0), [241] = string.char(0xF1), [242] = string.char(0xF2), [243] = string.char(0xF3), [244] = string.char(0xF4), [245] = string.char(0xF5), [246] = string.char(0xF6), [247] = string.char(0xF7), [248] = string.char(0xF8), [249] = string.char(0xF9), [250] = string.char(0xFA), [251] = string.char(0xFB), [252] = string.char(0xFC), [253] = string.char(0xFD), [254] = string.char(0xFE), [255] = string.char(0xFF), } local function rupper(s) s = s:upper() local strlen = s:len() if strlen == 0 then return s end local output = '' for i = 1, strlen do local ch = s:byte(i) if ch >= 224 and ch <= 255 then output = output .. russian_characters[ch - 32] elseif ch == 184 then output = output .. russian_characters[168] else output = output .. string.char(ch) end end return output end 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 function translite(text) for k, v in pairs(chars) do text = string.gsub(text, k, v) end return text end function showInputHelp() while true do local chat = sampIsChatInputActive() if chat == true then local in1 = sampGetInputInfoPtr() local in1 = getStructElement(in1, 0x8, 4) local in2 = getStructElement(in1, 0x8, 4) local in3 = getStructElement(in1, 0xC, 4) fib = in3 + 41 fib2 = in2 + 10 local _, pID = sampGetPlayerIdByCharHandle(playerPed) local name = sampGetPlayerNickname(pID) local score = sampGetPlayerScore(pID) local color = sampGetPlayerColor(pID) local capsState = ffi.C.GetKeyState(20) local success = ffi.C.GetKeyboardLayoutNameA(KeyboardLayoutName) local errorCode = 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 wait(0) end end function inputChat() while true do if(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, "^([^ ]+)(.*)") local nText = "/" .. translite(string.sub(cmd, 2)) .. text 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 wait(0) end end 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 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 byte = text:byte(i) if byte == 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 prevLetter = prevByte and isLetter(prevByte) or false local nextLetter = nextByte and isLetter(nextByte) or false local prevDigit = prevByte and isDigit(prevByte) or false local nextDigit = nextByte and isDigit(nextByte) or false if prevLetter or nextLetter then table.insert(result, string.char(0x2D)) elseif prevDigit or nextDigit then table.insert(result, string.char(0x96)) else table.insert(result, string.char(0x97)) end else table.insert(result, text:sub(i, i)) end i = i + 1 end return table.concat(result) end local function capitalizeFirst(text) if #text == 0 then return text end local first = text:sub(1, 1) local rest = text:sub(2) return rupper(first) .. rest 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 else return trimmed .. '.' end end sampev.onSendChat = function(text) if text:sub(1, 1) == "/" then return {text} end local newText = replaceDashes(text) newText = capitalizeFirst(newText) newText = addEndingPunctuation(newText) 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) args = replaceDashes(args) return {command .. " " .. args} else return {cmd} end end sampev.onServerMessage = function(color, text) local connected_ru = string.char(0xEF,0xEE,0xE4,0xEA,0xEB,0xFE,0xF7,0xE8,0xEB,0xF1,0xFF) if text:find('Connected') or text:find(connected_ru) then sampAddChatMessage('{FAB907}Ultimate Chat Helper | {00FF00}True | {FFFFFF}Loaded!', -1) sampAddChatMessage('{FFFFFF}Author: {FAB907}Reverse', -1) end end function main() if not isSampLoaded() or not isSampfuncsLoaded() then return end while not isSampAvailable() do wait(100) end inputHelpText = renderCreateFont("Arial", 9, FCR_BORDER + FCR_BOLD) 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