Помощь по логике бота SA:MP на lua

TryNget

Новичок
Автор темы
1
0
Версия MoonLoader
Другое
Здравствуйте. Совсем недавно начал изучать lua, это мой второй язык программирования, который я решил освоить. В первый день смотрел синтаксис и просто как устроен язык. Через неделю приступил к практике написания скриптов для SA:MP и решил написать бота трамвайщика для ARZ, и так как это был мой первый подобный опыт я написал его логику "в лоб", то есть просто через нажатия клавиш бот должен выполнять все требуемые действия, ну и как начал так и продолжил. Так вот, основа логики езды трамвая основана на координатах: 1) точек чекпоинтов, 2) точек когда нужно начать тормозить чтобы остановиться на точках чекпоинтов. Мой вопрос: работает ли данный метод на любом устройстве? Ведь, очевидно, пинг, фпс и т.п. у каждого будет различаться. Я ставил ограничение фпс и вроде трамвай ехал как надо, но я всё же хочу уточнить этот момент пока бот еще довольно сырой (он уже полностью работает и автоматизирован, но имеет свои баги и явно нуждается в доработках) и потом мне не пришлось переписывать чуть ли не всю основу кода. Также я прикрепил видео для наглядности и полный код если вдруг кто заметит очевидно неверные подходы или просто не совсем рациональные решения в скрипте.
Заранее огромное спасибо!

Lua:
-- Подключение необходимых библиотек
require("lib.moonloader")
require("lib.sampfuncs")
local encoding = require "encoding"
local imgui = require "imgui"
local samp_ev = require "lib.samp.events"

-- Глобальные переменные для управления состоянием бота
run = false
play = false
stage = 1
time_count = 0
forced_stop = false
adm_check = false

-- Структуры для хранения данных о диалоговых окнах и статистике
dialogw = {
    dw_dialogId = nil,
    dw_style = nil,
    dw_title = nil,
    dw_button1 = nil,
    dw_button2 = nil,
    dw_text = nil
}
stats = {
    time_full = 0,
    salary = 0,
    route_count = 0
}

-- Координаты для взятия маршрута
spawn_point = {-2265.9475097656, 501.72549438477, 1487.6927490234}
take_point = {{-2263.2526855469, 513.08807373047, 1487.6917724609}}
restart_point = {
    {-2261.638671875, 509.93923950195, 1487.7027587891},
    {-2261.6967773438, 506.30764770508, 1487.7027587891},
    {-2260.1748046875, 502.38452148438, 1487.6927490234}
}
restart_food = {
    {-2261.3422851563, 512.25323486328, 1487.6927490234},
    {-2258.9245605469, 515.13458251953, 1487.6927490234},
    {-2257.9938964844, 511.3674621582, 1487.6927490234}
}
foot_coords = {
    {-2263.0354003906, 502.57992553711, 1487.6927490234},
    {-2262.5622558594, 502.96221923828, 1487.6927490234},
    {-2262.4440917969, 504.65939331055, 1487.6927490234},
    {-2262.9919433594, 507.09457397461, 1487.6927490234}
}
foot_coords1 = {
    {-2261.4553222656, 503.47729492188, 1487.6917724609},
    {-2261.2084960938, 503.5869140625, 1487.6917724609},
    {-2260.0004882813, 504.40594482422, 1487.6917724609},
    {-2258.7253417969, 505.50250244141, 1487.6917724609},
    {-2258.1333007813, 506.90542602539, 1487.6917724609},
    {-2257.9938964844, 511.75677490234, 1487.6917724609}
}
restart_p = {false, false}
-- Координаты остановок и торможения
main_coords = {
    {-2264.9060058594, 781.74798583984, 49.841285705566},
    {-2264.875, 1030.8063964844, 84.130714416504},
    {-1988.6790771484, 1307.875, 7.4973087310791},
    {-1641.5390625, 1254.5151367188, 7.4973087310791},
    {-1538.7077636719, 967.39538574219, 7.4973087310791},
    {-1741.2626953125, 921.125, 25.122308731079},
    {-2001.625, 892.17053222656, 45.74730682373},
    {-1874.95703125, 848.875, 35.49730682373},
    {-1604.5676269531, 848.908203125, 7.9973087310791},
    {-1626.8010253906, 728.75, 14.902610778809},
    {-1865.2484130859, 603.25, 35.49730682373},
    {-2006.5, 210.66424560547, 27.997308731079},
    {-2006.5, 88.550201416016, 27.997308731079},
    {-2166.625, -70.026824951172, 35.62230682373},
    {-2251.5573730469, 149.05737304688, 35.62230682373},
    {-2339.7514648438, 508.5, 30.301301956177},
}
brake_coords = {
    {-2264.9506835938, 732.50493164063, 49.79679107666},
    {-2264.875, 977.96145019531, 70.373799133301},
    {-2042.9405517578, 1305.6669921875, 7.6223087310791},
    {-1679.6846923828, 1292.7875976563, 7.4973087310791},
    {-1573.2088623047, 1011.6076660156, 7.4973087310791},
    {-1687.3196533203, 921.125, 25.122308731079},
    {-1972.1208496094, 921, 44.641761779785},
    {-1911.8176269531, 848.875, 35.49730682373},
    {-1658.4816894531, 848.875, 19.5728931427},
    {-1572.5378417969, 728.75, 7.6708054542542},
    {-1809.8125976563, 603.25, 34.998847961426},
    {-2004.6945800781, 265.88493652344, 31.753614425659},
    {-2006.5, 115.27388916016, 27.997308731079},
    {-2166.625, -48.292022705078, 35.62230682373},
    {-2251.8759765625, 96.16723632813, 35.62230682373},
    {-2361.2368164063, 483.1110534668, 30.597286224365}
}

