Полезные сниппеты и функции

Willy4ka

вилличка
Модератор
732
1,094
Описание: открывает диалог выбора папки
Код:
Lua:
local ffi = require('ffi');
local bit = require('bit');

ffi.cdef[[
    typedef void* HANDLE;
    typedef HANDLE HWND;
    typedef unsigned long DWORD;
    typedef int BOOL;
    typedef unsigned int UINT;
    typedef char* LPSTR;
    typedef const char* LPCSTR;
    typedef wchar_t* LPWSTR;
    typedef const wchar_t* LPCWSTR;
    typedef void* LPVOID;
    typedef long LPARAM;

    typedef struct _BROWSEINFOW {
        HWND hwndOwner;
        LPCWSTR pidlRoot;
        LPWSTR pszDisplayName;
        LPCWSTR lpszTitle;
        UINT ulFlags;
        int (__stdcall *lpfn)(HWND, UINT, LPARAM, LPARAM);
        LPARAM lParam;
        int iImage;
    } BROWSEINFOW;

    int SHGetPathFromIDListW(void *pidl, LPWSTR pszPath);
    void CoTaskMemFree(void *pv);
    void *SHBrowseForFolderW(BROWSEINFOW *lpbi);
    int MultiByteToWideChar(unsigned int CodePage, DWORD dwFlags, const char* lpMultiByteStr, int cbMultiByte, LPWSTR lpWideCharStr, int cchWideChar);
    int WideCharToMultiByte(unsigned int CodePage, DWORD dwFlags, LPCWSTR lpWideCharStr, int cchWideChar, LPSTR lpMultiByteStr, int cbMultiByte, LPCSTR lpDefaultChar, BOOL* lpUsedDefaultChar);
]]
local shell32 = ffi.load("shell32");
local ole32 = ffi.load("ole32");

function cp1251_to_utf16(str)
    local len = ffi.C.MultiByteToWideChar(1251, 0, str, -1, nil, 0)
    local buf = ffi.new("wchar_t[?]", len)
    ffi.C.MultiByteToWideChar(1251, 0, str, -1, buf, len)
    return buf
end

function utf16_to_cp1251(wstr)
    local len = ffi.C.WideCharToMultiByte(1251, 0, wstr, -1, nil, 0, nil, nil)
    local buf = ffi.new("char[?]", len)
    ffi.C.WideCharToMultiByte(1251, 0, wstr, -1, buf, len, nil, nil)
    return ffi.string(buf)
end

function selectFolder(title)
    title = title or ""
    local bi = ffi.new("BROWSEINFOW")
    local hwnd = ffi.new("HWND",nil);
    bi.hwndOwner = hwnd
    bi.pidlRoot = nil
    bi.pszDisplayName = ffi.new("wchar_t[?]", 260)
    bi.lpszTitle = cp1251_to_utf16(title)
    bi.ulFlags = bit.bor(0x00000001, 0x00000040)
    bi.lpfn = nil
    bi.lParam = 0
    bi.iImage = 0

    local pidl = shell32.SHBrowseForFolderW(bi)
    if pidl ~= nil then
        local path = ffi.new("wchar_t[?]", 260)
        if shell32.SHGetPathFromIDListW(pidl, path) ~= 0 then
            ole32.CoTaskMemFree(pidl)
            return utf16_to_cp1251(path)
        end
        ole32.CoTaskMemFree(pidl)
    end
    return nil
end

Пример использования:
Lua:
local path = selectFolder()
if path then
    sampAddChatMessage("Select folder: "..path, -1)
end

1752248653675.png
 

Lance_Sterling

Известный
1,016
375
Все ниже предоставленное работает только на SAMP R3
Описание: отключает HelpDialog который открывается на F1
Lua:
local samp = getModuleHandle("samp.dll")
memory.fill(samp + 0x752E2, 0x90, 0x5, true);

Описание: отключает "Returning to class selection after next" при нажатии F4
Lua:
local samp = getModuleHandle("samp.dll")
memory.fill(samp + 0x79B0, 0x90, 10, true);
memory.fill(samp + 0x79C6, 0x90, 0x5, true);

Описание: отключает отрисовку NetStats при нажатии F5 (бесполезная штука для обычного пользователя, но если на проекте не развит клиентсайд и в зоне стрима будет кастомная тачка - пофиксит краш)
Lua:
local samp = getModuleHandle("samp.dll")
memory.fill(samp + 0x7528A, 0x90, 0x5, true);
 

chapo

tg/inst: @moujeek
Всефорумный модератор
9,204
12,526
Описание: получает размер png изображения в пикселях
Код:
Lua:
local function getPngResolution(filePath)
    local size = { x = 0, y = 0 };
    local file = io.open(filePath, 'rb');
    if (not file) then
        return size;
    end
    local header = file:read(24);
    file:close();
    if (not header or header:sub(1, 8) ~= "\137PNG\r\n\26\n") then
        return size;
    end
    for i = 1, 4 do size.x = size.x * 256 + header:byte(16 + i) end
    for i = 1, 4 do size.y = size.y * 256 + header:byte(20 + i) end
    return size;
end
Пример использования: восстановление стандартного соотношения сторон изображения
Lua:
function ResetImageAspectRatio(image)
    local originalSize = Utils.getPngResolution(image.path);
    image.size.y = image.size.x / (originalSize.x / originalSize.y);
    return image.size;
end
 
Последнее редактирование:

g305noobo

Известный
Модератор
360
649
описание: устанавливает максимальное количество патрон в обойме
код:
Lua:
local ffi = require("ffi")

function setMaxAmmoInClip(weaponId, maxAmmo)
    local idx = weaponId + 36 -- skill 2
    local base = 0xC8AAB8
    local CWeaponInfoSize = 0x70
    local ammoInClipOffset = 0x20

    local weaponAddress = base + idx * CWeaponInfoSize
    ffi.cast("uint16_t*", weaponAddress + ammoInClipOffset)[0] = maxAmmo
end

-- пример для дигла
setMaxAmmoInClip(24, 1337)
 

User500050

Участник
24
26
Описание: Адаптированный SmoothScroll для mimgui с некоторыми отличиями.

Применение: Прокрутка применяется к тому объекту, который был объявлен выше него. То есть вы сверху объявили ListBoxHeader, и сразу же снизу добавили SmoothScroll.

Дополнение: Так как mimgui отличается многим от своего предка, то и тут не обошлось без обходных путей для исправления некоторых моментов при прокрутке. Подробности описаны в коде.

