SA:MP Lua Arizona [AutoRespawn] Авто релог для притона

Qubea

Новичок
Автор темы
1
0
Версия SA-MP
  1. 0.3.7-R3
AutoRespawn

Автоматический перезаход для АФК фармил

Версия: 1 | Автор: Qabeee​

Описание
Небольшой скрипт для перезахода после того как вас нокнули на притоне / разорвало соединение

После получения сообщения от сервера о завершении лечения скрипт запускает обратный отсчет в 10 секунд, выполняет /rec, выбирает заранее указанную точку спавна, а после появления персонажа открывает инвентарь и вызывает охранника.

Всё работает автоматически, без необходимости каждый раз выполнять действия вручную.

Возможности
  • Автоматический перезаход после завершения лечения.
  • Выбор нужной точки спавна
  • Автоматический спавн охранника после появления персонажа
  • Ручной перезаход с помощью команды /arec
  • Отображение текущего состояния скрипта
  • Настраиваемая горячая клавиша открытия меню.
Активация и настройка

1. Открыть меню /aurec или F12.
2. Выбрать необходимую точку спавна.
3. Нажать «Включить авто релог».

Готово. Теперь в случаи, если вас убьют после окончания лечения вы перезайдете на притон

Примечание: F12 - стандартная клавиша открытия меню. При желании её можно изменить непосредственно в интерфейсе.

Команды

/aurec — открыть/закрыть меню.
/arec — выполнить ручной перезаход.

Зависимости
Установка

1. Установить необходимые библиотеки.
2. Переместить файл AutoRespawn.lua в папку moonloader.
3. Запустить игру и открыть меню командой /aurec.

Настройки сохраняются автоматически.

Скриншот интерфейса

Без имени.png

Открытый код: скрипт распространяется в формате .lua без обфускации. Любой желающий может ознакомиться с исходником и при необходимости изменить его под себя.

Код:
script_name('AutoRespawn')
script_version('1')
script_author('Qabeee')

local acef = require('arizona-events')
local sampev = require('lib.samp.events')
local imgui = require('mimgui')
local inicfg = require('inicfg')
local encoding = require('encoding')
local ffi = require('ffi')

encoding.default = 'UTF-8'
local cyr = encoding.CP1251

local defaults = {
    main = {
        enabled = false,
        selected = '',
        pending = 0,
        cycle_selected = '',
        hotkey = 123
    },
    points = {count = 0}
}

local cfg = inicfg.load(defaults, 'Q123')
cfg.main = cfg.main or {}
cfg.points = cfg.points or {}

local function diagnostic(s)
    print('[AutoRespawn] ' .. tostring(s))
end

local function errorMessage(s)
    diagnostic(s)
    if isSampAvailable() then
        sampAddChatMessage(cyr('[AutoRespawn] ' .. s), 0xFF6666)
    end
end

local function save()
    local ok, result = pcall(inicfg.save, cfg, 'Q123')
    if not ok or result == false then
        diagnostic('Settings save error: ' .. tostring(result))
        return false
    end
    return true
end

local settings = {
    enabled = cfg.main.enabled == true
        or tostring(cfg.main.enabled) == 'true',
    selected = tostring(cfg.main.selected or ''),
    hotkey = tonumber(cfg.main.hotkey) or 123,
    points = {}
}

local count = tonumber(cfg.points.count) or 0