function main()
    -- Ожидание загрузки SA:MP
    while not isSampAvailable() do wait(1000) end

    -- ImGui
    myImgui = {
        windows = {status = {main = imgui.ImBool(false)}}
    }
    apply_custom_style()
    print("{27AB0F}LOADED {FFFFFF}(/tramon)")

    -- Регистрация команд для включения и выключения бота
    sampRegisterChatCommand('tramon', function()
        if not run then
            run = true
            stats.time_full = os.clock()
            sampAddChatMessage('> {FFFFFF}Tram turned {27AB0F}[ENABLED]', 0xFF27ab0f)
            lua_thread.create(thread_checks)
        else
            sampAddChatMessage('> {FFFFFF}Tram is already {E5FF00}[ENABLED]', 0xFFe5ff00)
        end
    end)

    sampRegisterChatCommand('tramoff', function()
        if run then
            run = false
            play = false
            stage = 1
            time_count = 0
            stats.time_full = 0
            dialogvalue = false
            sampAddChatMessage('> {FFFFFF}Tram turned {AB0F0F}[DISABLED]', 0xFFab0f0f)
        else
            sampAddChatMessage('> {FFFFFF}Tram is already {E5FF00}[DISABLED]', 0xFFe5ff00)
        end
    end)

    -- Основной цикл бота
    while true do
        wait(0)
        -- Проверка нажатия клавиши для запуска бота
        if wasKeyPressed(VK_1) and run and not play and not sampIsCursorActive() then
            play = true
            sampAddChatMessage('Bot started.', 0xFF27ab0f)
            time_count = os.clock()
            stats.time_full = os.clock()
            if isCharInAnyCar(PLAYER_PED) then
                stage = 2
            else
                stage = 1
            end
        end

        -- Основная логика бота
        if play then
            if stage == 2 and isCharInAnyCar(PLAYER_PED) then
                for i = 1, #brake_coords do
                    wait(300)
                    if not isCharInAnyCar(PLAYER_PED) and not forced_stop then
                        break
                    end
                    if not isCharInAnyCar(PLAYER_PED) and forced_stop then
                        while not isCharInAnyCar(PLAYER_PED) do
                            wait(0)
                        end
                    end
                    local tram = storeCarCharIsInNoSave(PLAYER_PED)
                    local xR, yR, zR = main_coords[i][1], main_coords[i][2], main_coords[i][3]
                    local xBr, yBr, zBr = brake_coords[i][1], brake_coords[i][2], brake_coords[i][3]
                    local xN, yN, zN = getCharCoordinates(PLAYER_PED)
                    local dist = getDistanceBetweenCoords3d(xR, yR, zR, xN, yN, zN)
                    local dist_brake = getDistanceBetweenCoords3d(xBr, yBr, zBr, xN, yN, zN)
                    local br_vel = 0.06
                    local braking = false

                    while dist > 3 or getCarSpeed(tram) > 0.1 do
                        wait(0)
                        if adm_check then
                            admin_check()
                        end
                        if isCharInAnyCar(PLAYER_PED) then
                            local xn, yn, zn = getCharCoordinates(PLAYER_PED)
                            dist = getDistanceBetweenCoords3d(xR, yR, zR, xn, yn, zn)
                            dist_brake = getDistanceBetweenCoords3d(xBr, yBr, zBr, xn, yn, zn)

                            if braking then
                                draw_line(xR, yR, zR, "db1d1d", 0, 0, 0)
                                printStringNow("Distance (No." .. i .."): ~r~~h~" .. math.floor(dist) .. "m~n~~w~Velocity: ~r~~h~" .. math.floor(getCarSpeed(tram) * 3.5))
                            else
                                draw_line(xR, yR, zR, "1ddb4f", xBr, yBr, zBr)
                                printStringNow("Distance (No." .. i .."): ~g~~h~" .. math.floor(dist) .. "m~n~~w~Velocity: ~g~~h~" .. math.floor(getCarSpeed(tram) * 3.5))
                            end

                            if (not braking and dist_brake < 1) or forced_stop then
                                braking = true
                            end
                            if not braking then
                                if getCarSpeed(tram) < 24 then
                                    press_gas()
                                else
                                    press_brake(braking)
                                end
                            else
                                if getCarSpeed(tram) > br_vel then
                                    press_brake(braking)
                                else
                                    if br_vel < 0.5 and not forced_stop then
                                        br_vel = 1
                                    end
                                    press_gas()
                                end
                                if br_vel > 0.5 and dist <= 1 then
                                    br_vel = 0.06
                                end
                            end
                            if wasKeyPressed(VK_3) then
                                sampAddChatMessage('playing paused.', -1)
                                while true do
                                    wait(0)
                                    if wasKeyPressed(VK_1) and not sampIsCursorActive() then
                                        sampAddChatMessage('playing unpaused...', -1)
                                        break
                                    end
                                end
                            end
                        else
                            break
                        end
                    end
                end
                stage = 1
            else
                if restart_p[1] and not restart_p[2] then
                    local resX, resY, resZ = getCharCoordinates(PLAYER_PED)
                    if getDistanceBetweenCoords3d(spawn_point[1], spawn_point[2], spawn_point[3], resX, resY, resZ) > 2 then
                        take_route(restart_point)
                    else
                        take_route(foot_coords)
                    end
                else
                    if os.clock() - time_count >= 2000 then
                        time_count = os.clock()
                        local notfood = take_route(foot_coords1)
                        if notfood then
                            take_route(restart_food)
                        else
                            take_route(take_point)
                        end
                    else
                        take_route(foot_coords)
                    end
                end
            end
        end
        if not play and run then
            draw_line(0, 0, 0, "ca61ff", 0, 0, 0)
        end
    end