UPD: Исправил баг при котором прокрутки на других объектах не работали. Теперь фикс используется по-умолчанию, но нужно использовать уже другую функцию для обновления состояния прокрутки. Добавил спец. функции для установления позиции прокрутки для объектов с SmoothScroll. Подробности в коде.

Код:
Lua:
-- local imgui = import "mimgui"

imgui._SmoothScroll = {
-- Состояние плавной прокрутки у всех объектов
-- [Можно менять]
    active = true,

-- Стандартная скорость прокрутки. Рекомендуемое значение от 15 до 50
-- [Можно менять]
    defaultSpeed = 40,

-- Плавность прокрутки. Рекомендуемое значение от 1 до 10
-- [Можно менять]
    smoothness = 5,

-- Клавиша управления горизонтальной прокруткой (по-умолчанию 0x10 = VK_SHIFT)
-- [Можно менять]
    xAxisKey = 0x10,

-- Обновитель состояния прокрутки. Без этой функции плавная прокрутка работать не будет.
-- Использовать строго внутри pre-frame (второй по счету) функции при объявлении через `imgui.OnFrame(function() ... end, function() ...[>> использовать тут <<]... end, function() ... end)`
    Update = function ()
        local ss = imgui._SmoothScroll
        local prev_MouseWheel = ss.MouseWheel or imgui.GetIO().MouseWheel
        ss.MouseWheel = imgui.GetIO().MouseWheel
        if ss.active then
            if ss.scrolling then
                ss.scrolling = false
                imgui.GetIO().MouseWheel = 0
            else
                imgui.GetIO().MouseWheel = prev_MouseWheel
            end
        end
    end,

    pos = {},
    scroll = {},
    setPos = {},
    curr_id = nil,
    scrolling = false,

-- Пока только для информации
    MouseWheel = nil
}
imgui.SmoothScroll = function (id, speed, lockX, lockY)
    local ss = imgui._SmoothScroll
    if ss.active then
        ss.curr_id = id
        if ss.pos[id] == nil then
            ss.pos[id] = {x = 0.0, y = 0.0}
            ss.scroll[id] = {x = 0.0, y = 0.0}
            ss.setPos[id] = false
        end
        speed = speed or ss.defaultSpeed
        local MouseWheel = ss.MouseWheel or imgui.GetIO().MouseWheel
        local smoothness = 100 * (ss.smoothness > 0 and ss.smoothness or 1)
        if (imgui.IsItemHovered() or imgui.IsWindowHovered()) and (not imgui.IsMouseDown(0)) and (not ss.setPos[id]) then
            if (not lockY) and MouseWheel ~= 0 and (not isKeyDown(ss.xAxisKey) or lockX) then
                ss.scrolling = true
                ss.pos[id].y = ss.scroll[id].y + (-MouseWheel)*speed
            end
            ss.pos[id].y = math.max(math.min(ss.pos[id].y, imgui.GetScrollMaxY()-0), 0)
            ss.scroll[id].y = ss.scroll[id].y + speed*(ss.pos[id].y - ss.scroll[id].y)/(smoothness)
            imgui.SetScrollY(ss.scroll[id].y)
            if (not lockX) and MouseWheel ~= 0 and (isKeyDown(ss.xAxisKey) or lockY) then
                ss.scrolling = true
                ss.pos[id].x = ss.scroll[id].x + (-MouseWheel)*speed
            end
            ss.pos[id].x = math.max(math.min(ss.pos[id].x, imgui.GetScrollMaxX()-0), 0)
            ss.scroll[id].x = ss.scroll[id].x + speed*(ss.pos[id].x - ss.scroll[id].x)/(smoothness)
            imgui.SetScrollX(ss.scroll[id].x)
        else
            ss.setPos[id] = false
            ss.pos[id].x = imgui.GetScrollX()
            ss.scroll[id].x = ss.pos[id].x
            ss.pos[id].y = imgui.GetScrollY()
            ss.scroll[id].y = ss.pos[id].y
        end
    end
end

-- Для объектов с плавной прокруткой (Объекты с SmoothScroll) вместо обычных imgui.SetScroll... функций нужно использовать imgui.SetSmoothScroll...
do
local ss = imgui._SmoothScroll
local function setX()
    ss.pos[ss.curr_id].x = imgui.GetScrollX()
    ss.scroll[ss.curr_id].x = ss.pos[ss.curr_id].x
    ss.setPos[ss.curr_id] = true
end
local function setY()
    ss.pos[ss.curr_id].y = imgui.GetScrollY()
    ss.scroll[ss.curr_id].y = ss.pos[ss.curr_id].y
    ss.setPos[ss.curr_id] = true
end
---@param scroll_x float
imgui.SetSmoothScrollX = function (scroll_x)
    imgui.SetScrollX(scroll_x)
    setX()
end
---@param scroll_y float
imgui.SetSmoothScrollY = function (scroll_y)
    imgui.SetScrollY(scroll_y)
    setY()
end
---@param center_x_ratio float|nil
imgui.SetSmoothScrollHereX = function (center_x_ratio)
    imgui.SetScrollHereX(center_x_ratio)
    setX()
end
---@param center_y_ratio float|nil
imgui.SetSmoothScrollHereY = function (center_y_ratio)
    imgui.SetScrollHereY(center_y_ratio)
    setY()
end
---@param local_x float
---@param center_x_ratio float|nil
imgui.SetSmoothScrollFromPosX = function (local_x, center_x_ratio)
    imgui.SetScrollFromPosX(local_x, center_x_ratio)
    setX()
end
---@param local_y float
---@param center_y_ratio float|nil
imgui.SetSmoothScrollFromPosY = function (local_y, center_y_ratio)
    imgui.SetScrollFromPosY(local_y, center_y_ratio)
    setY()
end
end

-- some shii...

imgui.OnFrame(
function () --[[ window visibility... ]] end,
function ()
    -- Без функции imgui._SmoothScroll.Update() плавная прокрутка работать не будет.
    -- Нужно использовать его строго внутри pre-frame функции-обработчика при передаче через imgui.OnFrame()
    imgui._SmoothScroll.Update()
end,
function () --[[ draw... ]] end)

Пример использования:
 

Вложения

  • SmoothScrollTest.lua
    8.3 KB · Просмотры: 6
Последнее редактирование:
  • Нравится
Реакции: kirieshki. и bfix

вайега52

Налуашил состояние
Модератор
2,980
3,097
Описание: изменяет свет очков ночного/инфракрасного света
Код:
Lua:
local memory = require("memory")


function setInfraredVisionColor(r, g, b, a)
  memory.setfloat(0x735F2F + 4, r, true)
  memory.setfloat(0x735F37 + 4, g, true)
  memory.setfloat(0x735F3F + 4, b, true)
  memory.setfloat(0x735F47 + 4, a, true)
