SA:MP Lua [ARZ] ModernControls

БеzликиЙ

Автор темы
Автор темы
Проверенный
1,072
663
Версия SA-MP
  1. 0.3.7-R3
Вас тоже заебало, что на Аризоне РПГ в 2025 году целых три разных независимых кнопки взаимодействия? ModernControls назначает их все на одну (с полной совместимостью с геймпадами!)

1742343659379.png


Инструкция по установке:
- засунуть скрипт в moonloader
- зайти в игру
- открыть настройки - управление
- в настройках управления с ног переназначить "Действие" на E (или любую другую понравившуюся вам кнопку)
- опционально - поменять название кнопки на то, которое вам больше нравится, в 10 строчке скрипта

Функционал скрипта:
- автоматическое переключение между режимами камеры для контроллера и для мыши, аналогично GTA 5 - если вы вдруг решите откинуться на спинку кресла и покататься на геймпаде
- перехват окна взаимодействия с одной кнопкой и подмена кнопки в нём
- автоматическое определение действия в поп-апе и использование правильной кнопки

Перехват кнопок работает только с ног (в авто и так всего одна кнопка взаимодействия, гудок).
Переключение режима камеры работает всегда.
Совместимо с GInput (кнопка действия - та же кнопка, что центрирует камеру, по умолчанию LB).
Не чит (автонаведение из одиночки по-прежнему не включается).

Удачи сделать из этого автодверь.
 

Вложения

  • ModernControls.lua
    3 KB · Просмотры: 20
Последнее редактирование:

Орк

Известный
313
266
Захотелось покататься на дальнобойщике геймпаде через GInput, а там с диалогами работа, поэтому вот костыль.
Lua:
local font
local y = 500

local wasDownPressed = false
local wasUpPressed = false
local wasAPressed = false
local wasBPressed = false

function main()
    while not isSampAvailable() do wait(100) end
    font = renderCreateFont('Arial', 18, 5)

    while true do
        wait(0)

        if sampIsDialogActive() then
            local text = sampGetDialogText()
            local total = getListItemsCount(text)
            local current = sampGetCurrentDialogListItem()

            -- стрелка вниз (9)
            if isButtonPressed(PLAYER_HANDLE, 9) then
                if not wasDownPressed then
                    current = (current + 1) % total
                    sampSetCurrentDialogListItem(current)
                    wasDownPressed = true
                end
            else
                wasDownPressed = false
            end

            -- стрелка вверх (8)
            if isButtonPressed(PLAYER_HANDLE, 8) then
                if not wasUpPressed then
                    current = (current - 1 + total) % total
                    sampSetCurrentDialogListItem(current)
                    wasUpPressed = true
                end
            else
                wasUpPressed = false
            end

            -- кнопка A (16) - OK
            if isButtonPressed(PLAYER_HANDLE, 16) then
                if not wasAPressed then
                    sampCloseCurrentDialogWithButton(1)
                    wasAPressed = true
                end
            else
                wasAPressed = false
            end

            -- кнопка B (4) - Cancel
            if isButtonPressed(PLAYER_HANDLE, 4) then
                if not wasBPressed then
                    sampCloseCurrentDialogWithButton(0)
                    wasBPressed = true
                end
            else
                wasBPressed = false
            end

            -- отладочная информация
            -- renderFontDrawText(font, string.format("Строк: %d | Выбрано: %d", total, current), 500, y, 0xFFFFFFFF)
        end
    end
end

function getListItemsCount(text)
    if not text then return 0 end
    local count = 0 -- исключаем заголовок
    for _ in text:gmatch("[^\n]+") do
        count = count + 1
    end
    return math.max(count, 0)
end

-- A - 16
-- B - 4
-- Down - 9
-- UP - 8
Выбор пунктов на вверх/вниз, на A enter, на B ESC

Я придумал вариант лучше геймпада, управление мышью геймпадом!
Lua:
script_name("GamepadMouseControl")
script_author("chatgpt")
local encoding = require 'encoding'
encoding.default = 'CP1251'
local u8 = encoding.UTF8
local ffi = require 'ffi'
local bit = require 'bit'

-- FFI-объявления для XInput и WinAPI
ffi.cdef[[
typedef unsigned long DWORD;
typedef unsigned short WORD;
typedef unsigned char BYTE;
typedef int BOOL;
typedef short SHORT;
typedef unsigned long ULONG_PTR;

typedef void* HWND;
typedef struct { long left; long top; long right; long bottom; } RECT;
typedef struct { long x; long y; } POINT;

typedef struct _XINPUT_GAMEPAD {
  WORD wButtons;
  BYTE bLeftTrigger;
  BYTE bRightTrigger;
  SHORT sThumbLX;
  SHORT sThumbLY;
  SHORT sThumbRX;
  SHORT sThumbRY;
} XINPUT_GAMEPAD;

typedef struct _XINPUT_STATE {
  DWORD dwPacketNumber;
  XINPUT_GAMEPAD Gamepad;
} XINPUT_STATE;

DWORD XInputGetState(DWORD dwUserIndex, XINPUT_STATE *pState);
void  XInputEnable(BOOL enable);

HWND FindWindowA(const char* lpClassName, const char* lpWindowName);
BOOL GetClientRect(HWND hWnd, RECT *lpRect);
BOOL ClientToScreen(HWND hWnd, POINT *lpPoint);
BOOL SetCursorPos(int X, int Y);
void mouse_event(DWORD dwFlags, DWORD dx, DWORD dy, DWORD dwData, ULONG_PTR dwExtraInfo);
BOOL ClipCursor(const RECT *lpRect);
]]