end

-- Функция для нажатия на газ
function press_gas()
    if not sampIsCursorActive() then
        writeMemory(0xB73458 + 0x20, 1, 255, false)
    else
        if not forced_stop then
            admin_check()
        end
    end
end

-- Функция для нажатия на тормоз
function press_brake(braking)
    if not sampIsCursorActive() or forced_stop then
        if not braking then
            writeMemory(0xB73458 + 0xC, 1, 64, false)
        else
            writeMemory(12006516, 1, 255, false)
        end
    else
        admin_check()
    end
end

-- Функция для установки позиции камеры
function set_camera_pos_unfix(posX, posY)
    local cPosX, cPosY, cPosZ = getActiveCameraCoordinates()
    setCameraPositionUnfixed(0.0, (getHeadingFromVector2d(posX - cPosX, posY - cPosY) - 90.0) / 57.2957795)
end

-- Функция для взятия маршрута
function take_route(coords)
    if not restart_p[1] and not restart_p[2] then
        timer(3000)
    end
    if not restart_p[1] and restart_p[2] then
        restart_p = {false, false}
    end
    wait(10)
    if not sampIsCursorActive() and not isCharInAnyCar(PLAYER_PED) then
        sampAddChatMessage("Time to eat: " .. math.floor(os.clock() - time_count) .. " / 2000", -1)

        local Xn, Yn, Zn = getCharCoordinates(PLAYER_PED)
        local Xf, Yf, Zf = spawn_point[1], spawn_point[2], spawn_point[3]
        if getDistanceBetweenCoords3d(Xn, Yn, Zn, Xf, Yf, Zf) > 100 then
            sampAddChatMessage("Too big distance:", 0xFFd10f0f)
            admin_check()
        end
        if restart_p[1] then
            restart_p[2] = true
        end

        local coords_check = {}
        local delay = 0
        if coords == take_point or coords == foot_coords then
            delay = 100
        end
        for i = 1, #coords do
            if isCharInAnyCar(PLAYER_PED) then
                admin_check()
                break
            end
            if (restart_p[1] and not restart_p[2]) or (not restart_p[1] and restart_p[2]) then
                break
            end
            local posX, posY, zF = coords[i][1], coords[i][2], coords[i][3]
            local xN, yN, zN = getCharCoordinates(PLAYER_PED)
            local dist = getDistanceBetweenCoords3d(posX, posY, zF, xN, yN, zN)
            local close_dist = getDistanceBetweenCoords3d(Xf, Yf, Zf, xN, yN, zN)
            local sprint = true
            if i == #coords then
                sprint = false
            end

            while dist > 1 do
                wait(0)
                if isCharInAnyCar(PLAYER_PED) then
                    break
                end
                if sampIsCursorActive() or adm_check then
                    admin_check()
                end

                local xn, yn, zn = getCharCoordinates(PLAYER_PED)

                table.insert(coords_check, {xn, yn, zn})
                if #coords_check >= 50 then
                    if delay == 0 then
                        local x_check1, y_check1, z_check1 = coords_check[1][1], coords_check[1][2], coords_check[1][3]
                        local x_check2, y_check2, z_check2 = coords_check[50][1], coords_check[50][2], coords_check[50][3]
                        if getDistanceBetweenCoords3d(x_check1, y_check1, z_check1, x_check2, y_check2, z_check2) < 0.1 then
                            if not restart_p[1] then
                                sampAddChatMessage("Stucked. restart...", -1)
                                restart_p[1] = true
                            else
                                admin_check()
                                restart_p = {false, true}
                            end
                            break
                        end
                    else
                        delay = delay - 1
                    end
                    table.remove(coords_check, 1)
                end

                dist = getDistanceBetweenCoords3d(posX, posY, zF, xn, yn, zn)
                close_dist = getDistanceBetweenCoords3d(Xf, Yf, Zf, xn, yn, zn)
                setGameKeyState(1, -128)
                if sprint then
                    setGameKeyState(16, 255)
                end
                set_camera_pos_unfix(posX, posY)
                draw_line(posX, posY, zF, "1d95db", 0, 0, 0)
                if wasKeyPressed(VK_3) then
                    sampAddChatMessage('playing paused.', -1)
                    while true do
                        wait(0)
                        if wasKeyPressed(VK_1) and not sampIsCursorActive() then
                            sampAddChatMessage('playing unpaused...', -1)
                            break
                        end
                    end
                end
            end
        end
        wait(500)
        if sampIsCursorActive() then
            admin_check()
        end
        if not restart_p[1] and not restart_p[2] and not isCharInAnyCar(PLAYER_PED) then
            printStringNow("~b~~h~Alt ~w~pressed", 300)
            press_button(18)
            wait(250)
            if sampIsCursorActive() then
                if dialogw.dw_dialogId == 4297 or dialogw.dw_dialogId == 185 then
                    wait(50)
                    printStringNow("~b~~h~Enter ~w~pressed", 300)
                    press_button(13)
                end
                clear_dw_data()
            else
                if coords == foot_coords1 then
                    if restart_p[1] and restart_p[2] then
                        restart_p = {false, false}
                    end
                    return true
                end
            end
        end
        if restart_p[1] and restart_p[2] then
            restart_p = {false, false}
        end
        if coords ~= foot_coords1 and not restart_p[1] and not restart_p[2] and coords ~= restart_point and not isCharInAnyCar(PLAYER_PED) then
            timer(3000)
            wait(10)
            while not sampIsCursorActive() do
                wait(0)
                if not isCharInAnyCar(PLAYER_PED) then
                    break
                end
            end
            press_button(13)
        end
        stage = 2
    else
        if sampIsCursorActive() then
            admin_check()
        else
            finish_route()
        end
    end