end

function setNightVisionColor(r, g, b, a)
  memory.setfloat(0x735F7F + 4, r, true)
  memory.setfloat(0x735F87 + 4, g, true)
  memory.setfloat(0x735F8F + 4, b, true)
  memory.setfloat(0x735F97 + 4, a, true)
end

Пример использования:
Lua:
local memory = require("memory")

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

  sampRegisterChatCommand("nc", function()
    setNightVisionColor(0.5, 0.5, 0.5, 1)
  end)

  wait(-1)
end

function setInfraredVisionColor(r, g, b, a)
  memory.setfloat(0x735F2F + 4, r, true)
  memory.setfloat(0x735F37 + 4, g, true)
  memory.setfloat(0x735F3F + 4, b, true)
  memory.setfloat(0x735F47 + 4, a, true)
end

function setNightVisionColor(r, g, b, a)
  memory.setfloat(0x735F7F + 4, r, true)
  memory.setfloat(0x735F87 + 4, g, true)
  memory.setfloat(0x735F8F + 4, b, true)
  memory.setfloat(0x735F97 + 4, a, true)
end
 

БеzликиЙ

Автор темы
Проверенный
1,720
1,002
Получение оффсетов dummy транспортного средства (используется в основном для фар):
Lua:
function get_light_pos(car, light, invert_x)
    local m_nModelIndex = getCarModel(car) -- vehicle -> m_nModelIndex
    local ms_ModelInfoPtrs = 0xA9B0C8
    local CBaseModelInfo_size = 0x20
    local ptr = ms_ModelInfoPtrs + (0x4 * m_nModelIndex) -- ms_ModelInfoPtrs[vehicle -> m_nModelIndex] (pointer)
    ptr = mem.getuint32(ptr) -- cModelInfo
    ptr = ptr + 0x5c -- cModelInfo -> m_pVehicleStruct
    ptr = mem.getuint32(ptr) -- VehicleStruct
    ptr = ptr + 0x0 + 12*light
    local x = mem.getfloat(ptr)
    local y = mem.getfloat(ptr + 4)
    local z = mem.getfloat(ptr + 8)
    if invert_x then return -x, y, z else return x, y, z end
end
Пример использования:
Код:
local lights = {
    lf = {},
    rf = {},
    lb = {},
    rb = {}
}
lights.lf.x, lights.lf.y, lights.lf.z = get_light_pos(car, 0, true) -- левый передний
lights.rf.x, lights.rf.y, lights.rf.z = get_light_pos(car, 0, false) -- правый передний
lights.lb.x, lights.lb.y, lights.lb.z = get_light_pos(car, 1, true) -- левый задний
lights.rb.x, lights.rb.y, lights.rb.z = get_light_pos(car, 1, false) -- правый задний

Ух, как перепишу половину своих скриптов с использованием этого чуда!..

Спойлер к обнове ivf.lua: там это используется для добавления поворотников туда, где их изначально не было.
1753652682129.png
 

User500050

Участник
24
26
Описание: Фикс проблемы с кодировкой копирования/вставки текста на консоль SAMPFUNCS при переключении языка с кириллицы на латинский.
Использует часть модуля hooks от @RTD

Код:
SFConsoleClipboardFix.lua:
-- Протестировано на SAMPFUNCS v5.7.1 rel.25 (SA-MP 0.3.7-R1)

local settings = {
    enabled = true,
    consoleCommand = "clipboardfix",
}

local ffi = require("ffi")
ffi.cdef[[
int VirtualProtect(void* lpAddress, unsigned long dwSize, unsigned long flNewProtect, unsigned long* lpflOldProtect);
void* VirtualAlloc(void* lpAddress, unsigned long dwSize, unsigned long  flAllocationType, unsigned long flProtect);
int VirtualFree(void* lpAddress, unsigned long dwSize, unsigned long dwFreeType);

typedef unsigned int uint32;
typedef int BOOL;
typedef void* HGLOBAL;
typedef void* HANDLE;
typedef char* LPSTR;
typedef const char* LPCSTR;
typedef wchar_t* LPCWSTR;
typedef wchar_t* LPWSTR;
typedef BOOL* LPBOOL;
typedef unsigned int UINT;
typedef unsigned long DWORD;

BOOL OpenClipboard(void* hWndNewOwner);
BOOL CloseClipboard();
BOOL EmptyClipboard();
HANDLE SetClipboardData(UINT uFormat, HANDLE hMem);
HANDLE GetClipboardData(UINT uFormat);
UINT EnumClipboardFormats(UINT format);
HGLOBAL GlobalAlloc(UINT uFlags, size_t dwBytes);
void* GlobalLock(HGLOBAL hMem);
BOOL GlobalUnlock(HGLOBAL hMem);
int MultiByteToWideChar(UINT CodePage, DWORD dwFlags, LPCSTR lpMultiByteStr, int cbMultiByte, LPWSTR lpWideCharStr, int cchWideChar);
int WideCharToMultiByte(
    UINT    CodePage,
    DWORD    dwFlags,
    LPCWSTR    lpWideCharStr,
    int        cchWideChar,
    LPSTR    lpMultiByteStr,
    int        cbMultiByte,
    LPCSTR    lpDefaultChar,
    LPBOOL    lpUsedDefaultChar
);

enum {
    CF_UNICODETEXT    = 13,
    CF_TEXT            = 1,
    GMEM_MOVEABLE    = 0x0002,
    CP1251            = 1251,
};
]]