for i = 1, math.min(count, 100) do
    local name = cfg.points['p' .. i]
    if type(name) == 'string' and name ~= '' then
        settings.points[#settings.points + 1] = name
    end
end

local function saveSettings()
    cfg.main.enabled = settings.enabled
    cfg.main.selected = settings.selected
    cfg.main.hotkey = settings.hotkey
    cfg.points = {count = #settings.points}

    for i, name in ipairs(settings.points) do
        cfg.points['p' .. i] = name
    end

    return save()
end

local oldPending = tonumber(cfg.main.pending) or 0

local pending = oldPending > 0
    and oldPending <= os.time() + 5
    and os.time() - oldPending <= 180

local restored = pending
local cycleSelected = ''

if pending then
    cycleSelected = tostring(cfg.main.cycle_selected or '')
    if cycleSelected == '' then
        cycleSelected = settings.selected
    end
else
    cfg.main.pending = 0
    cfg.main.cycle_selected = ''
    save()
end

local window = imgui.new.bool(false)
local search = imgui.new.char[128]()
local uiError = ''
local state = 'idle'
local enteredAt = 0
local actionAt = 0
local deadlineAt = 0
local captureKey = false
local captureArmed = false
local previousHotkeyDown = false
local keyCodes = {}
local keyNames = {}
for n = 65, 90 do keyCodes[#keyCodes + 1] = n; keyNames[n] = string.char(n) end
for n = 48, 57 do keyCodes[#keyCodes + 1] = n; keyNames[n] = string.char(n) end
for n = 112, 123 do keyCodes[#keyCodes + 1] = n; keyNames[n] = 'F' .. (n - 111) end
for n = 96, 105 do keyCodes[#keyCodes + 1] = n; keyNames[n] = 'NUM ' .. (n - 96) end
keyCodes[#keyCodes + 1] = 32; keyNames[32] = 'SPACE'
if not keyNames[settings.hotkey] then settings.hotkey = 123 end
local function anyBindKeyDown()
    for _, vk in ipairs(keyCodes) do
        if isKeyDown(vk) then return true end
    end
    return false
end
local function statusText()
    if state == 'hospital_wait' then return 'Лечение завершено' end
    if state == 'wait_reconnect' then return 'Ожидание появления' end
    if state == 'settle' then return 'Подготовка инвентаря' end
    if state == 'inventory_request' or state == 'inventory_wait' then return 'Открытие инвентаря' end
    if state == 'security_tab' or state == 'security_wait' or state == 'select_guard'
        or state == 'select_wait' or state == 'menu_wait' or state == 'choose_menu'
        or state == 'confirm_wait' or state == 'confirm_guard' then return 'Вызов охранника' end
    if state == 'close_inventory' or state == 'close_inventory_wait' or state == 'finished' then
        return 'Завершение цикла'
    end
    if settings.enabled then return 'Ожидание завершения лечения' end
    return 'Авто релог отключен'
end

local spawnAt
local syncCount = 0
local firstSyncAt
local lastSyncAt

local inventoryCount = 0
local inventoryOpened = false
local guardMenuAction
local guardMenuIndex

local hospitalAt
local hospitalCount = 0
local lastTreatment = -30000

local spawnSelectedThisLogin = false
local lastSpawnSignature
local font

local function go(nextState, delay, timeout)
    state = nextState
    enteredAt = os.time()
    actionAt = enteredAt + (delay or 0)
    deadlineAt = timeout and enteredAt + timeout or 0
    diagnostic('STATE ' .. nextState)
end

local function resetConnectionSignals()
    spawnAt = nil
    syncCount = 0
    firstSyncAt = nil
    lastSyncAt = nil
end

local function halt(reason)
    pending = false
    cycleSelected = ''

    cfg.main.pending = 0
    cfg.main.cycle_selected = ''
    save()

    inventoryCount = 0
    inventoryOpened = false
    guardMenuAction = nil
    guardMenuIndex = nil

    go('idle')
    diagnostic(reason)
end

local function sendCEF(payload)
    local ok, err = pcall(
        acef.send,
        'onArizonaSend',
        {text = payload, server_id = 0}
    )

    if not ok then
        errorMessage('Ошибка CEF: ' .. tostring(err))
        halt('CEF request failed')
    end

    return ok
end

local function issueRec()
    if pending or state ~= 'idle' then
        diagnostic('Cycle already running: ' .. state)
        return
    end

    local selected = settings.enabled and settings.selected or ''

    cfg.main.pending = os.time()
    cfg.main.cycle_selected = selected

    if not save() then
        errorMessage('Ошибка сохранения состояния.')
        cfg.main.pending = 0
        cfg.main.cycle_selected = ''
        return
    end

    pending = true
    cycleSelected = selected

    inventoryCount = 0
    inventoryOpened = false
    guardMenuAction = nil
    guardMenuIndex = nil

    spawnSelectedThisLogin = false
    lastSpawnSignature = nil

    resetConnectionSignals()
    go('wait_reconnect', 0, 45)

    diagnostic('Executing /rec')

    local ok, err = pcall(sampProcessChatInput, '/rec')

    if not ok then
        errorMessage('Ошибка /rec: ' .. tostring(err))
        halt('Reconnect failed')
    end
end

local function inventory()
    if not pending or state ~= 'inventory_request' then return end

    inventoryCount = inventoryCount + 1

    if inventoryCount > 1 then
        errorMessage('Инвентарь не открылся.')
        halt('Inventory attempt limit')
        return
    end

    go('inventory_wait', 0, 12)

    local ok, err = pcall(sampSendChat, '/invent')

    if not ok then
        errorMessage('Ошибка /invent: ' .. tostring(err))
        halt('Inventory failed')
    end
end

local function sendSpawn(id)
    local s = 'authSpawn|' .. tostring(id)
    local bs = raknetNewBitStream()

    raknetBitStreamWriteInt8(bs, 220)
    raknetBitStreamWriteInt8(bs, 18)
    raknetBitStreamWriteInt32(bs, #s)
    raknetBitStreamWriteString(bs, s)
    raknetBitStreamWriteInt32(bs, 0)

    raknetSendBitStreamEx(bs, 1, 7, 1)
    raknetDeleteBitStream(bs)
end

local function activeSpawnName()
    if pending then return cycleSelected end
    if settings.enabled then return settings.selected end
    return ''
end

local function processSpawnPoints(points)
    if type(points) ~= 'table' then return false end

    local names = {}
    local selectedPoint
    local wanted = activeSpawnName()

    for _, p in ipairs(points) do
        if type(p) == 'table' and type(p.spawn) == 'string' then
            local name = cyr:decode(p.spawn)
            names[#names + 1] = name

            if wanted ~= '' and name == wanted and p.id ~= nil then
                selectedPoint = p
            end
        end
    end

    if #names == 0 then
        diagnostic('No spawn points received')
        return false
    end

    settings.points = names

    if not saveSettings() then
        diagnostic('Spawn list saving failed')
    end

    diagnostic('Spawn points: ' .. #names)

    if wanted == '' or spawnSelectedThisLogin then
        return true
    end

    if not selectedPoint then
        diagnostic('Selected spawn unavailable: ' .. wanted)
        return true
    end

    local ok, err = pcall(sendSpawn, selectedPoint.id)

    if ok then
        spawnSelectedThisLogin = true
        diagnostic('Selected spawn: ' .. wanted)
    else
        diagnostic('Spawn selection error: ' .. tostring(err))
    end

    return true
end

local function bitStreamStructure(bs)
    local text = ''

    for i = 1, raknetBitStreamGetNumberOfBytesUsed(bs) do
        local byte = raknetBitStreamReadInt8(bs)

        if byte >= 32 and byte <= 255 and byte ~= 37 then
            text = text .. string.char(byte)
        end
    end

    raknetBitStreamResetReadPointer(bs)
    return text
end

local function handleSpawnPacket(id, bs)
    if id ~= 239 then return end

    local ok, err = pcall(function()
        local text = bitStreamStructure(bs)

        if not text:find(
            'event.auth.initializeSpawnPoints', 1, true
        ) then
            return
        end

        local event, data = text:match(
            "window%.executeEvent%('([%w%.]+)',%s*'(.+)'%)"
        )

        if event ~= 'event.auth.initializeSpawnPoints' then
            diagnostic('Spawn event mismatch')
            return
        end

        if data == lastSpawnSignature then return end

        local decodedOk, decoded = pcall(decodeJson, data)

        if not decodedOk or type(decoded) ~= 'table' then
            diagnostic('Spawn JSON decoding failed')
            return
        end

        if processSpawnPoints(decoded[1]) then
            lastSpawnSignature = data
        end
    end)

    if not ok then
        diagnostic('Spawn packet error: ' .. tostring(err))
    end
end

addEventHandler('onReceivePacket', handleSpawnPacket)

local function dialogTextUtf8(value)
    if type(value) ~= 'string' then return '' end

    local ok, result = pcall(function()
        return cyr:decode(value)
    end)

    if ok and type(result) == 'string' then return result end
    return value
end

local function recognizeGuardMenu(body)
    local plain = dialogTextUtf8(body)
        :gsub('{%x%x%x%x%x%x}', '')

    local index = 0
    local spawnIndex
    local activeGuard = false

    for line in (plain .. '\n'):gmatch('(.-)\n') do
        line = line:gsub('\r', '')

        if line:find('Заспавнить рядом с собой', 1, true) then
            spawnIndex = index
        end

        if line:find('Спрятать', 1, true)
            or line:find('Отозвать', 1, true)
            or line:find('Убрать охранника', 1, true)
        then
            activeGuard = true
        end

        index = index + 1
    end

    if activeGuard then return 'already_spawned' end
    if spawnIndex ~= nil then return 'spawn', spawnIndex end
    return 'unknown'
end

function sampev.onShowDialog(id, style, title, b1, b2, body)
    local titleText = dialogTextUtf8(title)

    if titleText:find('Выбор места спавна', 1, true) then
        local parsed = {}
        local plain = dialogTextUtf8(body)

        for line in plain:gmatch('[^\r\n]+') do
            local name = line:match('%[%d+%] %{ffffff%}%s*(.+)')

            if name then
                if name:find('Сохраненная точка', 1, true) then
                    name = 'Сохраненная точка'
                end
                parsed[#parsed + 1] = name
            else
                parsed[#parsed + 1] = ''
            end
        end

        if #parsed > 0 then
            settings.points = parsed
            saveSettings()
        end

        local wanted = activeSpawnName()

        if wanted ~= '' and not spawnSelectedThisLogin then
            for index, name in ipairs(parsed) do
                if name == wanted then
                    local ok, err = pcall(
                        sampSendDialogResponse,
                        id, 1, index - 1, ''
                    )

                    if ok then
                        spawnSelectedThisLogin = true
                        diagnostic('Dialog spawn selected: ' .. wanted)
                        return false
                    end

                    diagnostic('Dialog response error: ' .. tostring(err))
                    break
                end
            end
        end
    end

    if not pending then return end

    if state == 'menu_wait' and id == 25749 then
        guardMenuAction, guardMenuIndex = recognizeGuardMenu(body)

        diagnostic(
            'Guard: ' .. guardMenuAction
            .. ' index=' .. tostring(guardMenuIndex)
        )

        go('choose_menu', 1, 8)

    elseif state == 'confirm_wait' and id == 0 then
        go('confirm_guard', 1, 8)
    end
end

local treatmentPhrases = {
    'Вы закончили свое лечение',
    'Вы закончили своё лечение',
    'Вы закончили лечение'
}

function sampev.onServerMessage(color, message)
    if type(message) ~= 'string' then return end

    local s = dialogTextUtf8(
        message:gsub('{%x%x%x%x%x%x}', '')
    )

    if pending and state == 'inventory_wait' then
        if s:find(
            'Нельзя так быстро открывать инвентарь', 1, true
        ) then
            go('inventory_rejected', 0, 0)
            diagnostic('Inventory cooldown received')
            return
        end
    end

    if not settings.enabled or pending or state ~= 'idle' then
        return
    end

    if settings.selected == '' then return end

    local t = getGameTimer()
    if t - lastTreatment < 20000 then return end

    for _, phrase in ipairs(treatmentPhrases) do
        if s:find(phrase, 1, true) then
            lastTreatment = t
            hospitalAt = os.time() + 10
            hospitalCount = 10
            go('hospital_wait')
            diagnostic('Treatment finished; countdown started')
            return
        end
    end
end

function sampev.onPlayerSpawn()
    if pending and state == 'wait_reconnect' then
        spawnAt = os.time()
        diagnostic('OBSERVED onPlayerSpawn')
    end
end

function sampev.onSendPlayerSync(data)
    if not pending or state ~= 'wait_reconnect' then return end

    local now = os.time()

    if lastSyncAt ~= now then
        syncCount = syncCount + 1
        if not firstSyncAt then firstSyncAt = now end
        lastSyncAt = now
    end
end

function sampev.onInitGame()
    spawnSelectedThisLogin = false
    lastSpawnSignature = nil
    resetConnectionSignals()
    diagnostic('OBSERVED onInitGame')
end

function acef.onArizonaDisplay(packet)
    if not pending or type(packet) ~= 'table' then return end

    local s = type(packet.text) == 'string'
        and packet.text
        or (
            type(packet[1]) == 'table'
            and packet[1].text
            or nil
        )

    if type(s) ~= 'string' then return end

    local visible = s:find(
        'event.inventory.setPlayerInventoryVisible', 1, true
    ) and s:find('true', 1, true)

    if visible and (
        state == 'wait_reconnect'
        or state == 'settle'
        or state == 'inventory_request'
        or state == 'inventory_wait'
        or state == 'inventory_rejected'
    ) then
        inventoryOpened = true
        go('security_tab', 1, 10)
        diagnostic('ACK inventory visible')

    elseif inventoryOpened and (
        (
            s:find('event.inventory.setPlayerInventoryVisible', 1, true)
            and s:find('false', 1, true)
        )
        or (
            s:find('event.setActiveView', 1, true)
            and s:find('null', 1, true)
        )
    ) then
        inventoryOpened = false
        diagnostic('ACK inventory closed')

    elseif state == 'security_wait'
        and s:find('event.inventory.updateCharacterTab', 1, true)
        and s:find('security', 1, true)
    then
        go('select_guard', 1, 10)
    end
end

local function disableHospital()
    local previous = settings.enabled
    settings.enabled = false

    if not saveSettings() then
        settings.enabled = previous
        uiError = 'Ошибка сохранения настроек.'
        errorMessage(uiError)
        return
    end

    if state == 'hospital_wait' then
        hospitalAt = nil
        hospitalCount = 0
        go('idle')
    end

    uiError = ''
    diagnostic('Automation disabled')
end

local function enableHospital()
    if settings.selected == '' then
        uiError = 'Ошибка! Не выбрано место спавна.'
        errorMessage(uiError)
        return
    end

    local found = false

    for _, name in ipairs(settings.points) do
        if name == settings.selected then
            found = true
            break
        end
    end

    if not found then
        uiError = 'Ошибка! Выбранная точка отсутствует в списке.'
        errorMessage(uiError)
        return
    end

    settings.enabled = true

    if not saveSettings() then
        settings.enabled = false
        uiError = 'Ошибка сохранения настроек.'
        errorMessage(uiError)
        return
    end

    uiError = ''
    diagnostic('Automation enabled: ' .. settings.selected)
end

local function searchKey(value)
    local ok, cp = pcall(function()
        return cyr:encode(tostring(value or ''))
    end)

    if not ok or type(cp) ~= 'string' then
        return tostring(value or ''):lower()
    end

    local result = {}

    for i = 1, #cp do
        local b = cp:byte(i)

        if (b >= 65 and b <= 90)
            or (b >= 192 and b <= 223)
        then
            b = b + 32
        elseif b == 168 then
            b = 184
        end

        result[i] = string.char(b)
    end

    return table.concat(result)
end

local function matchesSearch(name, query)
    if query == '' then return true end
    return searchKey(name):find(searchKey(query), 1, true) ~= nil
end

local function cleanCheck(cx, cy)
    local draw = imgui.GetWindowDrawList()
    local color = imgui.GetColorU32(imgui.Col.Text)
    draw:AddLine(imgui.ImVec2(cx - 7, cy), imgui.ImVec2(cx - 2, cy + 5), color, 2.5)
    draw:AddLine(imgui.ImVec2(cx - 2, cy + 5), imgui.ImVec2(cx + 8, cy - 6), color, 2.5)
end

imgui.OnInitialize(function()
    imgui.SwitchContext()
    local st = imgui.GetStyle()
    st.WindowPadding = imgui.ImVec2(18, 12)
    st.FramePadding = imgui.ImVec2(9, 7)
    st.ItemSpacing = imgui.ImVec2(7, 7)
    st.ItemInnerSpacing = imgui.ImVec2(6, 5)
    st.WindowTitleAlign = imgui.ImVec2(.5, .5)
    st.WindowRounding = 7
    st.ChildRounding = 5
    st.FrameRounding = 5
    st.ScrollbarSize = 9
    st.WindowBorderSize = 1
    st.ChildBorderSize = 0
    st.Colors[imgui.Col.WindowBg] = imgui.ImVec4(.055, .055, .068, 1)
    st.Colors[imgui.Col.ChildBg] = imgui.ImVec4(.065, .065, .079, 1)
    st.Colors[imgui.Col.TitleBg] = imgui.ImVec4(.08, .08, .095, 1)
    st.Colors[imgui.Col.TitleBgActive] = imgui.ImVec4(.08, .08, .095, 1)
    st.Colors[imgui.Col.FrameBg] = imgui.ImVec4(.11, .11, .13, 1)
    st.Colors[imgui.Col.Button] = imgui.ImVec4(.39, .23, .8, 1)
    st.Colors[imgui.Col.ButtonHovered] = imgui.ImVec4(.46, .29, .9, 1)
    st.Colors[imgui.Col.Header] = imgui.ImVec4(0, 0, 0, 0)
    st.Colors[imgui.Col.HeaderHovered] = imgui.ImVec4(.14, .14, .17, 1)
    st.Colors[imgui.Col.HeaderActive] = imgui.ImVec4(.14, .14, .17, 1)
    st.Colors[imgui.Col.Border] = imgui.ImVec4(.22, .22, .26, 1)
    imgui.GetIO().IniFilename = nil
end)

imgui.OnFrame(function()
    return window[0]
end, function()
    local sw, sh = getScreenResolution()
    imgui.SetNextWindowPos(imgui.ImVec2(sw / 2, sh / 2), imgui.Cond.FirstUseEver, imgui.ImVec2(.5, .5))
    imgui.SetNextWindowSize(imgui.ImVec2(830, 525), imgui.Cond.Always)
    local flags = imgui.WindowFlags.NoResize + imgui.WindowFlags.NoCollapse
        + imgui.WindowFlags.NoScrollbar + imgui.WindowFlags.NoScrollWithMouse
    imgui.Begin('AutoRespawn | v1', window, flags)

    local allW = imgui.GetContentRegionAvail().x
    local allH = imgui.GetContentRegionAvail().y
    local gap = 16
    local leftW = math.floor((allW - gap) * .49)
    local rightW = allW - gap - leftW
    local panelH = allH - 43

    imgui.BeginChild('##left_panel', imgui.ImVec2(leftW, panelH), false,
        imgui.WindowFlags.NoScrollbar)

    local previousPadding = imgui.GetStyle().WindowPadding
    imgui.GetStyle().WindowPadding = imgui.ImVec2(12, 10)
    imgui.BeginChild('##selected_block', imgui.ImVec2(0, 88), true,
        imgui.WindowFlags.NoScrollbar)
    imgui.GetStyle().WindowPadding = previousPadding
    imgui.SetCursorPosX(12)
    imgui.TextDisabled('Выбранная точка спавна:')
    imgui.Spacing()
    imgui.SetCursorPosX(12)
    if settings.selected == '' then
        imgui.TextDisabled('Не выбрано')
    else
        imgui.PushTextWrapPos(imgui.GetCursorPosX() + leftW - 62)
        imgui.Text(settings.selected)
        imgui.PopTextWrapPos()
        local a, b = imgui.GetItemRectMin(), imgui.GetItemRectMax()
        cleanCheck(b.x + 16, (a.y + b.y) / 2)
    end
    imgui.EndChild()

    imgui.Spacing()
    local previousPadding = imgui.GetStyle().WindowPadding
    imgui.GetStyle().WindowPadding = imgui.ImVec2(12, 8)
    imgui.BeginChild('##help_area', imgui.ImVec2(0, 46), false,
        imgui.WindowFlags.NoScrollbar)
    imgui.GetStyle().WindowPadding = previousPadding
    imgui.SetCursorPosX(12)
    imgui.Text('Как работает скрипт')
    imgui.SameLine(nil, 8)
    imgui.PushStyleColor(imgui.Col.Button, imgui.ImVec4(.25, .19, .38, 1))
    imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(.38, .27, .61, 1))
    imgui.SmallButton('?##help')
    imgui.PopStyleColor(2)
    if imgui.IsItemHovered() then
        imgui.BeginTooltip()
        imgui.PushTextWrapPos(imgui.GetCursorPosX() + 350)
        imgui.Text('Как работает скрипт')
        imgui.Separator()
        imgui.Text('После появления в больнице персонаж лечится. Для этого необходим аксессуар с регенерацией HP.')
        imgui.Spacing()
        imgui.Text('Когда сервер отправит в чат сообщение «Вы закончили свое лечение», начнётся отсчёт 10 секунд.')
        imgui.Spacing()
        imgui.Text('Затем выполняется /rec. После появления персонажа скрипт открывает инвентарь и вызывает охранника. Можно продолжать стоять АФК.')
        imgui.Spacing()
        imgui.Separator()
        imgui.Text('ВАЖНО: выберите нужную точку спавна и включите авто релог.')
        imgui.PopTextWrapPos()
        imgui.EndTooltip()
    end
    imgui.EndChild()

    imgui.SetCursorPosY(201)
    imgui.SetCursorPosX(12)
    imgui.TextDisabled('Состояние скрипта')
    imgui.SetCursorPosX(12)
    imgui.Text(statusText())
    imgui.Spacing()
    imgui.SetCursorPosX(12)
    imgui.TextDisabled('Открытие меню: /aurec или')
    imgui.SameLine(nil, 6)
    if imgui.SmallButton((captureKey and 'Нажмите клавишу...' or keyNames[settings.hotkey]) .. '##bind') then
        captureKey = true
        captureArmed = false
        previousHotkeyDown = true
    end
    if captureKey then
        imgui.SetCursorPosX(12)
        imgui.TextDisabled('ESC — отмена; только латиница, цифры и F-клавиши')
    end
    imgui.Spacing()

    local controlsH = settings.enabled and 70 or 46
    if uiError ~= '' then controlsH = controlsH + 38 end
    local targetY = panelH - controlsH - 2
    imgui.SetCursorPosY(math.max(imgui.GetCursorPosY() + 4, targetY))
    local previousPadding = imgui.GetStyle().WindowPadding
    imgui.GetStyle().WindowPadding = imgui.ImVec2(12, 8)
    imgui.BeginChild('##control_block', imgui.ImVec2(0, controlsH), false,
        imgui.WindowFlags.NoScrollbar)
    imgui.GetStyle().WindowPadding = previousPadding
    if uiError ~= '' then
        imgui.PushTextWrapPos(0)
        imgui.TextColored(imgui.ImVec4(1, .4, .4, 1), uiError)
        imgui.PopTextWrapPos()
    end
    if settings.enabled then
        imgui.PushStyleColor(imgui.Col.Button, imgui.ImVec4(.23, .23, .26, 1))
        imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(.30, .30, .34, 1))
        if imgui.Button('Отключить авто релог', imgui.ImVec2(-1, 38)) then
            disableHospital()
        end
        imgui.PopStyleColor(2)
        local p = imgui.GetCursorScreenPos()
        cleanCheck(p.x + 8, p.y + 8)
        imgui.SetCursorPosX(imgui.GetCursorPosX() + 23)
        imgui.Text('Авто релог активен')
    else
        if imgui.Button('Включить авто релог', imgui.ImVec2(-1, 38)) then
            enableHospital()
        end
    end
    imgui.EndChild()
    imgui.EndChild()

    imgui.SameLine(nil, gap)
    local previousPadding = imgui.GetStyle().WindowPadding
    imgui.GetStyle().WindowPadding = imgui.ImVec2(12, 10)
    imgui.BeginChild('##right_panel', imgui.ImVec2(rightW, panelH), true,
        imgui.WindowFlags.NoScrollbar)
    imgui.GetStyle().WindowPadding = previousPadding
    imgui.Text('Доступные точки:  ' .. tostring(#settings.points))
    imgui.Separator()
    imgui.Spacing()
    imgui.SetNextItemWidth(-1)
    if imgui.InputTextWithHint then
        imgui.InputTextWithHint('##spawn_search', 'Поиск', search, 128)
    else
        imgui.InputText('##spawn_search', search, 128)
        if ffi.string(search) == '' and not imgui.IsItemActive() then
            local p = imgui.GetItemRectMin()
            imgui.GetWindowDrawList():AddText(imgui.ImVec2(p.x + 9, p.y + 7),
                imgui.GetColorU32(imgui.Col.TextDisabled), 'Поиск')
        end
    end
    imgui.Spacing()
    local query = ffi.string(search)
    local matches = 0
    imgui.BeginChild('##results', imgui.ImVec2(0, imgui.GetContentRegionAvail().y - 3), false)
    for index, name in ipairs(settings.points) do
        if type(name) == 'string' and name ~= '' and matchesSearch(name, query) then
            matches = matches + 1
            if imgui.Selectable('##point_' .. index, false, 0,
                imgui.ImVec2(0, 32)) then
                local old = settings.selected
                settings.selected = name
                uiError = ''
                if not saveSettings() then
                    settings.selected = old
                    uiError = 'Ошибка сохранения настроек.'
                end
            end

            local a = imgui.GetItemRectMin()
            local b = imgui.GetItemRectMax()
            local draw = imgui.GetWindowDrawList()
            local midY = (a.y + b.y) * 0.5
            local accent = imgui.GetColorU32(imgui.Col.Button)
            local textColor = imgui.GetColorU32(imgui.Col.Text)
            local textH = imgui.CalcTextSize(name).y

            draw:AddRectFilled(
                imgui.ImVec2(a.x + 8, midY - 7),
                imgui.ImVec2(a.x + 11, midY + 7),
                accent, 1.5
            )
            draw:AddText(
                imgui.ImVec2(a.x + 23, midY - textH * 0.5),
                textColor, name
            )
            if settings.selected == name then
                cleanCheck(b.x - 17, midY)
            end
        end
    end
    if matches == 0 then imgui.TextDisabled('Ничего не найдено') end
    imgui.EndChild()
    imgui.EndChild()

    imgui.Spacing()
    imgui.Separator()
    imgui.BeginChild('##footer', imgui.ImVec2(0, 24), false,
        imgui.WindowFlags.NoScrollbar)
    local credit = 'by Qabeee'
    local tw = imgui.CalcTextSize(credit).x
    imgui.SetCursorPosX((imgui.GetWindowWidth() - tw) / 2)
    imgui.TextDisabled(credit)
    imgui.EndChild()
    imgui.End()
end)
function main()
    while not isSampAvailable() do wait(100) end

    sampRegisterChatCommand('arec', issueRec)

    sampRegisterChatCommand('aurec', function()
        window[0] = not window[0]
        uiError = ''
    end)

    font = renderCreateFont('Arial', 13, 5)

    if restored then
        resetConnectionSignals()
        go('wait_reconnect', 0, 45)
        diagnostic('Restored cycle; waiting for spawn confirmation')
    end

    diagnostic(
        'Version 1 loaded | points='
        .. tostring(#settings.points)
        .. ' | enabled=' .. tostring(settings.enabled)
    )

    while true do
        wait(0)
        local now = os.time()

        if captureKey then
            if not captureArmed then
                if not anyBindKeyDown() and not isKeyDown(27) then captureArmed = true end
            elseif isKeyDown(27) then
                captureKey = false
            else
                for _, vk in ipairs(keyCodes) do
                    if isKeyDown(vk) then
                        local old = settings.hotkey
                        settings.hotkey = vk
                        if not saveSettings() then settings.hotkey = old end
                        previousHotkeyDown = true
                        captureKey = false
                        diagnostic('Menu hotkey: ' .. keyNames[settings.hotkey])
                        break
                    end
                end
            end
        else
            local down = isKeyDown(settings.hotkey)
            if down and not previousHotkeyDown and not sampIsChatInputActive()
                and not sampIsDialogActive()
                and (type(isPauseMenuActive) ~= 'function' or not isPauseMenuActive()) then
                window[0] = not window[0]
                uiError = ''
            end
            previousHotkeyDown = down
        end

        if state == 'hospital_wait' then
            if not settings.enabled then
                hospitalAt = nil
                hospitalCount = 0
                go('idle')

            elseif hospitalAt then
                hospitalCount = math.max(0, hospitalAt - now)

                if now >= hospitalAt then
                    hospitalAt = nil
                    hospitalCount = 0
                    go('idle')
                    issueRec()
                end
            end

        elseif state == 'wait_reconnect' then
            if spawnAt and now - spawnAt >= 8 then
                go('settle', 3, 25)

            elseif syncCount >= 3
                and firstSyncAt
                and now - firstSyncAt >= 8
            then
                go('settle', 3, 25)

            elseif now - enteredAt >= 45 then
                halt('Reconnect cancelled: no spawn/sync confirmation')
            end

        elseif state == 'settle' and now >= actionAt then
            go('inventory_request', 1, 15)

        elseif state == 'inventory_request'
            and now >= actionAt
        then
            inventory()

        elseif state == 'inventory_rejected' then
            errorMessage('Инвентарь не открылся.')
            halt('Inventory rejected')

        elseif state == 'security_tab' and now >= actionAt then
            go('security_wait', 0, 10)
            sendCEF('requestShowingInventory|28')

        elseif state == 'select_guard' and now >= actionAt then
            go('select_wait', 1, 10)
            sendCEF('selectSecurity|{"id":0}')

        elseif state == 'select_wait' and now >= actionAt then
            go('menu_wait', 0, 10)
            sendCEF('clickOnMenu|{"id": 0}')

        elseif state == 'choose_menu' and now >= actionAt then
            if guardMenuAction == 'spawn'
                and guardMenuIndex ~= nil
            then
                go('confirm_wait', 0, 10)

                local ok, err = pcall(
                    sampSendDialogResponse,
                    25749,
                    1,
                    guardMenuIndex,
                    ''
                )

                if not ok then
                    errorMessage('Ошибка меню: ' .. tostring(err))
                    halt('Guard menu error')
                end
            else
                local ok, err = pcall(function()
                    if sampIsDialogActive()
                        and sampGetCurrentDialogId() == 25749
                    then
                        sampCloseCurrentDialogWithButton(0)
                    end
                end)

                if not ok then
                    errorMessage('Ошибка закрытия меню: ' .. tostring(err))
                    halt('Guard dialog close error')

                elseif inventoryOpened then
                    go('close_inventory', 1, 6)
                else
                    halt('Guard dialog cancelled')
                end
            end

        elseif state == 'close_inventory'
            and now >= actionAt
        then
            go('close_inventory_wait', 0, 6)
            sendCEF('inventoryClose')

        elseif state == 'close_inventory_wait'
            and not inventoryOpened
        then
            halt('Inventory closed')

        elseif state == 'confirm_guard'
            and now >= actionAt
        then
            go('finished')

            local ok, err = pcall(
                sampSendDialogResponse,
                0, 1, 0, ''
            )

            if not ok then
                errorMessage('Ошибка подтверждения: ' .. tostring(err))
                halt('Guard confirm error')
            else
                halt('Guard sequence completed')
            end
        end

        if pending
            and deadlineAt > 0
            and now >= deadlineAt
            and state ~= 'idle'
        then
            local failedState = state

            if failedState == 'inventory_wait' then
                errorMessage('Инвентарь не открылся.')
            end

            halt('Timeout: ' .. failedState)
        end

        if state == 'hospital_wait'
            and hospitalCount > 0
            and font
        then
            local w, h = getScreenResolution()

            renderFontDrawText(
                font,
                cyr('Релог через... ' .. hospitalCount),
                math.floor(w * 0.045),
                math.floor(h * 0.60),
                0xFFFFFFFF
            )
        end
    end
end
 

Вложения

  • AutoRespawn.lua
    34 KB · Просмотры: 1