end

-- Функция для досрочного завершения маршрута если пропустил чекпоинт
function finish_route()
    forced_stop = true
    while not sampIsCursorActive() do
        wait(0)
        if not isCharInAnyCar(PLAYER_PED) then
            break
        end
        timer(300)
        press_button(70)
        wait(300)
    end
    forced_stop = false
    if sampIsCursorActive() then
        if dialogw.dw_dialogId == 4295 then
            press_button(13)
        else
            admin_check()
        end
        clear_dw_data()
    end
end

-- Функция для рисования линии
function draw_line(posX, posY, posZ, col, dopX, dopY, dopZ)
    local chPosX, chPosY, chPosZ = getCharCoordinates(PLAYER_PED)
    local ang = 14
    if not play then
        chPosZ = chPosZ + 1
        ang = 3
    end
    local wPosX, wPosY = convert3DCoordsToScreen(posX, posY, posZ)
    local wPosX1, wPosY1 = convert3DCoordsToScreen(chPosX, chPosY, chPosZ)
    if isPointOnScreen(posX, posY, posZ, 1) and play then
        renderDrawLine(wPosX1, wPosY1, wPosX, wPosY, 2, "0xFF" .. col)
        renderDrawPolygon(wPosX, wPosY, 10, 10, 4, 0, "0x7F" .. col)
    end
    if stage == 2 and dopX ~= 0 and dopY ~= 0 and dopZ ~= 0 then
        local dPosX, dPosY = convert3DCoordsToScreen(dopX, dopY, dopZ)
        if isPointOnScreen(dopX, dopY, dopZ, 1) then
            renderDrawPolygon(dPosX, dPosY, 30, 30, 14, 0, "0x7Fdb1d1d")
        end
    end
    renderDrawPolygon(wPosX1, wPosY1 , 15, 15, ang, 180, "0x9F" .. col)