-- hooks от @RTD (https://www.blast.hk/threads/55743/)
-- module hooks -----------------------------------------------
local function copy(dst, src, len)
    return ffi.copy(ffi.cast('void*', dst), ffi.cast('const void*', src), len)
end
local buff = {free = {}}
local function VirtualProtect(lpAddress, dwSize, flNewProtect, lpflOldProtect)
    return ffi.C.VirtualProtect(ffi.cast('void*', lpAddress), dwSize, flNewProtect, lpflOldProtect)
end
local function VirtualAlloc(lpAddress, dwSize, flAllocationType, flProtect, blFree)
    local alloc = ffi.C.VirtualAlloc(lpAddress, dwSize, flAllocationType, flProtect)
    if blFree then
        table.insert(buff.free, alloc)
    end
    return ffi.cast('intptr_t', alloc)
end
local jmp_hook = {hooks = {}}
function jmp_hook.new(cast, callback, hook_addr, size, trampoline, org_bytes_tramp)
    jit.off(callback, true) --off jit compilation | thx FYP
    local size = size or 5
    local trampoline = trampoline or false
    local new_hook, mt = {}, {}
    local detour_addr = tonumber(ffi.cast('intptr_t', ffi.cast(cast, callback)))
    local old_prot = ffi.new('unsigned long[1]')
    local org_bytes = ffi.new('uint8_t[?]', size)
    copy(org_bytes, hook_addr, size)
    if trampoline then
        local alloc_addr = VirtualAlloc(nil, size + 5, 0x1000, 0x40, true)
        local trampoline_bytes = ffi.new('uint8_t[?]', size + 5, 0x90)
        if org_bytes_tramp then
            local i = 0
            for byte in org_bytes_tramp:gmatch('(%x%x)') do
                trampoline_bytes[i] = tonumber(byte, 16)
                i = i + 1
            end
        else
            copy(trampoline_bytes, org_bytes, size)
        end
        trampoline_bytes[size] = 0xE9
        ffi.cast('int32_t*', trampoline_bytes + size + 1)[0] = hook_addr - tonumber(alloc_addr) - size + (size - 5)
        copy(alloc_addr, trampoline_bytes, size + 5)
        new_hook.call = ffi.cast(cast, alloc_addr)
        mt = {__call = function(self, ...)
            return self.call(...)
        end}
    else
        new_hook.call = ffi.cast(cast, hook_addr)
        mt = {__call = function(self, ...)
            self.stop()
            local res = self.call(...)
            self.start()
            return res
        end}
    end
    local hook_bytes = ffi.new('uint8_t[?]', size, 0x90)
    hook_bytes[0] = 0xE9
    ffi.cast('int32_t*', hook_bytes + 1)[0] = detour_addr - hook_addr - 5
    new_hook.status = false
    local function set_status(bool)
        new_hook.status = bool
        VirtualProtect(hook_addr, size, 0x40, old_prot)
        copy(hook_addr, bool and hook_bytes or org_bytes, size)
        VirtualProtect(hook_addr, size, old_prot[0], old_prot)
    end
    new_hook.stop = function() set_status(false) end
    new_hook.start = function() set_status(true) end
    new_hook.start()
    if org_bytes[0] == 0xE9 or org_bytes[0] == 0xE8 then
        print('[WARNING] rewrote another hook'.. (trampoline and ' (old hook was disabled, through trampoline)' or ''))
    end
    table.insert(jmp_hook.hooks, new_hook)
    return setmetatable(new_hook, mt)
end
addEventHandler('onScriptTerminate', function(scr)
    if scr == script.this then
        for i, hook in ipairs(jmp_hook.hooks) do
            if hook.status then
                hook.stop()
            end
        end
        for i, addr in ipairs(buff.free) do
            ffi.C.VirtualFree(addr, 0, 0x8000)
        end
    end
end)
---------------------------------------------------------------

local base = getModuleHandle("SAMPFUNCS.asi")
assert(base and base ~= 0, "Can't find SAMPFUNCS.asi file")

local pasteToConsoleInputOrig
local copyToClipboardOrig

local function toggle()
    settings.enabled = not settings.enabled
    print("ClipboardFix " .. (settings.enabled and "{00ff00}enabled" or "{ff0000}disabled"))
end

local function extract_inline_string(Src, a2, a3, a4, Size)
    local data = ffi.new("uint8_t[15]")
    ffi.copy(data, ffi.new("uint32_t[1]", tonumber(ffi.cast("uint32_t", Src))), 4)
    ffi.copy(data + 4, ffi.new("uint32_t[1]", a2), 4)
    ffi.copy(data + 8, ffi.new("uint32_t[1]", a3), 4)
    ffi.copy(data + 12, ffi.new("uint32_t[1]", a4), 3) -- максимум 15 байт
    return ffi.string(data, Size)
end

local function cp1251ToUtf16(str)
    local len = #str
    local wideLen = ffi.C.MultiByteToWideChar(ffi.C.CP1251, 0, str, len, nil, 0)
    if wideLen == 0 then return nil end

    local wchar_buf = ffi.new("wchar_t[?]", wideLen + 1)
    ffi.C.MultiByteToWideChar(ffi.C.CP1251, 0, str, len, wchar_buf, wideLen)
    wchar_buf[wideLen] = 0 -- null-terminate
    return wchar_buf, (wideLen + 1) * 2
end

local function utf16ToCp1251(wstr)
    local wlen = 0
    while wstr[wlen] ~= 0 do wlen = wlen + 1 end

    local mbLen = ffi.C.WideCharToMultiByte(ffi.C.CP1251, 0, wstr, wlen, nil, 0, nil, nil)
    if mbLen == 0 then return nil end

    local char_buf = ffi.new("char[?]", mbLen + 1)
    ffi.C.WideCharToMultiByte(ffi.C.CP1251, 0, wstr, wlen, char_buf, mbLen, nil, nil)
    char_buf[mbLen] = 0 -- null-terminate

    return ffi.string(char_buf, mbLen)
end


local initialised = false
function main()
    if not initialised then
        initialised = true
        lua_thread.create(function ()
            main = nil
        end)
        if (not isSampLoaded()) or (not isSampfuncsLoaded()) then return false end
        while not isSampAvailable() do wait(100) end

-- Переводит формат копируемого текста с CF_TEXT на CF_UNICODETEXT (UTF-16, формат копирования текстов на Windows по большей части)
        copyToClipboardOrig = jmp_hook.new("char (__cdecl *)(void **Src, int a2, int a3, int a4, size_t Size, unsigned int a6)",
        function (Src, a2, a3, a4, Size, a6)
            if not settings.enabled then return copyToClipboardOrig(Src, a2, a3, a4, Size, a6) end
            local text
            if a6 <= 0xF then
                text = extract_inline_string(Src, a2, a3, a4, Size)
            else
                local str_ptr = ffi.cast("char*", Src)
                text = ffi.string(str_ptr, Size)
            end

            local utf16_text, utf16_len = cp1251ToUtf16(text)
            local new_uFormat = ffi.C.CF_UNICODETEXT
            if utf16_text == nil then return copyToClipboardOrig(Src, a2, a3, a4, Size, a6) end

            local hNew = ffi.C.GlobalAlloc(ffi.C.GMEM_MOVEABLE, utf16_len)
            if hNew == nil then return copyToClipboardOrig(Src, a2, a3, a4, Size, a6) end

            local new_ptr = ffi.C.GlobalLock(hNew)
            if new_ptr == nil then return copyToClipboardOrig(Src, a2, a3, a4, Size, a6) end

            ffi.copy(new_ptr, utf16_text, utf16_len)
            ffi.C.GlobalUnlock(hNew)

            if ffi.C.OpenClipboard(nil) == 0 then return copyToClipboardOrig(Src, a2, a3, a4, Size, a6) end
            ffi.C.EmptyClipboard()
            ffi.C.SetClipboardData(new_uFormat, hNew)
            ffi.C.CloseClipboard()

            return 0
        end, base + 0x14864)

-- Переводит формат вставляемого текста в поле ввода консоли с CF_UNICODETEXT на CF_TEXT (ANSI/CP1251, стандарт консоли SAMPFUNCS)
        pasteToConsoleInputOrig = jmp_hook.new("int (__thiscall *)(int this)",
        function (this)
            if not settings.enabled then return pasteToConsoleInputOrig(this) end
            if ffi.C.OpenClipboard(nil) == 0 then return pasteToConsoleInputOrig(this) end
            if ffi.C.EnumClipboardFormats(0) == ffi.C.CF_UNICODETEXT then
                local hMem = ffi.C.GetClipboardData(ffi.C.CF_UNICODETEXT)
                if hMem == nil then return pasteToConsoleInputOrig(this) end
                local ptr = ffi.C.GlobalLock(hMem)
                if ptr == nil then return pasteToConsoleInputOrig(this) end
                local text = utf16ToCp1251(ffi.cast("wchar_t*", ptr))

                local text_len = #text
                local text_buf = ffi.new("char[?]", text_len + 1, text)
                text_buf[text_len] = 0 -- null-terminated

                local uint32_ptr = ffi.cast("uint32_t*", this)
                uint32_ptr[0]  = 0
                uint32_ptr[1]  = 0
                uint32_ptr[2]  = 0
                uint32_ptr[3]  = 0
                uint32_ptr[4]  = 0
                uint32_ptr[5]  = 15

                local uint8_ptr = ffi.cast("uint8_t*", this)
                uint8_ptr[0] = 0

                local sub_10002619 = ffi.cast("void **(__thiscall *)(void **this, void *Src, size_t Size)", base + 0x2619)
                sub_10002619(ffi.cast("void**", this), text_buf, text_len)

                ffi.C.GlobalUnlock(hMem)
                ffi.C.CloseClipboard()
                return this
            end
            ffi.C.CloseClipboard()

            return pasteToConsoleInputOrig(this)
        end, base + 0x148F1)

        sampfuncsRegisterConsoleCommand(settings.consoleCommand, toggle)

        print("ClipboardFix " .. (settings.enabled and "{00ff00}enabled" or "{ff0000}disabled"))

        while true do
            wait(0)
        end
    end
end
 

chapo

tg/inst: @moujeek
Всефорумный модератор
9,204
12,526
Описание: получает цвет символа из строки в которой используются hex коды цветов по типу {ff0000}
Код:
Lua:
---@param str string
---@param pos number
---@param startColor? string
---@return string?
function string.getColorAtPos(str, pos, startColor)
    local result, parts, charIndex = nil, { [0] = startColor or '{ffffff}' }, 0;
    for char in str:gmatch('.') do
        charIndex = charIndex + 1;
        if (char == '{') then
            local colorTag = str:sub(charIndex, charIndex + 7):match('{%x+}');
            if (colorTag) then
                parts[charIndex] = colorTag;
            end
        end
    end
    for partIndex, partColorTag in pairs(parts) do
        if (pos >= partIndex) then
            result = partColorTag:match('{(%x+)}');
        end
    end
    return result or (startColor or 'ffffff');
end
Пример использования:
Lua:
local testString = '{9ACD32}[Подсказка]{FFFFFF} Подробнее: {9ACD32}/hotel';
local targetWordIndex = testString:find('/hotel');
print(targetWordIndex, string.getColorAtPos(testString, targetWordIndex)); -- Out: 48      9ACD32
 

kyrtion

Известный
1,324
483
Описание: Усовершенствованный код на асинхронные HTTP запросы в ООП (или не?).
Пример использования:
Первый пример:
-- local async_http_request = require('tools.http') -- for moonly
local cryptos = { 'bitcoin', 'ethereum', 'solana' }

async_http_request
  :create('json', 'GET', 'https://api.coingecko.com/api/v3/simple/price')
  :setHeaders({
    ['accept'] = 'application/json',
    ['user-agent'] = 'Unknown'
  })
  :setParams({
    ids = cryptos,
    vs_currencies = 'usd'
  })
  :setCallback(function(status_code, res, err)
    print(status_code, res, err)
  end)
  :send()
print(string.format('Запрос отправлен для %s', table.concat(cryptos, ', ')), -1)
-- 200   {"bitcoin":{"usd":118050},"ethereum":{"usd":4533.03},"solana":{"usd":193.82}}   nil
Второй пример:
-- local async_http_request = require('tools.http') -- for moonly

async_http_request
  :create('xform', 'POST', 'https://httpbin.org/post')
  :setParams({ key1 = 'val1&val2?/,=val3', key2 = 123, key3 = '<!название server"a>' })
  :setData({ key1 = 'val&1?привет!' })
  :setCallback(function(status_code, res, err)
    print(status_code, res, err)
  end)
  :send()

--[[
200   {
  "args": {
    "key1": "val1&val2?/,=val3",
    "key2": "123",
    "key3": "<!\u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 server\"a>"
  },
  "data": "",
  "files": {},
  "form": {
    "key1": "val&1?\u043f\u0440\u0438\u0432\u0435\u0442!"
  },
  "headers": {
    "Content-Length": "54",
    "Content-Type": "application/x-www-form-urlencoded",
    "Host": "httpbin.org",
    "User-Agent": "LuaSocket 3.0-rc1",
    "X-Amzn-Trace-Id": "Root=1-68a209d7-799337f111d230c047565790"
  },
  "json": null,
  "origin": "куда смотришь?)",
  "url": "https://httpbin.org/post?key1=val1#26val2#3F#2F#2C#3Dval3&key3=<!\u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 server\"a>&key2=123"
}
   nil
]]
Код:
Lua:
--- @diagnostic disable: lowercase-global
--- Author: kyrtion
--- Version: 2025-08-17

local effil = require('effil')
local encoding = require('encoding')
encoding.default = 'CP1251'
local u8 = encoding.UTF8

local header_content_types = {
  ['xform'] = 'application/x-www-form-urlencoded',
  ['json'] = 'application/json'
}

local function encodeUrl(data)
  local function valueToUrlEncode(str)
    str = str:gsub('([^%w])', function(char) return string.format('%%%02X', string.byte(char)) end)
    return str
  end
  local t = {}
  for k, v in pairs(data) do
    if type(v) == 'table' then
      local n = {}
      for _, j in ipairs(v) do
        table.insert(n, valueToUrlEncode(u8(tostring(j))))
      end
      if #n ~= 0 then
        table.insert(t, string.format('%s=%s', k, table.concat(n, '%2C')))
      end
    else
      v = valueToUrlEncode(u8(tostring(v)))
      table.insert(t, string.format('%s=%s', k, v))
    end
  end
  return u8(table.concat(t, '&'))
end

local function createRequest(_method, _url, _body)
  local requests = require('requests')
  local success, response = pcall(requests.request, _method, _url, _body)
  if success then
    response.json, response.xml = nil, nil
    return true, response
  else
    return false, response
  end
end

local function createEffilThread(method, url, body, callback)
  local thread = effil.thread(createRequest)(method, url, body)

  lua_thread.create(function()
    while true do
      wait(10)
      local status, err = thread:status()
      if not status or err then
        return callback(nil, nil, err)
      end
      if status == 'completed' or status == 'canceled' then
        local success, response = thread:get()
        if not success then
          return callback(nil, nil, response)
        end
        if response and type(response.text) == 'string' and response.text:len() ~= 0 then
          response.text = u8:decode(response.text)
        end
        return callback(response.status_code, response.text, nil)
      end
    end
  end)
end

local async_http_request = { request = {} }
async_http_request.__index = async_http_request

--- @param type 'xform'|'json'
--- @param method 'GET'|'POST'
--- @param url string
function async_http_request:create(type, method, url)
  self.request = {
    method = method,
    type = type,
    url = url,
    body = {
      headers = {}
    },
    callback = function() end
  }
  self:setHeaders({ ['content-type'] = header_content_types[type] })
  return self
end

--- @param headers table
function async_http_request:setHeaders(headers)
  for k, v in pairs(headers) do
    self.request.body.headers[k] = v
  end
  return self
end

--- @param params table
function async_http_request:setParams(params)
  self.request.url = self.request.url .. '?' .. encodeUrl(params)
  return self
end

--- @param data table
function async_http_request:setData(data)
  if self.request.type == 'xform' then
    self.request.body.data = encodeUrl(data)
  elseif self.request.type == 'json' then
    self.request.body.data = u8(encodeJson(data))
  end
  return self
end

--- @param callback fun(status_code: string|nil, res: any|nil, err: string|nil)
function async_http_request:setCallback(callback)
  self.request.callback = callback
  return self
end

function async_http_request:send()
  self.request.url = u8(self.request.url)
  createEffilThread(self.request.method, self.request.url, self.request.body, self.request.callback)
end

return async_http_request
 

chapo

tg/inst: @moujeek
Всефорумный модератор
9,204
12,526
Описание: функция для более удобного сообщения при разработке. Выводит список вызовов функций. Удобнее чем print(debug.traceback()) тем что сообщение на выходе получается более компактным, а так же переводит таблицы в читаемый вид.
Код:
Lua:
function debug.log(...)
    if (_G.LUBU_BUNDLED) then return end ---@diagnostic disable-line
    local function tableToString(tbl, indent, compact)
        local function formatTableKey(k)
            local defaultType = type(k);
            if (defaultType ~= 'string') then
                k = tostring(k);
            end
            local useSquareBrackets = k:find('^(%d+)') or k:find('(%p)') or k:find('\\') or k:find('%-');
            return useSquareBrackets == nil and k or ('[%s]'):format(defaultType == 'string' and "'" .. k .. "'" or k);
        end
        local str = { '{' };
        local indent = indent or 0;
        for k, v in pairs(tbl) do
            table.insert(str, ('%s%s = %s,'):format(string.rep("    ", compact and 0 or indent + 1), formatTableKey(k), type(v) == "table" and tableToString(v, indent + 1, compact) or (type(v) == 'string' and "'" .. v .. "'" or tostring(v))));
        end
        table.insert(str, string.rep('    ',compact and 0 or indent) .. '}');
        return table.concat(str, compact and '' or '\n');
    end
    local function getTrace()
        local list = {};
        local level = 3;
        while true do
            local info = debug.getinfo(level, "nS")
            if not info then break end
            if info.what ~= "C" and info.name then
                table.insert(list, ('%s()[%s:%d]'):format(info.name, info.namewhat, info.linedefined));
            end
            level = level + 1
        end
        return list;
    end

    local args = { ... };
    local trace = getTrace();
    for k, v in ipairs(args) do
        if (type(v) ~= 'string') then
            args[k] = type(v) == 'table' and tableToString(v, nil, true) or tostring(v);
        end
    end
    print(('%s: %s'):format(table.concat(trace, '->'), table.concat(args, '\t')));
end
Пример использования:
Lua:
local function a()
    b();
end
function b()
    c();
end
function c()
    debug.log('hello', true, { artur = { 'luasher', 'incel', 'dolbaeb' } });
end
a();
Print('test');
end

Результат: c()[global:53]->b()[global:49]->a()[local:45]: hello true {artur = {[1] = 'luasher',[2] = 'incel',[3] = 'dolbaeb',},}
 

вайега52

Налуашил состояние
Модератор
2,980
3,097
Получение оффсетов dummy транспортного средства (используется в основном для фар):
Lua:
function get_light_pos(car, light, invert_x)
    local m_nModelIndex = getCarModel(car) -- vehicle -> m_nModelIndex
    local ms_ModelInfoPtrs = 0xA9B0C8
    local CBaseModelInfo_size = 0x20
    local ptr = ms_ModelInfoPtrs + (0x4 * m_nModelIndex) -- ms_ModelInfoPtrs[vehicle -> m_nModelIndex] (pointer)
    ptr = mem.getuint32(ptr) -- cModelInfo
    ptr = ptr + 0x5c -- cModelInfo -> m_pVehicleStruct
    ptr = mem.getuint32(ptr) -- VehicleStruct
    ptr = ptr + 0x0 + 12*light
    local x = mem.getfloat(ptr)
    local y = mem.getfloat(ptr + 4)
    local z = mem.getfloat(ptr + 8)
    if invert_x then return -x, y, z else return x, y, z end
end
Пример использования:
Код:
local lights = {
    lf = {},
    rf = {},
    lb = {},
    rb = {}
}
lights.lf.x, lights.lf.y, lights.lf.z = get_light_pos(car, 0, true) -- левый передний
lights.rf.x, lights.rf.y, lights.rf.z = get_light_pos(car, 0, false) -- правый передний
lights.lb.x, lights.lb.y, lights.lb.z = get_light_pos(car, 1, true) -- левый задний
lights.rb.x, lights.rb.y, lights.rb.z = get_light_pos(car, 1, false) -- правый задний

Ух, как перепишу половину своих скриптов с использованием этого чуда!..

Спойлер к обнове ivf.lua: там это используется для добавления поворотников туда, где их изначально не было.
Посмотреть вложение 275165
Вот номера всех dummy:
C++:
// enum by forkerer (https://github.com/forkerer/)
enum eVehicleDummy {
    DUMMY_LIGHT_FRONT_MAIN      = 0,
    DUMMY_LIGHT_REAR_MAIN       = 1,

    DUMMY_LIGHT_FRONT_SECONDARY = 2,
    DUMMY_LIGHT_REAR_SECONDARY  = 3,

    DUMMY_SEAT_FRONT            = 4,
    DUMMY_SEAT_REAR             = 5,

    DUMMY_EXHAUST               = 6,
    DUMMY_ENGINE                = 7,
    DUMMY_GAS_CAP               = 8,
    DUMMY_TRAILER_ATTACH        = 9,
    DUMMY_HAND_REST             = 10,
    DUMMY_EXHAUST_SECONDARY     = 11,
    DUMMY_WING_AIR_TRAIL        = 12,
    DUMMY_VEHICLE_GUN           = 13,
};
 
  • Вау
  • Нравится
Реакции: bfix и kirieshki.

chapo

tg/inst: @moujeek
Всефорумный модератор
9,204
12,526
Описание: набор функций для работы с аризоновским цефом
Код:
Lua:
CEF = {};

---@param id number
---@param bs any
---@param printString boolean
---@return boolean status
---@return string event
---@return table data
---@return string json
---@return string fullString
function CEF.readIncomingPacket(id, bs, printString)
    if (id == 220) then
        raknetBitStreamIgnoreBits(bs, 8);
        if (raknetBitStreamReadInt8(bs) == 17) then
            raknetBitStreamIgnoreBits(bs, 32);
            local length = raknetBitStreamReadInt16(bs);
            local encoded = raknetBitStreamReadInt8(bs);
            local str = (encoded ~= 0) and raknetBitStreamDecodeString(bs, length + encoded) or
            raknetBitStreamReadString(bs, length);
            if (printString) then
                print(str);
            end
            if (not str:find('window%.executeEvent%(\'event%.(.+)\', `(.+)`%);')) then
                goto bad_packet
            end
            local event, json = str:match('window%.executeEvent%(\'event%.(.+)\', `(.+)`%);');
            -- print(event, json)
            return true, event, decodeJson(json)[1], json, str;
        end
    end
    ::bad_packet::
    return false, 'NONE', {}, '[]', '';
end

---@param id number
---@param bs any
---@param printString boolean
---@return boolean status
---@return string str
function CEF.readOutcomingPacket(id, bs, printString)
    if (id == 220) then
        local id = raknetBitStreamReadInt8(bs);
        local packettype = raknetBitStreamReadInt8(bs);
        local strlen = raknetBitStreamReadInt16(bs);
        local str = raknetBitStreamReadString(bs, strlen);
        if (packettype ~= 0 and packettype ~= 1 and #str > 2) then
            if (printString) then
                print('[SENT]', str);
            end
            return true, str;
        end
    end
    return false, 'NOT_220';
end

---@param str string
function CEF.send(str)
    local bs = raknetNewBitStream();
    raknetBitStreamWriteInt8(bs, 220);
    raknetBitStreamWriteInt8(bs, 18);
    raknetBitStreamWriteInt16(bs, #str);
    raknetBitStreamWriteString(bs, str);
    raknetBitStreamWriteInt32(bs, 0);
    raknetSendBitStream(bs);
    raknetDeleteBitStream(bs);
end

---@param code string
---@param encode boolean
function CEF.emulate(code, encode)
    local bs = raknetNewBitStream();
    raknetBitStreamWriteInt8(bs, 17);
    raknetBitStreamWriteInt32(bs, 0);
    raknetBitStreamWriteInt16(bs, #code);
    raknetBitStreamWriteInt8(bs, encode and 1 or 0);
    raknetBitStreamWriteString(bs, code);
    raknetEmulPacketReceiveBitStream(220, bs);
    raknetDeleteBitStream(bs);
end
 
Последнее редактирование:

chapo

tg/inst: @moujeek
Всефорумный модератор
9,204
12,526
Описание: получает список предметов Arizona RP. Для работы требуется ZZLib.
Код:
Lua:
---@return boolean Status
---@return table<number, {id: number, name: string, icon: string, acs_slot: number, type: number, active: number}> ItemsList
---@return string StatusText
function LoadArizonaItems()
    local FRONTEND_ZIP_PATH = getGameDirectory() .. '\\frontend.zip';
    local TEMP_FILE = getGameDirectory() .. '\\resource';
    local result = {};

    local zipStatus, zip = pcall(require, 'zzlib');
    if (not zipStatus) then
        return false, result, 'NO_ZZLIB';
    end
   
    local zipEntries = zip.get_zip_entries(FRONTEND_ZIP_PATH);
    if (not zip.is_file_does_exists("frontend\\svelte_js\\main.bundle.js", zipEntries)) then
        return false, result, 'JS_NOT_EXISTS';
    end

    if (not zip.unzip_entry(FRONTEND_ZIP_PATH, "frontend\\svelte_js\\main.bundle.js", TEMP_FILE)) then
        return false, result, 'UNZIP_FAILED';
    end

    local file = io.open(TEMP_FILE .. '\\main.bundle.js', 'r');
    if (not file) then
        return false, result, 'UNABLE_TO_OPEN_TEMP_FILE';
    end

    local itemsJson = file:read('*a'):match('var ITEMS=(.-);');
    file:close();

    if (not itemsJson) then
        return false, result, 'INVALID_JSON';
    end

    local list = decodeJson(itemsJson);
    if (not list or #list == 0) then
        return false, result, 'JSON_DECODE_FAILED';
    end

    for _, item in ipairs(list) do
        item.icon = 'https://cdn.azresources.cloud/projects/arizona-rp/assets/images/donate/' .. item.icon;
        result[item.id] = item;
    end

    return true, result, tostring(#list);
end
Пример использования:
Lua:
local encoding = require('encoding');
encoding.default = 'CP1251';
u8 = encoding.UTF8;

local ITEMS = {};

local status, list, msg = LoadArizonaItems();
print(status and 'Загружено' .. msg .. 'предметов!' or 'Ошибка, не удалось загрузить список предметов:' .. msg);
ITEMS = list;

local function findItemByName(name)
    for _, item in pairs(ITEMS) do
        if (item.name:find(u8(name))) then
            return item;
        end
    end
    return nil;
end

function main()
    while (not isSampAvailable()) do wait(0) end
    sampRegisterChatCommand('finditem', function(name)
        local item = findItemByName(name);
        if (not item) then
            return sampAddChatMessage('Предмет не найден!', 0xFFff0000);
        end
        sampAddChatMessage(('[Предмет найден] %s [%d], картинка: %s'):format(u8:decode(item.name), item.id, item.icon), -1);
    end);
    wait(-1);
end
Спасибо @CaJlaT за основу
 

CaJlaT

07.11.2024 14:55
Модератор
2,857
2,721
Описание: Чтение нового пакета отправки сообщения с предметами на аризоне (когда тегаешь предмет в чате через :itemID: или :slotID_INV:)
Пример использования:
Подмена команды:
function printChat(...) sampAddChatMessage(string.format('[{007882}%s{FFFFFF}]: %s', thisScript().name, table.concat({...}, ' ')), -1) end

addEventHandler('onSendPacket', function(id, bs)
    if id == 220 then
        raknetBitStreamIgnoreBits(bs, 8)
        local pId = raknetBitStreamReadInt8(bs)
        if pId == 210 then
            raknetBitStreamIgnoreBits(bs, 8)
            local count = raknetBitStreamReadInt8(bs)

            local parts = {}
            for i = 1, count do
                if raknetBitStreamGetNumberOfUnreadBits(bs) < 8 then break end
                local token = raknetBitStreamReadInt8(bs)
                if token == 0 or token == 3 then
                    if raknetBitStreamGetNumberOfUnreadBits(bs) < 16 then break end
                    local len = raknetBitStreamReadInt16(bs)
                    local s = (len > 0) and raknetBitStreamReadString(bs, len) or ""
                    parts[#parts+1] = s
                elseif token == 1 then
                    if raknetBitStreamGetNumberOfUnreadBits(bs) < 8+16 then break end
                    local invType = raknetBitStreamReadInt8(bs)
                    local slotIdx = raknetBitStreamReadInt16(bs)
                    parts[#parts+1] = string.format(":slot%d_%d:", slotIdx, invType)
                elseif token == 2 then
                    if raknetBitStreamGetNumberOfUnreadBits(bs) < 16 then break end
                    local itemId = raknetBitStreamReadInt16(bs)
                    parts[#parts+1] = string.format(":item%d:", itemId)
                else
                    print('UNKNOWN', token)
                    break
                end
            end
            local text = table.concat(parts)
            printChat(text)
            if text:find('^/fam') then
                printChat('Нашли сообщение в /fam, меняем на /b')
                sendArzChat(text:gsub('^/fam', '/b')) -- меняет /fam чат на /b чат
                return false
            end

        end
    end
end)
1758379173209.png

Код:
Чтение:
addEventHandler('onSendPacket', function(id, bs)
    if id == 220 then
        raknetBitStreamIgnoreBits(bs, 8)
        local pId = raknetBitStreamReadInt8(bs)
        if pId == 210 then
            raknetBitStreamIgnoreBits(bs, 8)
            local count = raknetBitStreamReadInt8(bs)

            local parts = {}
            for i = 1, count do
                if raknetBitStreamGetNumberOfUnreadBits(bs) < 8 then break end
                local token = raknetBitStreamReadInt8(bs)
                if token == 0 or token == 3 then
                    if raknetBitStreamGetNumberOfUnreadBits(bs) < 16 then break end
                    local len = raknetBitStreamReadInt16(bs)
                    local s = (len > 0) and raknetBitStreamReadString(bs, len) or ""
                    parts[#parts+1] = s
                elseif token == 1 then
                    if raknetBitStreamGetNumberOfUnreadBits(bs) < 8+16 then break end
                    local invType = raknetBitStreamReadInt8(bs)
                    local slotIdx = raknetBitStreamReadInt16(bs)
                    parts[#parts+1] = string.format(":slot%d_%d:", slotIdx, invType)
                elseif token == 2 then
                    if raknetBitStreamGetNumberOfUnreadBits(bs) < 16 then break end
                    local itemId = raknetBitStreamReadInt16(bs)
                    parts[#parts+1] = string.format(":item%d:", itemId)
                else
                    print('UNKNOWN', token)
                    break
                end
            end
            local text = table.concat(parts)

            --code
            
        end
    end
end)

Отправка:
local function tokenize(str)
    local t, i = {}, 1
    if str:sub(1,1) == "/" then
        local sp = str:find(" ")
        if sp then t[#t+1] = {token=3, str=str:sub(1, sp)}; i = sp+1 else t[#t+1] = {token=3, str=str}; return t end
    end
    while true do
        local i1,j1,id = str:find(":item(%d+):", i)
        local i2,j2,sl,iv = str:find(":slot(%d+)_(%d+):", i)
        if i1 and (not i2 or i1 < i2) then
            if i1 > i then t[#t+1] = {token=0, str=str:sub(i, i1-1)} end
            t[#t+1] = {token=2, id=tonumber(id)}; i = j1+1
        elseif i2 then
            if i2 > i then t[#t+1] = {token=0, str=str:sub(i, i2-1)} end
            t[#t+1] = {token=1, slot=tonumber(sl), inv=tonumber(iv)}; i = j2+1
        else
            if i <= #str then t[#t+1] = {token=0, str=str:sub(i)} end
            break
        end
    end
    return t
end

function sendArzChat(str)
    local tokens = tokenize(str)
    local bs = raknetNewBitStream()
    raknetBitStreamWriteInt8(bs, 220)
    raknetBitStreamWriteInt8(bs, 210)
    raknetBitStreamWriteInt8(bs, 0)
    raknetBitStreamWriteInt8(bs, #tokens)
    for _,v in ipairs(tokens) do
        raknetBitStreamWriteInt8(bs, v.token)
        if v.token == 0 or v.token == 3 then -- обычный текст
            raknetBitStreamWriteInt16(bs, #v.str)
            raknetBitStreamWriteString(bs, v.str)
        elseif v.token == 2 then -- :itemID:
            raknetBitStreamWriteInt16(bs, v.id)
        elseif v.token == 1 then -- :slotID_INV:
            raknetBitStreamWriteInt8(bs, v.inv)
            raknetBitStreamWriteInt16(bs, v.slot)
        end
    end
    raknetSendBitStream(bs)
    raknetDeleteBitStream(bs)
end
P.s: Скорее всего, этот пакет в ближайшем времени перестанет существовать, но пока он есть - пользуйтесь)
1758378155326.png