script_name('AutoNalog') require 'lib.moonloader' local sampev = require 'samp.events' local vkeys = require 'vkeys' local encoding = require 'encoding' encoding.default = 'CP1251' local u8 = encoding.UTF8 local function cp(text) return u8:decode(text) end local enabled = true local working = false local frozen = false local lastHomeDialog = nil local lastPayDialog = nil local TAX_LIMIT = 100000 local TAX_POS = { x = -2228.07, y = -17.28, z = 10.68 } local PAY_MENU_CLICK = { x = 175, y = 164 } local PAY_MENU_FALLBACK_ITEM = 3 local AUTH_MESSAGE = cp('[A] Вы успешно авторизовались как') local HOME_NEEDLES = { cp('МОИ ДОМА'), cp('Мои дома') } local TRAILER_NEEDLES = { cp('ТРЕЙЛЕР'), cp('Трейлер') } local PAY_TAX_NEEDLES = { cp('ОПЛАТИТЬ НАЛОГИ НА ТРЕЙЛЕРЫ'), cp('Оплатить налоги на трейлеры') } local HOME_DIALOG_TIMEOUT = 3000 local PAY_DIALOG_TIMEOUT = 700 local AFTER_HOME_DELAY = 250 local AFTER_TP_DELAY = 900 local function msg(text) sampAddChatMessage(cp('{66CCFF}[AutoTax]{FFFFFF} ' .. text), -1) end local function stripColors(text) return (text or ''):gsub('{%x%x%x%x%x%x}', '') end local function containsAny(text, needles) text = stripColors(text) for _, needle in ipairs(needles) do if text:find(needle, 1, true) then return true end end return false end local function isHomeText(text) return containsAny(text, HOME_NEEDLES) end local function isTrailerText(text) return containsAny(text, TRAILER_NEEDLES) end local function isPayTaxText(text) return containsAny(text, PAY_TAX_NEEDLES) end local function matchesNeedle(text, needles) if not needles then return true end return containsAny(text, needles) end local function moneyToNumber(text) local digits = (text or ''):gsub('%D', '') if digits == '' then return nil end return tonumber(digits) end local function extractTrailerTax(dialogText) local clean = stripColors(dialogText) local maxTax = nil for line in clean:gmatch('[^\r\n]+') do if isTrailerText(line) then for rawTax in line:gmatch('(%d[%d%.%,%s]*)%s*/%s*%d[%d%.%,%s]*') do local tax = moneyToNumber(rawTax) if tax and (not maxTax or tax > maxTax) then maxTax = tax end end end end if maxTax then return maxTax end for rawTax in clean:gmatch('(%d[%d%.%,%s]*)%s*/%s*%d[%d%.%,%s]*') do local tax = moneyToNumber(rawTax) if tax and (not maxTax or tax > maxTax) then maxTax = tax end end return maxTax end local function formatPosCommand(x, y, z) return string.format('/pos %.2f %.2f %.2f', x, y, z):gsub(',', '.') end local function sendPos(x, y, z) sampSendChat(formatPosCommand(x, y, z)) end local function pressKey(vk, holdMs) setVirtualKeyDown(vk, true) wait(holdMs or 80) setVirtualKeyDown(vk, false) end local function pressAlt() pressKey(vkeys.VK_MENU, 80) end local function pressEnter() pressKey(vkeys.VK_RETURN, 60) end local function pressEsc() pressKey(vkeys.VK_ESCAPE, 60) end local function clickAt(x, y) if type(setCursorPos) == 'function' then setCursorPos(x, y) wait(60) end pressKey(vkeys.VK_LBUTTON, 60) end local function setPlayerFrozen(state) frozen = state if type(freezeCharPosition) == 'function' then pcall(freezeCharPosition, PLAYER_PED, state) end if type(setPlayerControl) == 'function' and PLAYER_HANDLE then pcall(setPlayerControl, PLAYER_HANDLE, not state) end end local function waitDialogContains(needles, timeout) local elapsed = 0 while elapsed <= timeout do if sampIsDialogActive() then local id = sampGetCurrentDialogId() local caption = sampGetDialogCaption() or '' local text = sampGetDialogText() or '' if matchesNeedle(caption, needles) or matchesNeedle(text, needles) then return true, id, caption, text end end if needles and lastHomeDialog and (matchesNeedle(lastHomeDialog.title, needles) or matchesNeedle(lastHomeDialog.text, needles)) then return true, lastHomeDialog.id, lastHomeDialog.title, lastHomeDialog.text end if needles and lastPayDialog and (matchesNeedle(lastPayDialog.title, needles) or matchesNeedle(lastPayDialog.text, needles)) then return true, lastPayDialog.id, lastPayDialog.title, lastPayDialog.text end wait(100) elapsed = elapsed + 100 end return false end local function closeDialog(button) if sampIsDialogActive() then if type(sampCloseCurrentDialogWithButton) == 'function' then sampCloseCurrentDialogWithButton(button or 0) else sampSendDialogResponse(sampGetCurrentDialogId(), button or 0, -1, '') end else pressEsc() end wait(120) end local function findListItem(dialogText, needles) local index = 0 local clean = stripColors(dialogText) for line in clean:gmatch('[^\r\n]+') do local trimmed = line:gsub('^%s+', ''):gsub('%s+$', '') if trimmed ~= '' then if matchesNeedle(trimmed, needles) then return index end index = index + 1 end end return nil end local function openPayDialog() lastPayDialog = nil pressAlt() local ok, id, caption, text = waitDialogContains(PAY_TAX_NEEDLES, PAY_DIALOG_TIMEOUT) if ok then return 'samp', id, caption, text end return 'cef' end local function acceptTwoDialogs() for _ = 1, 2 do waitDialogContains(nil, 1500) pressEnter() wait(300) end end local function acceptTwoCefDialogs() for _ = 1, 2 do wait(300) pressEnter() end end local function autoPayTaxes() if working then return end working = true setPlayerFrozen(true) local ok, err = pcall(function() wait(AFTER_HOME_DELAY) lastHomeDialog = nil lastPayDialog = nil sampSendChat('/home') local homeOk, _, _, homeText = waitDialogContains(HOME_NEEDLES, HOME_DIALOG_TIMEOUT) if not homeOk then msg('Ошибка: не дождался /home.') return end local tax = extractTrailerTax(homeText) if not tax then closeDialog(0) msg('Ошибка: не нашел налог трейлера.') return end if tax <= TAX_LIMIT then closeDialog(0) return end local oldX, oldY, oldZ = getCharCoordinates(PLAYER_PED) closeDialog(0) wait(120) sendPos(TAX_POS.x, TAX_POS.y, TAX_POS.z) wait(AFTER_TP_DELAY) local payMode, payId, _, payText = openPayDialog() if payMode == 'samp' then local item = findListItem(payText, PAY_TAX_NEEDLES) or PAY_MENU_FALLBACK_ITEM sampSendDialogResponse(payId, 1, item, '') else clickAt(PAY_MENU_CLICK.x, PAY_MENU_CLICK.y) end wait(250) if payMode == 'samp' then acceptTwoDialogs() else acceptTwoCefDialogs() end wait(500) sendPos(oldX, oldY, oldZ) wait(250) end) setPlayerFrozen(false) if not ok then msg('Ошибка: ' .. tostring(err)) end working = false end function sampev.onServerMessage(_, text) if enabled and not working and stripColors(text):find(AUTH_MESSAGE, 1, true) then lua_thread.create(autoPayTaxes) end end function sampev.onShowDialog(dialogId, style, title, button1, button2, text) local fullText = (title or '') .. '\n' .. (text or '') if isHomeText(fullText) or isTrailerText(fullText) then lastHomeDialog = { id = dialogId, title = title or '', text = fullText } end if isPayTaxText(fullText) then lastPayDialog = { id = dialogId, title = title or '', text = fullText } end end function onScriptTerminate(script, quitGame) if script == thisScript() and frozen then setPlayerFrozen(false) end end function main() repeat wait(0) until isSampAvailable() sampRegisterChatCommand('taxauto', function() enabled = not enabled msg(enabled and 'Автооплата включена.' or 'Автооплата выключена.') end) sampRegisterChatCommand('taxnow', function() if working then msg('Ошибка: проверка уже выполняется.') return end lua_thread.create(autoPayTaxes) end) msg('Загружен.') while true do wait(1000) end end