end

-- Функция для краша при подозрении на админа
function admin_check()
    adm_check = true
    if sampIsCursorActive() then
        timer(2000)
        press_button(13)
        wait(10)
        sampAddChatMessage("(admin check): Dialog window closed.", -1)
    end
    local intime = 4
    local t = os.clock()
    local n = {0, true}
    sampAddChatMessage("!!! The game will shutdown in " .. intime .. "s. Press 0 to cancel. !!!", 0xFFd10f0f)
    timer(500)
    wait(100)
    press_button(84)
    sampAddChatMessage("(admin check): Chat opened.", -1)
    while os.clock() - t < intime do
        wait(0)
        printStringNow("~r~Shutdown in " .. intime - math.floor((os.clock() - t) * 10) / 10 .. "s")
        if wasKeyPressed(VK_0) then
            n[2] = false
            adm_check = false
            sampAddChatMessage("Canceled.", -1)
            sampAddChatMessage('playing paused.', -1)
            while true do
                wait(0)
                if wasKeyPressed(VK_1) and not sampIsCursorActive() then
                    sampAddChatMessage('playing unpaused...', -1)
                    break
                end
            end
            break
        end
    end
    if n[2] then
        while n[2] do
            if wasKeyPressed(VK_0) then
                n[2] = false
            end
        end
    end
    wait(1000)
end

-- Функция для нажатия на кнопку
function press_button(id)
    setVirtualKeyDown(id, true)
    wait(50)
    setVirtualKeyDown(id, false)
end

-- Функция таймера
function timer(time)
    local t = os.clock()
    while os.clock() - t < time / 1000 do
        wait(0)
        printStringNow("Wait: ~y~" .. time / 1000 - math.floor((os.clock() - t) * 10) / 10 .. "s" )
        if wasKeyPressed(VK_3) then
            sampAddChatMessage('playing paused.', -1)
            local ps = time / 1000 - math.floor((os.clock() - t) * 10) / 10
            while true do
                wait(0)
                printStringNow("Wait (paused): ~r~~h~~h~" .. ps .. "s" )
                if wasKeyPressed(VK_1) and not sampIsCursorActive() then
                    sampAddChatMessage('playing unpaused...', -1)
                    t = os.clock()
                    time = ps * 1000
                    break
                end
            end
        end
    end