-- Загрузка библиотек
local user32 = ffi.load("user32.dll")
local xinput = ffi.load("xinput1_4.dll", true) or ffi.load("XInput9_1_0.dll", true)
xinput.XInputEnable(1)

-- Получение окна SA:MP
local hwnd = user32.FindWindowA(nil, "GTA:SA:MP") -- ← заголовок окна
local crect = ffi.new("RECT")
local screenRect = ffi.new("RECT")

-- Начальные координаты курсора
local curX, curY = 400, 300

-- Настройки чувствительности
local DEADZONE = 8000
local SPEED = 15

-- Состояние управления
local controlEnabled = false
local lastButtons = 0

-- Шрифт для текста на экране
local font = renderCreateFont("Tahoma", 14, 5)

-- Если окно найдено — ограничим курсор областью окна
if hwnd ~= nil then
    user32.GetClientRect(hwnd, crect)
    local pt = ffi.new("POINT", {0, 0})
    user32.ClientToScreen(hwnd, pt)

    screenRect.left = pt.x
    screenRect.top = pt.y
    screenRect.right = pt.x + crect.right
    screenRect.bottom = pt.y + crect.bottom

    user32.ClipCursor(screenRect)

    -- Центр курсора
    curX = crect.right / 2
    curY = crect.bottom / 2
end

function main()
    while not isSampAvailable() do wait(100) end

    while true do
        wait(0)

        -- Получаем состояние геймпада
        local state = ffi.new("XINPUT_STATE")
        if xinput.XInputGetState(0, state) == 0 then
            local gp = state.Gamepad
            local btn = gp.wButtons

            -- Переключение по кнопке Start (0x0010)
            local startNow = bit.band(btn, 0x0010) ~= 0
            local startBefore = bit.band(lastButtons, 0x0010) ~= 0
            if startNow and not startBefore then
                controlEnabled = not controlEnabled
                local text1 = "{00FF00}[GamepadMouse]{FFFFFF} Управление: " .. (controlEnabled and "включено" or "выключено")
                sampAddChatMessage(u8:decode(text1), -1)
                wait(200)
            end
            lastButtons = btn

            if controlEnabled then
                -- Получение положения стика
                local lx, ly = gp.sThumbLX, gp.sThumbLY
                if math.abs(lx) < DEADZONE then lx = 0 end
                if math.abs(ly) < DEADZONE then ly = 0 end

                -- Преобразование в движение курсора
                local dx = lx / 32767 * SPEED
                local dy = ly / 32767 * SPEED

                curX = curX + dx
                curY = curY - dy -- инвертируем, т.к. вверх — уменьшение Y

                -- Ограничиваем в пределах окна
                if hwnd ~= nil then
                    local w, h = crect.right, crect.bottom
                    if curX < 0 then curX = 0 end
                    if curY < 0 then curY = 0 end
                    if curX > w then curX = w end
                    if curY > h then curY = h end

                    -- Устанавливаем курсор на экране
                    local pt = ffi.new("POINT", {curX, curY})
                    user32.ClientToScreen(hwnd, pt)
                    user32.SetCursorPos(pt.x, pt.y)
                end

                -- Кнопка A (0x1000) — ЛКМ
                if bit.band(btn, 0x1000) ~= 0 then
                    user32.mouse_event(0x0002, 0, 0, 0, 0) -- LEFTDOWN
                    user32.mouse_event(0x0004, 0, 0, 0, 0) -- LEFTUP
                    wait(150)
                end

                -- Кнопка X (0x4000) — ПКМ
                if bit.band(btn, 0x4000) ~= 0 then
                    user32.mouse_event(0x0008, 0, 0, 0, 0) -- RIGHTDOWN
                    user32.mouse_event(0x0010, 0, 0, 0, 0) -- RIGHTUP
                    wait(150)
                end

                -- Прокрутка вверх (LB — 0x0100)
                if bit.band(btn, 0x0100) ~= 0 then
                    user32.mouse_event(0x0800, 0, 0, 120, 0)
                    wait(150)
                end

                -- Прокрутка вниз (RB — 0x0200)
                if bit.band(btn, 0x0200) ~= 0 then
                    user32.mouse_event(0x0800, 0, 0, -120, 0)
                    wait(150)
                end
            end
        end
    end
end

-- Отображение текста на экране
function onD3DPresent()
    local text2 = string.format("Gamepad мышь: %s", controlEnabled and "включено" or "выключено")
    renderFontDrawText(font,
        u8:decode(text2),
        510, 500, controlEnabled and 0xFF00FF00 or 0xFFFF0000)
end

-- При завершении скрипта снимаем ограничение курсора
function onScriptTerminate(s, quit)
    user32.ClipCursor(nil)
end
КнопкаДействие
Левый стикПеремещение курсора мыши
AЛевый клик мыши
XПравый клик мыши
LB / RBПрокрутка колёсиком
StartВкл/выкл управление мышью
Да, нужна доработка chatgpt всё-таки, например, кнопку ESC что ли отдельно рендерить и на неё нажимать, иногда необходима, например, при открытом телефоне.
P.S использовал XInput9_1_0.dll и Ginput отсюда https://libertycity.ru/files/gta-san-andreas/84240-ginput.html
А так же я не очень понимаю, что с безопасностью, но у меня всё работает.
если что мой геймпад https://www.dns-shop.ru/product/40e...j-microsoft-xbox-wireless-controller-zelenyj/
 
Последнее редактирование:
  • Нравится
Реакции: БеzликиЙ