end

-- Функция для отображения окна статистики
function imgui.OnDrawFrame()
    if myImgui.windows.status.main.v then
        imgui.ShowCursor = false
        local posX, posY = get_window_position(200, 100)
        imgui.SetNextWindowPos(imgui.ImVec2(posX, posY), imgui.Cond.Appearing, imgui.ImVec2(0.0, 0.0))
        imgui.SetNextWindowSize(imgui.ImVec2(200, 100), imgui.Cond.Appearing)
        imgui.Begin("Stats", myImgui.windows.status.main, imgui.WindowFlags.NoResize + imgui.WindowFlags.NoTitleBar)
        imgui.Text("Stats:\n> time: " .. get_time(os.clock(), stats.time_full) .. "\n> earned: " .. stats.salary .. "$\n> rounds: " .. stats.route_count)
        imgui.End()
    end
end

-- Функция для кастомизации окна статистики
function apply_custom_style()
    imgui.SwitchContext()
    imgui.GetIO().MouseDoubleClickTime = 0.1
    local style = imgui.GetStyle()
    local colors = style.Colors
    local clr = imgui.Col
    local ImVec4 = imgui.ImVec4

    style.WindowRounding = 5.0
    style.WindowTitleAlign = imgui.ImVec2(0.5, 0.84)
    style.FrameRounding = 2.5
    style.ItemSpacing = imgui.ImVec2(5.0, 4.0)
    style.ScrollbarSize = 20
    style.ScrollbarRounding = 5.0
    style.GrabMinSize = 10.0
    style.GrabRounding = 5.0
    colors[clr.WindowBg] = imgui.ImVec4(0.02, 0.02, 0.02, 0.9)
    colors[clr.ComboBg] = ImVec4(0.15, 0.15, 0.15, 1.00)
    colors[clr.Border] = ImVec4(0.125, 0.125, 0.125, 0.50)
    colors[clr.FrameBg] = ImVec4(0.11, 0.11, 0.11, 1.00)
    colors[clr.TitleBg] = ImVec4(0.10, 0.10, 0.10, 1.00)
    colors[clr.TitleBgActive] = ImVec4(0.10, 0.10, 0.10, 1.00)
    colors[clr.TitleBgCollapsed] = ImVec4(0.10, 0.10, 0.10, 0.50)
    colors[clr.Button] = ImVec4(0.15, 0.15, 0.15, 1.00)
    colors[clr.ButtonHovered] = ImVec4(0.175, 0.175, 0.175, 1.00)
    colors[clr.ButtonActive] = ImVec4(0.2, 0.2, 0.2, 1.00)
    colors[clr.Header] = ImVec4(0.125, 0.125, 0.125, 1.00)
    colors[clr.HeaderHovered] = ImVec4(0.15, 0.15, 0.15, 1.00)
    colors[clr.HeaderActive] = ImVec4(0.175, 0.175, 0.175, 1.00)
    colors[clr.ScrollbarBg] = ImVec4(0.1, 0.1, 0.1, 1.0)
    colors[clr.ScrollbarGrab] = ImVec4(0.15, 0.15, 0.15, 1.00)
    colors[clr.CheckMark] = ImVec4(0.75, 0.75, 0.75, 1.00)
    colors[clr.SliderGrab] = ImVec4(0.15, 0.15, 0.15, 0.50)
    colors[clr.ScrollbarGrabHovered] = ImVec4(0.175, 0.175, 0.175, 1.00)
    colors[clr.ScrollbarGrabActive] = ImVec4(0.20, 0.20, 0.20, 1.00)
    colors[clr.TextSelectedBg] = ImVec4(0.125, 0.125, 0.125, 1.00)
    colors[clr.ResizeGrip] = ImVec4(0.125, 0.125, 0.125, 0.70)
    colors[clr.ResizeGripHovered] = ImVec4(0.15, 0.15, 0.15, 1.00)
    colors[clr.ResizeGripActive] = ImVec4(0.175, 0.175, 0.175, 1.00)
    colors[clr.CloseButton] = ImVec4(0.15, 0.15, 0.15, 1.00)
    colors[clr.CloseButtonHovered] = ImVec4(0.175, 0.175, 0.175, 1.00)
    colors[clr.CloseButtonActive] = ImVec4(0.20, 0.20, 0.20, 1.00)
end

-- Получить позицию окна
function get_window_position(sizeX, sizeY)
    local resX, resY = getScreenResolution()
    local posX = resX - sizeX - 20
    local posY = resY - sizeY - 20
    return posX, posY
end

-- Функция для получения времени работы
function get_time(time_a, time_b)
    local uptime = time_a - time_b
    local gonetime = {math.floor(uptime / 3600), math.floor(uptime / 60) % 60, math.floor(uptime % 60)}
    for i = 1, 3 do
        if gonetime[i] < 10 then
            gonetime[i] = "0" .. gonetime[i]
        end
    end
    return gonetime[1] .. ":" .. gonetime[2] .. ":" .. gonetime[3]
end

-- Мониторинг сообщений
function samp_ev.onServerMessage(color, text)
    if not adm_check and run then
        if text:find("говорит:") or text:find("Администратор") then
            print("{D10f0f}WARNING: {FFFFFF}" .. text)
            if text:find(" tut") or text:find(" тут") or text:find(" здесь") or text:find(" monitora") or text:find(" монитора") then
                local wrds = {" tut?", " тут?", " здесь?", " monitora?", " монитора?"}
                local index
                for i = 1, #wrds do
                    if text:find(wrds[i]) then
                        index = i
                        break
                    end
                end
                sampAddChatMessage("Text detected (" .. wrds[index] .. "): " .. text, 0xFFd10f0f)
                adm_check = true
            end
        end
        if text:find("Вы не переоделись в рабочую одежду") then
            print("Error: not dressed up.")
            adm_check = true
        end
        if text:find("Зачислено на банковский счёт") then
            stats.salary = stats.salary + text:match("(%d+)")
            stats.route_count = stats.route_count + 1
        end
        if text:find("У вас осталось 30 секунд") then
            sampAddChatMessage("finish_route() by attention.", -1)
            lua_thread.create(finish_route)
        end
    end
end

-- Мониторинг диалоговых окон
function samp_ev.onShowDialog(dialogId, style, title, button1, button2, text)
    dialogw.dw_dialogId = dialogId
    dialogw.dw_style = style
    dialogw.dw_title = title
    dialogw.dw_button1 = button1
    dialogw.dw_button2 = button2
    dialogw.dw_text = text
end

-- Функция для очистки данных диалогового окна
function clear_dw_data()
    dialogw.dw_dialogId = nil
    dialogw.dw_style = nil
    dialogw.dw_title = nil
    dialogw.dw_button1 = nil
    dialogw.dw_button2 = nil
end

-- Поток для показа статистики
function thread_checks()
    while true do
        wait(0)
        if run then
            imgui.Process = myImgui.windows.status.main.v
            if isKeyJustPressed(103) then
                myImgui.windows.status.main.v = not myImgui.windows.status.main.v
            end
        else
            break
        end
    end
end
 

wojciech?

Известный
Проверенный
392
289
Мой вопрос: работает ли данный метод на любом устройстве? Ведь, очевидно, пинг, фпс и т.п. у каждого будет различаться.
В теории, для пк клиента проблем быть не должно

неверные подходы или просто не совсем рациональные решения в скрипте
press_gas() и press_brake(braking) сомнительные функции, лучше использовать https://wiki.blast.hk/moonloader/lua/setGameKeyState

Вместо press_button лучше использовать setGameKeyState и эмулировать нажатие именно игровых клавиш с принудительной отправкой синхронизации с помощью sampForceOnfootSync() или sampForceVehicleSync(int id). Тогда будут не нужны задержки и кнопки будут гарантировано отправлены серверу.

Вместо фиксированных координат в main_coords и brake_coords, лучше находить игровой маркер с помощью https://www.blast.hk/threads/13380/post-119165 и в зависимости от дистанции и скорости до следующего ускоряться или начинать тормозить. Не нужно в ручную будет вписывать всё это, да и при изменениях со стороны сервера (или при переработке для бота электропоезда) меньше телодвижений потребуется для исправления.

Самого бота лучше запускать в потоке https://wiki.blast.hk/moonloader/lua/lua_thread/create и не занимать основной цикл. Тогда для принудительной остановки скрипта пользователем или при действиях администраторов нужно будет только завершить поток с помощью метода terminate() из любого места.