Lua WallHack

volodyapivo

Участник
Автор темы
32
26
Версия SA-MP
  1. Любая
Wallhack — это чит, который позволяет видеть игровые объекты и других игроков сквозь стены и другие препятствия.

Написал чисто для себя, когда был администратором на Arizona RP.
Выглядит очень приятно для глаза, не имеет багов, имеет приятное меню, в котором можно настроить цвета определенных групп скинов, например - 102, 103, 104 - фиолетового цвета, ибо скины банды Ballas.

Команда активации - /whadmin

На OBS и других захватах экрана работает нормально, виден, поэтому не является скрытным читом, лишь для удобства и красоты администрации, кому наскучил старый UWH.

1785770716838.png
1785770833743.png

ВИДЫ КОСТЕЙ
1785770739180.png

1785770763615.png

1785770778610.png

1785770805575.png

1785770818906.png

Зависимости:
mimgui
sampivents
ffi
memory
dkjson
iconv
 

Вложения

  • wh.lua
    30.5 KB · Просмотры: 92

volodyapivo

Участник
Автор темы
32
26
смари @Орк как он реализовал обводки
это ентити эффект от моего любимого великого гавнакодера @вайега52

памоему эта чит, нет? 🤪
ну типа ну как бы да ебать, я его в модификации отнес, потому что он больше подойдет не для читеров, а для администрации, ибо делал именно для них. он не обс не скрыт, ибо это имгуи вообще епт, у меня есть асишник через директх который рисует и обс не видит, но я его не хочу давать....
 
Последнее редактирование:

James Saula

Известный
114
26
сделай 3d боксы, у всех на луа почему-то кубы кривые, когда толщину линий выкручиваешь
 

Joce

Известный
95
33
сделай 3d боксы, у всех на луа почему-то кубы кривые, когда толщину линий выкручиваешь
ну на 1 промт в нейрнке к слову

Lua:
local imgui   = require 'mimgui'
local sampev  = require 'lib.samp.events'
local ffi     = require 'ffi'
local json    = require 'dkjson'
local iconv   = require 'iconv'
local utf8cp  = iconv.new('CP1251', 'UTF-8')
local function cp(s) return utf8cp:iconv(s) end
local sampLoaded = false

local math_min, math_max = math.min, math.max
local NEAR_PLANE_EPSILON = 1e-3

local function makeCharArray(tb)
    local arr = ffi.new('const char*['..#tb..']')
    for i = 1, #tb do
        arr[i-1] = tb[i]
    end
    return arr
end

-- ══════════════════════════════════════════════════════
--  CPed::GetBonePosition
-- ══════════════════════════════════════════════════════
ffi.cdef[[
    typedef struct { float x, y, z; } RwV3d;
    typedef void (__thiscall *tGetBonePos)(void*, RwV3d*, unsigned int, int);
]]
local _getBoneFn = ffi.cast('tGetBonePos', 0x5E4280)
local _boneOut   = ffi.new('RwV3d[1]')
local function bonePos(ptr, id)
    _getBoneFn(ffi.cast('void*', ptr), _boneOut, id, 0)
    return _boneOut[0].x, _boneOut[0].y, _boneOut[0].z
end

-- ══════════════════════════════════════════════════════
--  w2s & 3D Line Rendering with Near Plane Clipping
-- ══════════════════════════════════════════════════════
local _sw, _sh = getScreenResolution()

local function w2s(x, y, z)
    local sx, sy = convert3DCoordsToScreen(x, y, z)
    if not sx then return nil, nil end
    if sx < -_sw or sx > _sw*2 or sy < -_sh or sy > _sh*2 then
        return nil, nil
    end
    return sx, sy
end

local function isPedVisible(cx, cy, cz)
    return isPointOnScreen(cx, cy, cz, 1.0)
end

local function clipToNearPlane(fx, fy, fz, wf, bx, by, bz, wb)
    local dw = wf - wb
    if dw == 0 then return nil, nil end
    local t = math_max(0.0, math_min(1.0, (wf - NEAR_PLANE_EPSILON) / dw))
    local _, sx, sy = convert3DCoordsToScreenEx(
        fx + (bx - fx) * t,
        fy + (by - fy) * t,
        fz + (bz - fz) * t
    )
    return sx, sy
end

local function draw3DLine(dl, x1, y1, z1, x2, y2, z2, color, thickness)
    local _, sx1, sy1, sz1 = convert3DCoordsToScreenEx(x1, y1, z1)
    local _, sx2, sy2, sz2 = convert3DCoordsToScreenEx(x2, y2, z2)

    if sz1 <= 0 and sz2 <= 0 then return end

    if sz2 <= 0 then
        sx2, sy2 = clipToNearPlane(x1, y1, z1, sz1, x2, y2, z2, sz2)
    elseif sz1 <= 0 then
        sx1, sy1 = clipToNearPlane(x2, y2, z2, sz2, x1, y1, z1, sz1)
    end

    if not sx1 or not sx2 then return end

    dl:AddLine(imgui.ImVec2(sx1, sy1), imgui.ImVec2(sx2, sy2), color, thickness or 1.0)
end

-- ══════════════════════════════════════════════════════
--  Bone presets
-- ══════════════════════════════════════════════════════
local BONE_PRESETS = {
    {
        name  = 'Полный',
        ids   = {1,2,3,4,6,22,23,24,32,33,34,41,42,43,51,52,53},
        pairs = {
            {1,2},{2,3},{3,4},{4,6},
            {3,32},{32,33},{33,34},
            {3,22},{22,23},{23,24},
            {1,41},{41,42},{42,43},
            {1,51},{51,52},{52,53},
        }
    },
    {
        name  = 'Упрощённый',
        ids   = {1,4,6,23,33,42,52},
        pairs = {
            {1,4},{4,6},
            {4,33},{4,23},
            {1,42},{1,52},
        }
    },
    {
        name  = 'Только руки/ноги',
        ids   = {22,23,24,32,33,34,41,42,43,51,52,53},
        pairs = {
            {32,33},{33,34},
            {22,23},{23,24},
            {41,42},{42,43},
            {51,52},{52,53},
        }
    },
}

-- ══════════════════════════════════════════════════════
--  Config
-- ══════════════════════════════════════════════════════
local CFG_PATH = getWorkingDirectory() .. '\\config\\wh_config.json'
local cfg = {
    enabled     = true,
    show2DBox   = true,
    show3DBox   = false,
    showBoxFill = false,
    showDots    = true,
    showBones   = true,
    bonePreset  = 1,
    radius      = 500.0,
    skinGroups  = {
        { name='Grove',     ids='105,106,107',     color={0.0,  0.80, 0.27, 1.0} },
        { name='Ballas',    ids='102,103,104',     color={0.80, 0.27, 1.0,  1.0} },
        { name='Vagos',     ids='108,109,110',     color={1.0,  0.93, 0.0,  1.0} },
        { name='Aztecas',   ids='114,115,116',     color={0.0,  0.75, 1.0,  1.0} },
        { name='Rifa',      ids='173,174,175',     color={0.0,  0.85, 0.75, 1.0} },
        { name='DaNang',    ids='121,122,123',     color={0.27, 0.53, 1.0,  1.0} },
        { name='Triads',    ids='117,118,120',     color={0.13, 0.33, 1.0,  1.0} },
        { name='Mafia',     ids='111,112,113',     color={0.67, 0.67, 0.67, 1.0} },
        { name='VietMafia', ids='124,125,126,127', color={0.53, 0.67, 1.0,  1.0} },
        { name='Bikers',    ids='247,248',         color={1.0,  0.55, 0.13, 1.0} },
    }
}

local PREFIX = '{faa9ff}[WallHack]{ffffff} '
local AUTHOR = PREFIX .. 'Authors: {faa9ff}volodyapivo{ffffff} &.'
local function msg(text) sampAddChatMessage(PREFIX .. cp(text), 0xFFFFFF) end

local function saveConfig()
    local dir = getWorkingDirectory() .. '\\config'
    if not doesDirectoryExist(dir) then createDirectory(dir) end
    local f = io.open(CFG_PATH, 'w')
    if f then
        f:write(json.encode(cfg, {indent=true}))
        f:close()
        msg('{aaaaaa}Конфиг сохранён.')
    end
end

local function loadConfig()
    local f = io.open(CFG_PATH, 'r')
    if not f then return end
    local t = json.decode(f:read('*a')); f:close()
    if not t then return end
    for k,v in pairs(t) do cfg[k] = v end
end

-- ══════════════════════════════════════════════════════
--  Skin colour map
-- ══════════════════════════════════════════════════════
local skinColorMap = {}
local function rebuildColorMap()
    skinColorMap = {}
    for _, g in ipairs(cfg.skinGroups) do
        local c  = g.color
        local r  = math.floor(c[1]*255+0.5)
        local gv = math.floor(c[2]*255+0.5)
        local b  = math.floor(c[3]*255+0.5)
        local a  = math.floor(c[4]*255+0.5)
        local abgr = bit.bor(bit.lshift(a,24), bit.lshift(b,16), bit.lshift(gv,8), r)
        for idstr in (g.ids..','):gmatch('(%d+),') do
            local id = tonumber(idstr)
            if id then skinColorMap[id] = abgr end
        end
    end
end

local function skinColor(id) return skinColorMap[id] or 0xFFFFFFFF end

-- ══════════════════════════════════════════════════════
--  SAMP skin ID reader
-- ══════════════════════════════════════════════════════
local memory = require 'memory'
local function getSampSkinId(sampPlayerId)
    local ok, skin = pcall(sampGetPlayerSkin, sampPlayerId)
    if ok and skin and skin > 0 then return skin end
    local ok2, ptr = pcall(sampGetPlayerStructPtr, sampPlayerId)
    if ok2 and ptr and ptr ~= 0 then
        local ok3, v = pcall(memory.getint16, ptr + 0x12, false)
        if ok3 and v and v > 0 then return v end
    end
    return nil
end

local players = {}
local function getPlayerSkin(sampId, ped)
    local skin = getSampSkinId(sampId)
    if skin then return skin end
    return getCharModel(ped)
end

sampev.onPlayerStreamIn = function(id)
    local ok, ped = sampGetCharHandleBySampPlayerId(id)
    if ok then
        players[id] = { ped=ped, name=sampGetPlayerNickname(id), skin=getPlayerSkin(id, ped) }
    end
end
sampev.onPlayerStreamOut = function(id) players[id] = nil end

-- ══════════════════════════════════════════════════════
--  Colour helpers
-- ══════════════════════════════════════════════════════
local function v2(x,y)     return imgui.ImVec2(x,y) end
local function v4(r,g,b,a) return imgui.ImVec4(r,g,b,a) end
local function wa(col, a)
    return bit.bor(bit.band(col, 0x00FFFFFF), bit.lshift(a, 24))
end

local function lerpColor(c1, c2, t)
    local function ch(c,s) return bit.band(bit.rshift(c,s), 0xFF) end
    local function lp(a,b) return math.floor(a+(b-a)*t) end
    return bit.bor(
        bit.lshift(lp(ch(c1,24),ch(c2,24)),24),
        bit.lshift(lp(ch(c1,16),ch(c2,16)),16),
        bit.lshift(lp(ch(c1,8), ch(c2,8)), 8),
        lp(ch(c1,0),ch(c2,0))
    )
end

local function hpColor(pct)
    if pct > 0.5 then return lerpColor(0xFF00FFFF, 0xFF00FF00, (pct-0.5)*2)
    else              return lerpColor(0xFF0000FF, 0xFF00FFFF, pct*2) end
end

-- ══════════════════════════════════════════════════════
--  Skeleton
-- ══════════════════════════════════════════════════════
local function buildBoneCache(ptr, preset)
    local cache = {}
    for _, bid in ipairs(preset.ids) do
        local ok, x, y, z = pcall(bonePos, ptr, bid)
        if ok then
            local sx, sy = w2s(x, y, z)
            if sx then cache[bid] = {sx, sy} end
        end
    end
    return cache
end

local function drawSkeleton(dl, cache, col, boxH, preset)
    if not cfg.showBones then return end

    local s     = math.max(0.0, math.min(1.0, boxH / 100.0))
    local lineW = 0.8 + s * 1.4
    local glowW = lineW + 2.0
    local dotR  = math.max(0.8, 0.8 + s * 2.2)
    local glow  = wa(col, 0x35)
    local main  = wa(col, 0xDD)

    for _, p in ipairs(preset.pairs) do
        local a = cache[p[1]]
        local b = cache[p[2]]
        if a and b then
            dl:AddLine(v2(a[1],a[2]), v2(b[1],b[2]), glow, glowW)
            dl:AddLine(v2(a[1],a[2]), v2(b[1],b[2]), main, lineW)
        end
    end

    if cfg.showDots then
        local drawn = {}
        for _, p in ipairs(preset.pairs) do
            for _, bid in ipairs(p) do
                if not drawn[bid] then
                    drawn[bid] = true
                    local pt = cache[bid]
                    if pt then
                        dl:AddCircleFilled(v2(pt[1],pt[2]), dotR,     wa(col,0xFF))
                        dl:AddCircle(      v2(pt[1],pt[2]), dotR+0.5, wa(0xFF000000,0xAA), 12, 1.0)
                    end
                end
            end
        end
    end
end

-- ══════════════════════════════════════════════════════
--  3D Bounding Box Logic
-- ══════════════════════════════════════════════════════
local function draw3DBox(dl, ped, col)
    local heading = getCharHeading(ped)
    local rad = math.rad(heading)
    
    local cx, cy, cz = getCharCoordinates(ped)
    
    local minX, maxX = -0.4, 0.4
    local minY, maxY = -0.4, 0.4
    local minZ, maxZ = -0.9, 1.05

    local localCorners = {
        {minX, minY, minZ}, {maxX, minY, minZ}, {maxX, maxY, minZ}, {minX, maxY, minZ},
        {minX, minY, maxZ}, {maxX, minY, maxZ}, {maxX, maxY, maxZ}, {minX, maxY, maxZ}
    }

    local cosH, sinH = math.cos(rad), math.sin(rad)
    local worldCorners = {}

    for i, c in ipairs(localCorners) do
        local rx = c[1] * cosH - c[2] * sinH
        local ry = c[1] * sinH + c[2] * cosH
        worldCorners[i] = {cx + rx, cy + ry, cz + c[3]}
    end

    local edges = {
        {1,2}, {2,3}, {3,4}, {4,1},
        {5,6}, {6,7}, {7,8}, {8,5},
        {1,5}, {2,6}, {3,7}, {4,8}
    }

    local mainCol = wa(col, 0xFF)
    local glowCol = wa(col, 0x33)

    for _, e in ipairs(edges) do
        local p1 = worldCorners[e[1]]
        local p2 = worldCorners[e[2]]
        draw3DLine(dl, p1[1], p1[2], p1[3], p2[1], p2[2], p2[3], glowCol, 3.0)
        draw3DLine(dl, p1[1], p1[2], p1[3], p2[1], p2[2], p2[3], mainCol, 1.2)
    end
end

-- ══════════════════════════════════════════════════════
--  ESP
-- ══════════════════════════════════════════════════════
local function drawESP(dl, p, sampId)
    local ped = p.ped
    if not doesCharExist(ped) then return end
    local ok0, cx, cy, cz = pcall(getCharCoordinates, ped)
    if not ok0 then return end

    if not isPedVisible(cx, cy, cz) then return end

    local ptr = getCharPointer(ped)
    local headX, headY, headZ = cx, cy, cz + 0.9

    local preset = BONE_PRESETS[cfg.bonePreset] or BONE_PRESETS[1]
    local cache  = {}
    if ptr and ptr ~= 0 then
        cache = buildBoneCache(ptr, preset)
        local hb = cache[6]
        if hb then
            local ok, hx, hy, hz = pcall(bonePos, ptr, 6)
            if ok then headX, headY, headZ = hx, hy, hz + 0.12 end
        end
    end

    local hsx, hsy = w2s(headX, headY, headZ)
    local fsx, fsy = w2s(cx, cy, cz - 0.85)
    if not hsx or not fsx then return end
    if hsy >= fsy then return end

    local h  = fsy - hsy
    local w  = h * 0.32
    local x1, x2 = hsx - w, hsx + w
    local y1, y2 = hsy, fsy

    local col  = skinColor(p.skin)
    local cMid = wa(col, 0xBB)
    local cFul = wa(col, 0xFF)

    -- ── 3D Box Render ──
    if cfg.show3DBox then
        draw3DBox(dl, ped, col)
    end

    -- ── 2D Box Render ──
    if cfg.show2DBox then
        if cfg.showBoxFill then
            dl:AddRectFilled(v2(x1,y1), v2(x2,y2), wa(col, 0x22))
        end
        dl:AddRectFilled(v2(x1-2,y1-2), v2(x2+2,y2+2), wa(col, 0x18), 2)
        dl:AddRect(v2(x1-1,y1-1), v2(x2+1,y2+1), wa(0xFF000000,0xAA), 0, 15, 2.5)
        dl:AddRect(v2(x1,y1), v2(x2,y2), cMid, 0, 15, 1.0)

        local cl = math.max(4, h * 0.20)
        local bw = 2.0
        dl:AddLine(v2(x1,y1), v2(x1+cl,y1), cFul, bw)
        dl:AddLine(v2(x1,y1), v2(x1,y1+cl), cFul, bw)
        dl:AddLine(v2(x2,y1), v2(x2-cl,y1), cFul, bw)
        dl:AddLine(v2(x2,y1), v2(x2,y1+cl), cFul, bw)
        dl:AddLine(v2(x1,y2), v2(x1+cl,y2), cFul, bw)
        dl:AddLine(v2(x1,y2), v2(x1,y2-cl), cFul, bw)
        dl:AddLine(v2(x2,y2), v2(x2-cl,y2), cFul, bw)
        dl:AddLine(v2(x2,y2), v2(x2,y2-cl), cFul, bw)
    end

    -- ── Bars (HP & Armor) ──
    if cfg.show2DBox or cfg.show3DBox then
        local hp = nil
        if sampId then
            local ok, v = pcall(sampGetPlayerHealth, sampId)
            if ok and v then hp = v end
        end
        if not hp then
            local ok, v = pcall(getCharHealth, ped)
            hp = (ok and v) or 100
        end
        hp = math.max(0, math.min(100, hp))
        local pct = hp / 100
        local bh  = h * pct

        local barW = math.max(5, w * 0.22)
        local hbx2 = x1 - 4
        local hbx1 = hbx2 - barW
        dl:AddRectFilled(v2(hbx1,y1),    v2(hbx2,y2),    wa(0xFF000000,0xCC), 1)
        dl:AddRectFilled(v2(hbx1,y2-bh), v2(hbx2,y2),    hpColor(pct),        1)
        dl:AddRect(      v2(hbx1,y1),    v2(hbx2,y2),    wa(0xFF000000,0x88), 1, 15, 0.8)

        local ar = nil
        if sampId then
            local ok, v = pcall(sampGetPlayerArmor, sampId)
            if ok and v then ar = v end
        end
        if not ar then
            local ok, v = pcall(getCharArmour, ped)
            ar = (ok and v) or 0
        end
        ar = math.max(0, math.min(100, ar))
        if ar > 0 then
            local apct = ar / 100
            local abh  = h * apct
            local abx2 = hbx1 - 3
            local abx1 = abx2 - barW
            dl:AddRectFilled(v2(abx1,y1),     v2(abx2,y2), wa(0xFF000000,0xCC), 1)
            dl:AddRectFilled(v2(abx1,y2-abh), v2(abx2,y2), wa(0xFFFFAA33,0xFF), 1)
            dl:AddRect(      v2(abx1,y1),     v2(abx2,y2), wa(0xFF000000,0x88), 1, 15, 0.8)
        end
    end

    -- ── Skeleton ──
    drawSkeleton(dl, cache, col, h, preset)
end

-- ══════════════════════════════════════════════════════
--  ImGui menu
-- ══════════════════════════════════════════════════════
local menuOpen    = imgui.new.bool(false)
local menuWasOpen = false
local groupColorVec = {}
local function syncGroupColorVecs()
    groupColorVec = {}
    for i, g in ipairs(cfg.skinGroups) do
        local c = g.color
        groupColorVec[i] = imgui.new.float[4](c[1], c[2], c[3], c[4])
    end
end

local newGroupName  = imgui.new.char[64]('')
local newGroupIds   = imgui.new.char[256]('')
local newGroupColor = imgui.new.float[4](1, 1, 1, 1)

local chkEnabled    = imgui.new.bool(true)
local chk2DBox      = imgui.new.bool(true)
local chk3DBox      = imgui.new.bool(false)
local chkBoxFill    = imgui.new.bool(false)
local chkDots       = imgui.new.bool(true)
local chkBones      = imgui.new.bool(true)
local sldRadius     = imgui.new.float(500)

local function syncCheckboxes()
    chkEnabled[0] = cfg.enabled
    chk2DBox[0]   = cfg.show2DBox
    chk3DBox[0]   = cfg.show3DBox
    chkBoxFill[0] = cfg.showBoxFill
    chkDots[0]    = cfg.showDots
    chkBones[0]   = cfg.showBones
    sldRadius[0]  = cfg.radius
end

local function applyStyle()
    local s = imgui.GetStyle()
    s.WindowRounding = 6; s.FrameRounding = 4
    s.ScrollbarRounding = 4; s.GrabRounding = 4
    s.WindowBorderSize = 1; s.FrameBorderSize = 0
    local c = s.Colors
    local function set(i,r,g,b,a) c[i] = v4(r,g,b,a) end
    set(imgui.Col.WindowBg,        0.08,0.05,0.12,0.97)
    set(imgui.Col.TitleBg,         0.12,0.07,0.20,1.0)
    set(imgui.Col.TitleBgActive,   0.18,0.10,0.30,1.0)
    set(imgui.Col.FrameBg,         0.15,0.09,0.22,1.0)
    set(imgui.Col.FrameBgHovered,  0.22,0.13,0.32,1.0)
    set(imgui.Col.FrameBgActive,   0.28,0.17,0.40,1.0)
    set(imgui.Col.Button,          0.25,0.14,0.38,1.0)
    set(imgui.Col.ButtonHovered,   0.35,0.20,0.52,1.0)
    set(imgui.Col.ButtonActive,    0.45,0.27,0.65,1.0)
    set(imgui.Col.Header,          0.22,0.13,0.34,1.0)
    set(imgui.Col.HeaderHovered,   0.30,0.18,0.46,1.0)
    set(imgui.Col.HeaderActive,    0.38,0.23,0.58,1.0)
    set(imgui.Col.CheckMark,       0.75,0.45,1.00,1.0)
    set(imgui.Col.SliderGrab,      0.60,0.35,0.90,1.0)
    set(imgui.Col.SliderGrabActive,0.75,0.45,1.00,1.0)
    set(imgui.Col.Separator,       0.30,0.18,0.45,1.0)
    set(imgui.Col.ScrollbarBg,     0.06,0.04,0.10,1.0)
    set(imgui.Col.ScrollbarGrab,   0.30,0.18,0.45,1.0)
    set(imgui.Col.Text,            0.92,0.88,1.00,1.0)
    set(imgui.Col.Border,          0.35,0.20,0.55,0.6)
    set(imgui.Col.PopupBg,         0.10,0.06,0.16,0.97)
end

-- ══════════════════════════════════════════════════════
--  Render
-- ══════════════════════════════════════════════════════
local styleApplied = false

imgui.OnFrame(function() return true end, function(self)
    self.HideCursor = not menuOpen[0]

    if not sampLoaded then return end

    if not styleApplied then
        applyStyle()
        styleApplied = true
    end

    if menuWasOpen and not menuOpen[0] then
        for i, g in ipairs(cfg.skinGroups) do
            local v = groupColorVec[i]
            if v then g.color = {v[0],v[1],v[2],v[3]} end
        end
        rebuildColorMap()
        saveConfig()
    end
    menuWasOpen = menuOpen[0]

    -- ── ESP ──
    if cfg.enabled and isSampAvailable() then
        local dl = imgui.GetBackgroundDrawList()
        local ok0, mx, my, mz = pcall(getCharCoordinates, playerPed)
        if ok0 then
            for id, p in pairs(players) do
                if not doesCharExist(p.ped) then
                    players[id] = nil
                else
                    local ok, x, y, z = pcall(getCharCoordinates, p.ped)
                    if ok then
                        local gtaSkin = getCharModel(p.ped)
                        if gtaSkin > 311 then
                            local s = getSampSkinId(id)
                            p.skin = (s and s > 0) and s or gtaSkin
                        else
                            p.skin = gtaSkin
                        end
                        local dist = (x-mx)^2 + (y-my)^2 + (z-mz)^2
                        if dist <= cfg.radius * cfg.radius then
                            drawESP(dl, p, id)
                        end
                    end
                end
            end
        end
    end

    -- ── Menu ──
    if not menuOpen[0] then return end

    imgui.SetNextWindowSize(v2(480, 540), imgui.Cond.FirstUseEver)
    local sw, sh = getScreenResolution()
    imgui.SetNextWindowPos(v2(sw/2, sh/2), imgui.Cond.Appearing, v2(0.5, 0.5))

    if imgui.Begin('WH Admin', menuOpen, imgui.WindowFlags.NoCollapse) then
        if imgui.BeginTabBar('tabs') then

            -- ── Основное ──
            if imgui.BeginTabItem('Основное') then
                imgui.Spacing()
                if imgui.Checkbox('Включено', chkEnabled) then
                    cfg.enabled = chkEnabled[0]
                end
                imgui.Separator()
                
                if imgui.Checkbox('Включить 2D Box', chk2DBox) then
                    cfg.show2DBox = chk2DBox[0]
                end

                if cfg.show2DBox then
                    imgui.Indent()
                    if imgui.Checkbox('Заливка 2D Box', chkBoxFill) then
                        cfg.showBoxFill = chkBoxFill[0]
                    end
                    imgui.Unindent()
                end

                if imgui.Checkbox('Включить 3D Box', chk3DBox) then
                    cfg.show3DBox = chk3DBox[0]
                end

                imgui.Separator()
                if imgui.Checkbox('Включить точки на суставы', chkDots) then
                    cfg.showDots = chkDots[0]
                end
                if imgui.Checkbox('Включить Кости', chkBones) then
                    cfg.showBones = chkBones[0]
                end
                imgui.Spacing()
                imgui.Text('Вид костей:')
                for i, preset in ipairs(BONE_PRESETS) do
                    local selected = (cfg.bonePreset == i)
                    if imgui.Selectable(preset.name, selected) then
                        cfg.bonePreset = i
                    end
                end
                imgui.Separator()
                imgui.Text('Радиус (м):')
                imgui.SetNextItemWidth(200)
                if imgui.SliderFloat('##radius', sldRadius, 50, 500) then
                    cfg.radius = sldRadius[0]
                end
                imgui.EndTabItem()
            end

            if imgui.BeginTabItem('Цвета скинов') then
                imgui.Spacing()
                imgui.TextDisabled('Редактируй цвет и ID скинов для каждой группы.')
                imgui.Separator()
                imgui.BeginChild('##skinlist', v2(0, 300), false)

                local toRemove = nil
                for i, g in ipairs(cfg.skinGroups) do
                    local v = groupColorVec[i]
                    if v then
                        imgui.ColorEdit4('##col'..i, v,
                            imgui.ColorEditFlags.NoInputs + imgui.ColorEditFlags.NoLabel)
                        imgui.SameLine()
                    end
                    imgui.Text(g.name)
                    imgui.SameLine()
                    local idsBuf = imgui.new.char[256](g.ids)
                    imgui.SetNextItemWidth(150)
                    if imgui.InputText('##ids'..i, idsBuf, 256) then
                        g.ids = ffi.string(idsBuf)
                    end
                    imgui.SameLine()
                    if imgui.SmallButton('X##'..i) then toRemove = i end
                end

                imgui.EndChild()

                if toRemove then
                    table.remove(cfg.skinGroups, toRemove)
                    syncGroupColorVecs()
                end

                imgui.Separator()
                imgui.Text('Добавить группу:')
                imgui.SetNextItemWidth(90)
                imgui.InputText('##newname', newGroupName, 64)
                imgui.SameLine()
                imgui.SetNextItemWidth(150)
                imgui.InputText('##newids', newGroupIds, 256)
                imgui.SameLine()
                imgui.ColorEdit4('##newcol', newGroupColor,
                    imgui.ColorEditFlags.NoInputs + imgui.ColorEditFlags.NoLabel)
                imgui.SameLine()
                if imgui.Button('Добавить') then
                    local name = ffi.string(newGroupName)
                    local ids  = ffi.string(newGroupIds)
                    if name ~= '' and ids ~= '' then
                        table.insert(cfg.skinGroups, {
                            name  = name, ids = ids,
                            color = {newGroupColor[0],newGroupColor[1],
                                     newGroupColor[2],newGroupColor[3]},
                        })
                        syncGroupColorVecs()
                        imgui.StrCopy(newGroupName, '')
                        imgui.StrCopy(newGroupIds,  '')
                    end
                end
                imgui.EndTabItem()
            end

            imgui.EndTabBar()
        end
    end
    imgui.End()
end)

-- ══════════════════════════════════════════════════════
--  Main
-- ══════════════════════════════════════════════════════
function main()
    loadConfig()
    syncGroupColorVecs()
    syncCheckboxes()
    rebuildColorMap()

    while not isSampAvailable() do wait(1000) end
    msg('{aaaaaa}Конфиг загружен.')
    sampLoaded = true
    sampAddChatMessage(PREFIX .. cp('loaded  {aaaaaa}/whadmin'), 0xFFFFFF)
    sampAddChatMessage(AUTHOR, 0xFFFFFF)

    sampRegisterChatCommand('whadmin', function()
        menuOpen[0] = not menuOpen[0]
        if menuOpen[0] then
            syncCheckboxes()
            msg('Меню открыто.')
        else
            msg('Меню закрыто.')
        end
    end)

    sampRegisterChatCommand('checkskin', function(args)
        local targetId = tonumber(args)
        if not targetId then
            local ok0, mx, my, mz = pcall(getCharCoordinates, playerPed)
            for id, p in pairs(players) do
                if doesCharExist(p.ped) then
                    local ok, x, y, z = pcall(getCharCoordinates, p.ped)
                    local dist = ok and math.sqrt((x-mx)^2+(y-my)^2+(z-mz)^2) or 0
                    local gtaSkin  = getCharModel(p.ped)
                    local sampSkin = getSampSkinId(id)
                    local ptr      = getCharPointer(p.ped)
                    local memSkin  = 0
                    if ptr and ptr ~= 0 then
                        local ok2, v = pcall(memory.getint16, ptr + 0x22, false)
                        if ok2 then memSkin = v end
                    end
                    print(string.format('[checkskin] id=%d name=%s gta=%d samp=%s mem=%d dist=%.0fm',
                        id, p.name, gtaSkin, tostring(sampSkin), memSkin, dist))
                end
            end
        else
            local p = players[targetId]
            if not p or not doesCharExist(p.ped) then
                print('[checkskin] player '..targetId..' not found')
                return
            end
            local gtaSkin  = getCharModel(p.ped)
            local sampSkin = getSampSkinId(targetId)
            local ptr      = getCharPointer(p.ped)
            print(string.format('[checkskin] id=%d name=%s', targetId, p.name))
            print('  getCharModel   = '..gtaSkin)
            print('  getSampSkinId = '..tostring(sampSkin))
            local effectiveSkin = sampSkin or gtaSkin
            print('  effective skin used for color = '..tostring(effectiveSkin))
            print('  color in map = '..tostring(skinColorMap[effectiveSkin]))
            print('  >> /addskin '..tostring(effectiveSkin)..' GroupName  to add to a group')
            if ptr and ptr ~= 0 then
                for _, off in ipairs({0x20, 0x22, 0x24, 0x26, 0x28, 0x2A}) do
                    local ok, v = pcall(memory.getint16, ptr + off, false)
                    print(string.format('  ptr+0x%X = %s', off, ok and tostring(v) or 'err'))
                end
            end
        end
    end)

    sampRegisterChatCommand('addskin', function(args)
        local skinId, groupName = args:match('(%d+)%s+(.*)')
        if not skinId then
            sampAddChatMessage(PREFIX .. '/addskin {faa9ff}<skinId> <groupName>', 0xFFFFFF)
            return
        end
        skinId = tonumber(skinId)
        for _, g in ipairs(cfg.skinGroups) do
            if g.name == groupName then
                g.ids = g.ids .. ',' .. skinId
                rebuildColorMap()
                syncGroupColorVecs()
                saveConfig()
                msg(string.format('skin {faa9ff}%d{ffffff} добавлен в группу {faa9ff}%s', skinId, groupName))
                return
            end
        end
        table.insert(cfg.skinGroups, {
            name  = groupName,
            ids   = tostring(skinId),
            color = {1.0, 1.0, 1.0, 1.0},
        })
        rebuildColorMap()
        syncGroupColorVecs()
        saveConfig()
        msg(string.format('новая группа {faa9ff}%s{ffffff} создана со скином {faa9ff}%d', groupName, skinId))
    end)

    lua_thread.create(function()
        while true do
            wait(500)
            for id = 0, 1000 do
                if sampIsPlayerConnected(id) then
                    local ok, ped = sampGetCharHandleBySampPlayerId(id)
                    if ok and doesCharExist(ped) and ped ~= playerPed then
                        local skin = getPlayerSkin(id, ped)
                        if not players[id] then
                            players[id] = { ped=ped, name=sampGetPlayerNickname(id), skin=skin }
                        else
                            players[id].ped  = ped
                            players[id].skin = skin
                        end
                    end
                else
                    players[id] = nil
                end
            end
        end
    end)

    while true do wait(1000) end
end
 

volodyapivo

Участник
Автор темы
32
26
ну на 1 промт в нейрнке к слову

Lua:
local imgui   = require 'mimgui'
local sampev  = require 'lib.samp.events'
local ffi     = require 'ffi'
local json    = require 'dkjson'
local iconv   = require 'iconv'
local utf8cp  = iconv.new('CP1251', 'UTF-8')
local function cp(s) return utf8cp:iconv(s) end
local sampLoaded = false

local math_min, math_max = math.min, math.max
local NEAR_PLANE_EPSILON = 1e-3

local function makeCharArray(tb)
    local arr = ffi.new('const char*['..#tb..']')
    for i = 1, #tb do
        arr[i-1] = tb[i]
    end
    return arr
end

-- ══════════════════════════════════════════════════════
--  CPed::GetBonePosition
-- ══════════════════════════════════════════════════════
ffi.cdef[[
    typedef struct { float x, y, z; } RwV3d;
    typedef void (__thiscall *tGetBonePos)(void*, RwV3d*, unsigned int, int);
]]
local _getBoneFn = ffi.cast('tGetBonePos', 0x5E4280)
local _boneOut   = ffi.new('RwV3d[1]')
local function bonePos(ptr, id)
    _getBoneFn(ffi.cast('void*', ptr), _boneOut, id, 0)
    return _boneOut[0].x, _boneOut[0].y, _boneOut[0].z
end

-- ══════════════════════════════════════════════════════
--  w2s & 3D Line Rendering with Near Plane Clipping
-- ══════════════════════════════════════════════════════
local _sw, _sh = getScreenResolution()

local function w2s(x, y, z)
    local sx, sy = convert3DCoordsToScreen(x, y, z)
    if not sx then return nil, nil end
    if sx < -_sw or sx > _sw*2 or sy < -_sh or sy > _sh*2 then
        return nil, nil
    end
    return sx, sy
end

local function isPedVisible(cx, cy, cz)
    return isPointOnScreen(cx, cy, cz, 1.0)
end

local function clipToNearPlane(fx, fy, fz, wf, bx, by, bz, wb)
    local dw = wf - wb
    if dw == 0 then return nil, nil end
    local t = math_max(0.0, math_min(1.0, (wf - NEAR_PLANE_EPSILON) / dw))
    local _, sx, sy = convert3DCoordsToScreenEx(
        fx + (bx - fx) * t,
        fy + (by - fy) * t,
        fz + (bz - fz) * t
    )
    return sx, sy
end

local function draw3DLine(dl, x1, y1, z1, x2, y2, z2, color, thickness)
    local _, sx1, sy1, sz1 = convert3DCoordsToScreenEx(x1, y1, z1)
    local _, sx2, sy2, sz2 = convert3DCoordsToScreenEx(x2, y2, z2)

    if sz1 <= 0 and sz2 <= 0 then return end

    if sz2 <= 0 then
        sx2, sy2 = clipToNearPlane(x1, y1, z1, sz1, x2, y2, z2, sz2)
    elseif sz1 <= 0 then
        sx1, sy1 = clipToNearPlane(x2, y2, z2, sz2, x1, y1, z1, sz1)
    end

    if not sx1 or not sx2 then return end

    dl:AddLine(imgui.ImVec2(sx1, sy1), imgui.ImVec2(sx2, sy2), color, thickness or 1.0)
end

-- ══════════════════════════════════════════════════════
--  Bone presets
-- ══════════════════════════════════════════════════════
local BONE_PRESETS = {
    {
        name  = 'Полный',
        ids   = {1,2,3,4,6,22,23,24,32,33,34,41,42,43,51,52,53},
        pairs = {
            {1,2},{2,3},{3,4},{4,6},
            {3,32},{32,33},{33,34},
            {3,22},{22,23},{23,24},
            {1,41},{41,42},{42,43},
            {1,51},{51,52},{52,53},
        }
    },
    {
        name  = 'Упрощённый',
        ids   = {1,4,6,23,33,42,52},
        pairs = {
            {1,4},{4,6},
            {4,33},{4,23},
            {1,42},{1,52},
        }
    },
    {
        name  = 'Только руки/ноги',
        ids   = {22,23,24,32,33,34,41,42,43,51,52,53},
        pairs = {
            {32,33},{33,34},
            {22,23},{23,24},
            {41,42},{42,43},
            {51,52},{52,53},
        }
    },
}

-- ══════════════════════════════════════════════════════
--  Config
-- ══════════════════════════════════════════════════════
local CFG_PATH = getWorkingDirectory() .. '\\config\\wh_config.json'
local cfg = {
    enabled     = true,
    show2DBox   = true,
    show3DBox   = false,
    showBoxFill = false,
    showDots    = true,
    showBones   = true,
    bonePreset  = 1,
    radius      = 500.0,
    skinGroups  = {
        { name='Grove',     ids='105,106,107',     color={0.0,  0.80, 0.27, 1.0} },
        { name='Ballas',    ids='102,103,104',     color={0.80, 0.27, 1.0,  1.0} },
        { name='Vagos',     ids='108,109,110',     color={1.0,  0.93, 0.0,  1.0} },
        { name='Aztecas',   ids='114,115,116',     color={0.0,  0.75, 1.0,  1.0} },
        { name='Rifa',      ids='173,174,175',     color={0.0,  0.85, 0.75, 1.0} },
        { name='DaNang',    ids='121,122,123',     color={0.27, 0.53, 1.0,  1.0} },
        { name='Triads',    ids='117,118,120',     color={0.13, 0.33, 1.0,  1.0} },
        { name='Mafia',     ids='111,112,113',     color={0.67, 0.67, 0.67, 1.0} },
        { name='VietMafia', ids='124,125,126,127', color={0.53, 0.67, 1.0,  1.0} },
        { name='Bikers',    ids='247,248',         color={1.0,  0.55, 0.13, 1.0} },
    }
}

local PREFIX = '{faa9ff}[WallHack]{ffffff} '
local AUTHOR = PREFIX .. 'Authors: {faa9ff}volodyapivo{ffffff} &.'
local function msg(text) sampAddChatMessage(PREFIX .. cp(text), 0xFFFFFF) end

local function saveConfig()
    local dir = getWorkingDirectory() .. '\\config'
    if not doesDirectoryExist(dir) then createDirectory(dir) end
    local f = io.open(CFG_PATH, 'w')
    if f then
        f:write(json.encode(cfg, {indent=true}))
        f:close()
        msg('{aaaaaa}Конфиг сохранён.')
    end
end

local function loadConfig()
    local f = io.open(CFG_PATH, 'r')
    if not f then return end
    local t = json.decode(f:read('*a')); f:close()
    if not t then return end
    for k,v in pairs(t) do cfg[k] = v end
end

-- ══════════════════════════════════════════════════════
--  Skin colour map
-- ══════════════════════════════════════════════════════
local skinColorMap = {}
local function rebuildColorMap()
    skinColorMap = {}
    for _, g in ipairs(cfg.skinGroups) do
        local c  = g.color
        local r  = math.floor(c[1]*255+0.5)
        local gv = math.floor(c[2]*255+0.5)
        local b  = math.floor(c[3]*255+0.5)
        local a  = math.floor(c[4]*255+0.5)
        local abgr = bit.bor(bit.lshift(a,24), bit.lshift(b,16), bit.lshift(gv,8), r)
        for idstr in (g.ids..','):gmatch('(%d+),') do
            local id = tonumber(idstr)
            if id then skinColorMap[id] = abgr end
        end
    end
end

local function skinColor(id) return skinColorMap[id] or 0xFFFFFFFF end

-- ══════════════════════════════════════════════════════
--  SAMP skin ID reader
-- ══════════════════════════════════════════════════════
local memory = require 'memory'
local function getSampSkinId(sampPlayerId)
    local ok, skin = pcall(sampGetPlayerSkin, sampPlayerId)
    if ok and skin and skin > 0 then return skin end
    local ok2, ptr = pcall(sampGetPlayerStructPtr, sampPlayerId)
    if ok2 and ptr and ptr ~= 0 then
        local ok3, v = pcall(memory.getint16, ptr + 0x12, false)
        if ok3 and v and v > 0 then return v end
    end
    return nil
end

local players = {}
local function getPlayerSkin(sampId, ped)
    local skin = getSampSkinId(sampId)
    if skin then return skin end
    return getCharModel(ped)
end

sampev.onPlayerStreamIn = function(id)
    local ok, ped = sampGetCharHandleBySampPlayerId(id)
    if ok then
        players[id] = { ped=ped, name=sampGetPlayerNickname(id), skin=getPlayerSkin(id, ped) }
    end
end
sampev.onPlayerStreamOut = function(id) players[id] = nil end

-- ══════════════════════════════════════════════════════
--  Colour helpers
-- ══════════════════════════════════════════════════════
local function v2(x,y)     return imgui.ImVec2(x,y) end
local function v4(r,g,b,a) return imgui.ImVec4(r,g,b,a) end
local function wa(col, a)
    return bit.bor(bit.band(col, 0x00FFFFFF), bit.lshift(a, 24))
end

local function lerpColor(c1, c2, t)
    local function ch(c,s) return bit.band(bit.rshift(c,s), 0xFF) end
    local function lp(a,b) return math.floor(a+(b-a)*t) end
    return bit.bor(
        bit.lshift(lp(ch(c1,24),ch(c2,24)),24),
        bit.lshift(lp(ch(c1,16),ch(c2,16)),16),
        bit.lshift(lp(ch(c1,8), ch(c2,8)), 8),
        lp(ch(c1,0),ch(c2,0))
    )
end

local function hpColor(pct)
    if pct > 0.5 then return lerpColor(0xFF00FFFF, 0xFF00FF00, (pct-0.5)*2)
    else              return lerpColor(0xFF0000FF, 0xFF00FFFF, pct*2) end
end

-- ══════════════════════════════════════════════════════
--  Skeleton
-- ══════════════════════════════════════════════════════
local function buildBoneCache(ptr, preset)
    local cache = {}
    for _, bid in ipairs(preset.ids) do
        local ok, x, y, z = pcall(bonePos, ptr, bid)
        if ok then
            local sx, sy = w2s(x, y, z)
            if sx then cache[bid] = {sx, sy} end
        end
    end
    return cache
end

local function drawSkeleton(dl, cache, col, boxH, preset)
    if not cfg.showBones then return end

    local s     = math.max(0.0, math.min(1.0, boxH / 100.0))
    local lineW = 0.8 + s * 1.4
    local glowW = lineW + 2.0
    local dotR  = math.max(0.8, 0.8 + s * 2.2)
    local glow  = wa(col, 0x35)
    local main  = wa(col, 0xDD)

    for _, p in ipairs(preset.pairs) do
        local a = cache[p[1]]
        local b = cache[p[2]]
        if a and b then
            dl:AddLine(v2(a[1],a[2]), v2(b[1],b[2]), glow, glowW)
            dl:AddLine(v2(a[1],a[2]), v2(b[1],b[2]), main, lineW)
        end
    end

    if cfg.showDots then
        local drawn = {}
        for _, p in ipairs(preset.pairs) do
            for _, bid in ipairs(p) do
                if not drawn[bid] then
                    drawn[bid] = true
                    local pt = cache[bid]
                    if pt then
                        dl:AddCircleFilled(v2(pt[1],pt[2]), dotR,     wa(col,0xFF))
                        dl:AddCircle(      v2(pt[1],pt[2]), dotR+0.5, wa(0xFF000000,0xAA), 12, 1.0)
                    end
                end
            end
        end
    end
end

-- ══════════════════════════════════════════════════════
--  3D Bounding Box Logic
-- ══════════════════════════════════════════════════════
local function draw3DBox(dl, ped, col)
    local heading = getCharHeading(ped)
    local rad = math.rad(heading)
   
    local cx, cy, cz = getCharCoordinates(ped)
   
    local minX, maxX = -0.4, 0.4
    local minY, maxY = -0.4, 0.4
    local minZ, maxZ = -0.9, 1.05

    local localCorners = {
        {minX, minY, minZ}, {maxX, minY, minZ}, {maxX, maxY, minZ}, {minX, maxY, minZ},
        {minX, minY, maxZ}, {maxX, minY, maxZ}, {maxX, maxY, maxZ}, {minX, maxY, maxZ}
    }

    local cosH, sinH = math.cos(rad), math.sin(rad)
    local worldCorners = {}

    for i, c in ipairs(localCorners) do
        local rx = c[1] * cosH - c[2] * sinH
        local ry = c[1] * sinH + c[2] * cosH
        worldCorners[i] = {cx + rx, cy + ry, cz + c[3]}
    end

    local edges = {
        {1,2}, {2,3}, {3,4}, {4,1},
        {5,6}, {6,7}, {7,8}, {8,5},
        {1,5}, {2,6}, {3,7}, {4,8}
    }

    local mainCol = wa(col, 0xFF)
    local glowCol = wa(col, 0x33)

    for _, e in ipairs(edges) do
        local p1 = worldCorners[e[1]]
        local p2 = worldCorners[e[2]]
        draw3DLine(dl, p1[1], p1[2], p1[3], p2[1], p2[2], p2[3], glowCol, 3.0)
        draw3DLine(dl, p1[1], p1[2], p1[3], p2[1], p2[2], p2[3], mainCol, 1.2)
    end
end

-- ══════════════════════════════════════════════════════
--  ESP
-- ══════════════════════════════════════════════════════
local function drawESP(dl, p, sampId)
    local ped = p.ped
    if not doesCharExist(ped) then return end
    local ok0, cx, cy, cz = pcall(getCharCoordinates, ped)
    if not ok0 then return end

    if not isPedVisible(cx, cy, cz) then return end

    local ptr = getCharPointer(ped)
    local headX, headY, headZ = cx, cy, cz + 0.9

    local preset = BONE_PRESETS[cfg.bonePreset] or BONE_PRESETS[1]
    local cache  = {}
    if ptr and ptr ~= 0 then
        cache = buildBoneCache(ptr, preset)
        local hb = cache[6]
        if hb then
            local ok, hx, hy, hz = pcall(bonePos, ptr, 6)
            if ok then headX, headY, headZ = hx, hy, hz + 0.12 end
        end
    end

    local hsx, hsy = w2s(headX, headY, headZ)
    local fsx, fsy = w2s(cx, cy, cz - 0.85)
    if not hsx or not fsx then return end
    if hsy >= fsy then return end

    local h  = fsy - hsy
    local w  = h * 0.32
    local x1, x2 = hsx - w, hsx + w
    local y1, y2 = hsy, fsy

    local col  = skinColor(p.skin)
    local cMid = wa(col, 0xBB)
    local cFul = wa(col, 0xFF)

    -- ── 3D Box Render ──
    if cfg.show3DBox then
        draw3DBox(dl, ped, col)
    end

    -- ── 2D Box Render ──
    if cfg.show2DBox then
        if cfg.showBoxFill then
            dl:AddRectFilled(v2(x1,y1), v2(x2,y2), wa(col, 0x22))
        end
        dl:AddRectFilled(v2(x1-2,y1-2), v2(x2+2,y2+2), wa(col, 0x18), 2)
        dl:AddRect(v2(x1-1,y1-1), v2(x2+1,y2+1), wa(0xFF000000,0xAA), 0, 15, 2.5)
        dl:AddRect(v2(x1,y1), v2(x2,y2), cMid, 0, 15, 1.0)

        local cl = math.max(4, h * 0.20)
        local bw = 2.0
        dl:AddLine(v2(x1,y1), v2(x1+cl,y1), cFul, bw)
        dl:AddLine(v2(x1,y1), v2(x1,y1+cl), cFul, bw)
        dl:AddLine(v2(x2,y1), v2(x2-cl,y1), cFul, bw)
        dl:AddLine(v2(x2,y1), v2(x2,y1+cl), cFul, bw)
        dl:AddLine(v2(x1,y2), v2(x1+cl,y2), cFul, bw)
        dl:AddLine(v2(x1,y2), v2(x1,y2-cl), cFul, bw)
        dl:AddLine(v2(x2,y2), v2(x2-cl,y2), cFul, bw)
        dl:AddLine(v2(x2,y2), v2(x2,y2-cl), cFul, bw)
    end

    -- ── Bars (HP & Armor) ──
    if cfg.show2DBox or cfg.show3DBox then
        local hp = nil
        if sampId then
            local ok, v = pcall(sampGetPlayerHealth, sampId)
            if ok and v then hp = v end
        end
        if not hp then
            local ok, v = pcall(getCharHealth, ped)
            hp = (ok and v) or 100
        end
        hp = math.max(0, math.min(100, hp))
        local pct = hp / 100
        local bh  = h * pct

        local barW = math.max(5, w * 0.22)
        local hbx2 = x1 - 4
        local hbx1 = hbx2 - barW
        dl:AddRectFilled(v2(hbx1,y1),    v2(hbx2,y2),    wa(0xFF000000,0xCC), 1)
        dl:AddRectFilled(v2(hbx1,y2-bh), v2(hbx2,y2),    hpColor(pct),        1)
        dl:AddRect(      v2(hbx1,y1),    v2(hbx2,y2),    wa(0xFF000000,0x88), 1, 15, 0.8)

        local ar = nil
        if sampId then
            local ok, v = pcall(sampGetPlayerArmor, sampId)
            if ok and v then ar = v end
        end
        if not ar then
            local ok, v = pcall(getCharArmour, ped)
            ar = (ok and v) or 0
        end
        ar = math.max(0, math.min(100, ar))
        if ar > 0 then
            local apct = ar / 100
            local abh  = h * apct
            local abx2 = hbx1 - 3
            local abx1 = abx2 - barW
            dl:AddRectFilled(v2(abx1,y1),     v2(abx2,y2), wa(0xFF000000,0xCC), 1)
            dl:AddRectFilled(v2(abx1,y2-abh), v2(abx2,y2), wa(0xFFFFAA33,0xFF), 1)
            dl:AddRect(      v2(abx1,y1),     v2(abx2,y2), wa(0xFF000000,0x88), 1, 15, 0.8)
        end
    end

    -- ── Skeleton ──
    drawSkeleton(dl, cache, col, h, preset)
end

-- ══════════════════════════════════════════════════════
--  ImGui menu
-- ══════════════════════════════════════════════════════
local menuOpen    = imgui.new.bool(false)
local menuWasOpen = false
local groupColorVec = {}
local function syncGroupColorVecs()
    groupColorVec = {}
    for i, g in ipairs(cfg.skinGroups) do
        local c = g.color
        groupColorVec[i] = imgui.new.float[4](c[1], c[2], c[3], c[4])
    end
end

local newGroupName  = imgui.new.char[64]('')
local newGroupIds   = imgui.new.char[256]('')
local newGroupColor = imgui.new.float[4](1, 1, 1, 1)

local chkEnabled    = imgui.new.bool(true)
local chk2DBox      = imgui.new.bool(true)
local chk3DBox      = imgui.new.bool(false)
local chkBoxFill    = imgui.new.bool(false)
local chkDots       = imgui.new.bool(true)
local chkBones      = imgui.new.bool(true)
local sldRadius     = imgui.new.float(500)

local function syncCheckboxes()
    chkEnabled[0] = cfg.enabled
    chk2DBox[0]   = cfg.show2DBox
    chk3DBox[0]   = cfg.show3DBox
    chkBoxFill[0] = cfg.showBoxFill
    chkDots[0]    = cfg.showDots
    chkBones[0]   = cfg.showBones
    sldRadius[0]  = cfg.radius
end

local function applyStyle()
    local s = imgui.GetStyle()
    s.WindowRounding = 6; s.FrameRounding = 4
    s.ScrollbarRounding = 4; s.GrabRounding = 4
    s.WindowBorderSize = 1; s.FrameBorderSize = 0
    local c = s.Colors
    local function set(i,r,g,b,a) c[i] = v4(r,g,b,a) end
    set(imgui.Col.WindowBg,        0.08,0.05,0.12,0.97)
    set(imgui.Col.TitleBg,         0.12,0.07,0.20,1.0)
    set(imgui.Col.TitleBgActive,   0.18,0.10,0.30,1.0)
    set(imgui.Col.FrameBg,         0.15,0.09,0.22,1.0)
    set(imgui.Col.FrameBgHovered,  0.22,0.13,0.32,1.0)
    set(imgui.Col.FrameBgActive,   0.28,0.17,0.40,1.0)
    set(imgui.Col.Button,          0.25,0.14,0.38,1.0)
    set(imgui.Col.ButtonHovered,   0.35,0.20,0.52,1.0)
    set(imgui.Col.ButtonActive,    0.45,0.27,0.65,1.0)
    set(imgui.Col.Header,          0.22,0.13,0.34,1.0)
    set(imgui.Col.HeaderHovered,   0.30,0.18,0.46,1.0)
    set(imgui.Col.HeaderActive,    0.38,0.23,0.58,1.0)
    set(imgui.Col.CheckMark,       0.75,0.45,1.00,1.0)
    set(imgui.Col.SliderGrab,      0.60,0.35,0.90,1.0)
    set(imgui.Col.SliderGrabActive,0.75,0.45,1.00,1.0)
    set(imgui.Col.Separator,       0.30,0.18,0.45,1.0)
    set(imgui.Col.ScrollbarBg,     0.06,0.04,0.10,1.0)
    set(imgui.Col.ScrollbarGrab,   0.30,0.18,0.45,1.0)
    set(imgui.Col.Text,            0.92,0.88,1.00,1.0)
    set(imgui.Col.Border,          0.35,0.20,0.55,0.6)
    set(imgui.Col.PopupBg,         0.10,0.06,0.16,0.97)
end

-- ══════════════════════════════════════════════════════
--  Render
-- ══════════════════════════════════════════════════════
local styleApplied = false

imgui.OnFrame(function() return true end, function(self)
    self.HideCursor = not menuOpen[0]

    if not sampLoaded then return end

    if not styleApplied then
        applyStyle()
        styleApplied = true
    end

    if menuWasOpen and not menuOpen[0] then
        for i, g in ipairs(cfg.skinGroups) do
            local v = groupColorVec[i]
            if v then g.color = {v[0],v[1],v[2],v[3]} end
        end
        rebuildColorMap()
        saveConfig()
    end
    menuWasOpen = menuOpen[0]

    -- ── ESP ──
    if cfg.enabled and isSampAvailable() then
        local dl = imgui.GetBackgroundDrawList()
        local ok0, mx, my, mz = pcall(getCharCoordinates, playerPed)
        if ok0 then
            for id, p in pairs(players) do
                if not doesCharExist(p.ped) then
                    players[id] = nil
                else
                    local ok, x, y, z = pcall(getCharCoordinates, p.ped)
                    if ok then
                        local gtaSkin = getCharModel(p.ped)
                        if gtaSkin > 311 then
                            local s = getSampSkinId(id)
                            p.skin = (s and s > 0) and s or gtaSkin
                        else
                            p.skin = gtaSkin
                        end
                        local dist = (x-mx)^2 + (y-my)^2 + (z-mz)^2
                        if dist <= cfg.radius * cfg.radius then
                            drawESP(dl, p, id)
                        end
                    end
                end
            end
        end
    end

    -- ── Menu ──
    if not menuOpen[0] then return end

    imgui.SetNextWindowSize(v2(480, 540), imgui.Cond.FirstUseEver)
    local sw, sh = getScreenResolution()
    imgui.SetNextWindowPos(v2(sw/2, sh/2), imgui.Cond.Appearing, v2(0.5, 0.5))

    if imgui.Begin('WH Admin', menuOpen, imgui.WindowFlags.NoCollapse) then
        if imgui.BeginTabBar('tabs') then

            -- ── Основное ──
            if imgui.BeginTabItem('Основное') then
                imgui.Spacing()
                if imgui.Checkbox('Включено', chkEnabled) then
                    cfg.enabled = chkEnabled[0]
                end
                imgui.Separator()
               
                if imgui.Checkbox('Включить 2D Box', chk2DBox) then
                    cfg.show2DBox = chk2DBox[0]
                end

                if cfg.show2DBox then
                    imgui.Indent()
                    if imgui.Checkbox('Заливка 2D Box', chkBoxFill) then
                        cfg.showBoxFill = chkBoxFill[0]
                    end
                    imgui.Unindent()
                end

                if imgui.Checkbox('Включить 3D Box', chk3DBox) then
                    cfg.show3DBox = chk3DBox[0]
                end

                imgui.Separator()
                if imgui.Checkbox('Включить точки на суставы', chkDots) then
                    cfg.showDots = chkDots[0]
                end
                if imgui.Checkbox('Включить Кости', chkBones) then
                    cfg.showBones = chkBones[0]
                end
                imgui.Spacing()
                imgui.Text('Вид костей:')
                for i, preset in ipairs(BONE_PRESETS) do
                    local selected = (cfg.bonePreset == i)
                    if imgui.Selectable(preset.name, selected) then
                        cfg.bonePreset = i
                    end
                end
                imgui.Separator()
                imgui.Text('Радиус (м):')
                imgui.SetNextItemWidth(200)
                if imgui.SliderFloat('##radius', sldRadius, 50, 500) then
                    cfg.radius = sldRadius[0]
                end
                imgui.EndTabItem()
            end

            if imgui.BeginTabItem('Цвета скинов') then
                imgui.Spacing()
                imgui.TextDisabled('Редактируй цвет и ID скинов для каждой группы.')
                imgui.Separator()
                imgui.BeginChild('##skinlist', v2(0, 300), false)

                local toRemove = nil
                for i, g in ipairs(cfg.skinGroups) do
                    local v = groupColorVec[i]
                    if v then
                        imgui.ColorEdit4('##col'..i, v,
                            imgui.ColorEditFlags.NoInputs + imgui.ColorEditFlags.NoLabel)
                        imgui.SameLine()
                    end
                    imgui.Text(g.name)
                    imgui.SameLine()
                    local idsBuf = imgui.new.char[256](g.ids)
                    imgui.SetNextItemWidth(150)
                    if imgui.InputText('##ids'..i, idsBuf, 256) then
                        g.ids = ffi.string(idsBuf)
                    end
                    imgui.SameLine()
                    if imgui.SmallButton('X##'..i) then toRemove = i end
                end

                imgui.EndChild()

                if toRemove then
                    table.remove(cfg.skinGroups, toRemove)
                    syncGroupColorVecs()
                end

                imgui.Separator()
                imgui.Text('Добавить группу:')
                imgui.SetNextItemWidth(90)
                imgui.InputText('##newname', newGroupName, 64)
                imgui.SameLine()
                imgui.SetNextItemWidth(150)
                imgui.InputText('##newids', newGroupIds, 256)
                imgui.SameLine()
                imgui.ColorEdit4('##newcol', newGroupColor,
                    imgui.ColorEditFlags.NoInputs + imgui.ColorEditFlags.NoLabel)
                imgui.SameLine()
                if imgui.Button('Добавить') then
                    local name = ffi.string(newGroupName)
                    local ids  = ffi.string(newGroupIds)
                    if name ~= '' and ids ~= '' then
                        table.insert(cfg.skinGroups, {
                            name  = name, ids = ids,
                            color = {newGroupColor[0],newGroupColor[1],
                                     newGroupColor[2],newGroupColor[3]},
                        })
                        syncGroupColorVecs()
                        imgui.StrCopy(newGroupName, '')
                        imgui.StrCopy(newGroupIds,  '')
                    end
                end
                imgui.EndTabItem()
            end

            imgui.EndTabBar()
        end
    end
    imgui.End()
end)

-- ══════════════════════════════════════════════════════
--  Main
-- ══════════════════════════════════════════════════════
function main()
    loadConfig()
    syncGroupColorVecs()
    syncCheckboxes()
    rebuildColorMap()

    while not isSampAvailable() do wait(1000) end
    msg('{aaaaaa}Конфиг загружен.')
    sampLoaded = true
    sampAddChatMessage(PREFIX .. cp('loaded  {aaaaaa}/whadmin'), 0xFFFFFF)
    sampAddChatMessage(AUTHOR, 0xFFFFFF)

    sampRegisterChatCommand('whadmin', function()
        menuOpen[0] = not menuOpen[0]
        if menuOpen[0] then
            syncCheckboxes()
            msg('Меню открыто.')
        else
            msg('Меню закрыто.')
        end
    end)

    sampRegisterChatCommand('checkskin', function(args)
        local targetId = tonumber(args)
        if not targetId then
            local ok0, mx, my, mz = pcall(getCharCoordinates, playerPed)
            for id, p in pairs(players) do
                if doesCharExist(p.ped) then
                    local ok, x, y, z = pcall(getCharCoordinates, p.ped)
                    local dist = ok and math.sqrt((x-mx)^2+(y-my)^2+(z-mz)^2) or 0
                    local gtaSkin  = getCharModel(p.ped)
                    local sampSkin = getSampSkinId(id)
                    local ptr      = getCharPointer(p.ped)
                    local memSkin  = 0
                    if ptr and ptr ~= 0 then
                        local ok2, v = pcall(memory.getint16, ptr + 0x22, false)
                        if ok2 then memSkin = v end
                    end
                    print(string.format('[checkskin] id=%d name=%s gta=%d samp=%s mem=%d dist=%.0fm',
                        id, p.name, gtaSkin, tostring(sampSkin), memSkin, dist))
                end
            end
        else
            local p = players[targetId]
            if not p or not doesCharExist(p.ped) then
                print('[checkskin] player '..targetId..' not found')
                return
            end
            local gtaSkin  = getCharModel(p.ped)
            local sampSkin = getSampSkinId(targetId)
            local ptr      = getCharPointer(p.ped)
            print(string.format('[checkskin] id=%d name=%s', targetId, p.name))
            print('  getCharModel   = '..gtaSkin)
            print('  getSampSkinId = '..tostring(sampSkin))
            local effectiveSkin = sampSkin or gtaSkin
            print('  effective skin used for color = '..tostring(effectiveSkin))
            print('  color in map = '..tostring(skinColorMap[effectiveSkin]))
            print('  >> /addskin '..tostring(effectiveSkin)..' GroupName  to add to a group')
            if ptr and ptr ~= 0 then
                for _, off in ipairs({0x20, 0x22, 0x24, 0x26, 0x28, 0x2A}) do
                    local ok, v = pcall(memory.getint16, ptr + off, false)
                    print(string.format('  ptr+0x%X = %s', off, ok and tostring(v) or 'err'))
                end
            end
        end
    end)

    sampRegisterChatCommand('addskin', function(args)
        local skinId, groupName = args:match('(%d+)%s+(.*)')
        if not skinId then
            sampAddChatMessage(PREFIX .. '/addskin {faa9ff}<skinId> <groupName>', 0xFFFFFF)
            return
        end
        skinId = tonumber(skinId)
        for _, g in ipairs(cfg.skinGroups) do
            if g.name == groupName then
                g.ids = g.ids .. ',' .. skinId
                rebuildColorMap()
                syncGroupColorVecs()
                saveConfig()
                msg(string.format('skin {faa9ff}%d{ffffff} добавлен в группу {faa9ff}%s', skinId, groupName))
                return
            end
        end
        table.insert(cfg.skinGroups, {
            name  = groupName,
            ids   = tostring(skinId),
            color = {1.0, 1.0, 1.0, 1.0},
        })
        rebuildColorMap()
        syncGroupColorVecs()
        saveConfig()
        msg(string.format('новая группа {faa9ff}%s{ffffff} создана со скином {faa9ff}%d', groupName, skinId))
    end)

    lua_thread.create(function()
        while true do
            wait(500)
            for id = 0, 1000 do
                if sampIsPlayerConnected(id) then
                    local ok, ped = sampGetCharHandleBySampPlayerId(id)
                    if ok and doesCharExist(ped) and ped ~= playerPed then
                        local skin = getPlayerSkin(id, ped)
                        if not players[id] then
                            players[id] = { ped=ped, name=sampGetPlayerNickname(id), skin=skin }
                        else
                            players[id].ped  = ped
                            players[id].skin = skin
                        end
                    end
                else
                    players[id] = nil
                end
            end
        end
    end)

    while true do wait(1000) end
end
ну или так
 

James Saula

Известный
114
26
ну на 1 промт в нейрнке к слову

Lua:
local imgui   = require 'mimgui'
local sampev  = require 'lib.samp.events'
local ffi     = require 'ffi'
local json    = require 'dkjson'
local iconv   = require 'iconv'
local utf8cp  = iconv.new('CP1251', 'UTF-8')
local function cp(s) return utf8cp:iconv(s) end
local sampLoaded = false

local math_min, math_max = math.min, math.max
local NEAR_PLANE_EPSILON = 1e-3

local function makeCharArray(tb)
    local arr = ffi.new('const char*['..#tb..']')
    for i = 1, #tb do
        arr[i-1] = tb[i]
    end
    return arr
end

-- ══════════════════════════════════════════════════════
--  CPed::GetBonePosition
-- ══════════════════════════════════════════════════════
ffi.cdef[[
    typedef struct { float x, y, z; } RwV3d;
    typedef void (__thiscall *tGetBonePos)(void*, RwV3d*, unsigned int, int);
]]
local _getBoneFn = ffi.cast('tGetBonePos', 0x5E4280)
local _boneOut   = ffi.new('RwV3d[1]')
local function bonePos(ptr, id)
    _getBoneFn(ffi.cast('void*', ptr), _boneOut, id, 0)
    return _boneOut[0].x, _boneOut[0].y, _boneOut[0].z
end

-- ══════════════════════════════════════════════════════
--  w2s & 3D Line Rendering with Near Plane Clipping
-- ══════════════════════════════════════════════════════
local _sw, _sh = getScreenResolution()

local function w2s(x, y, z)
    local sx, sy = convert3DCoordsToScreen(x, y, z)
    if not sx then return nil, nil end
    if sx < -_sw or sx > _sw*2 or sy < -_sh or sy > _sh*2 then
        return nil, nil
    end
    return sx, sy
end

local function isPedVisible(cx, cy, cz)
    return isPointOnScreen(cx, cy, cz, 1.0)
end

local function clipToNearPlane(fx, fy, fz, wf, bx, by, bz, wb)
    local dw = wf - wb
    if dw == 0 then return nil, nil end
    local t = math_max(0.0, math_min(1.0, (wf - NEAR_PLANE_EPSILON) / dw))
    local _, sx, sy = convert3DCoordsToScreenEx(
        fx + (bx - fx) * t,
        fy + (by - fy) * t,
        fz + (bz - fz) * t
    )
    return sx, sy
end

local function draw3DLine(dl, x1, y1, z1, x2, y2, z2, color, thickness)
    local _, sx1, sy1, sz1 = convert3DCoordsToScreenEx(x1, y1, z1)
    local _, sx2, sy2, sz2 = convert3DCoordsToScreenEx(x2, y2, z2)

    if sz1 <= 0 and sz2 <= 0 then return end

    if sz2 <= 0 then
        sx2, sy2 = clipToNearPlane(x1, y1, z1, sz1, x2, y2, z2, sz2)
    elseif sz1 <= 0 then
        sx1, sy1 = clipToNearPlane(x2, y2, z2, sz2, x1, y1, z1, sz1)
    end

    if not sx1 or not sx2 then return end

    dl:AddLine(imgui.ImVec2(sx1, sy1), imgui.ImVec2(sx2, sy2), color, thickness or 1.0)
end

-- ══════════════════════════════════════════════════════
--  Bone presets
-- ══════════════════════════════════════════════════════
local BONE_PRESETS = {
    {
        name  = 'Полный',
        ids   = {1,2,3,4,6,22,23,24,32,33,34,41,42,43,51,52,53},
        pairs = {
            {1,2},{2,3},{3,4},{4,6},
            {3,32},{32,33},{33,34},
            {3,22},{22,23},{23,24},
            {1,41},{41,42},{42,43},
            {1,51},{51,52},{52,53},
        }
    },
    {
        name  = 'Упрощённый',
        ids   = {1,4,6,23,33,42,52},
        pairs = {
            {1,4},{4,6},
            {4,33},{4,23},
            {1,42},{1,52},
        }
    },
    {
        name  = 'Только руки/ноги',
        ids   = {22,23,24,32,33,34,41,42,43,51,52,53},
        pairs = {
            {32,33},{33,34},
            {22,23},{23,24},
            {41,42},{42,43},
            {51,52},{52,53},
        }
    },
}

-- ══════════════════════════════════════════════════════
--  Config
-- ══════════════════════════════════════════════════════
local CFG_PATH = getWorkingDirectory() .. '\\config\\wh_config.json'
local cfg = {
    enabled     = true,
    show2DBox   = true,
    show3DBox   = false,
    showBoxFill = false,
    showDots    = true,
    showBones   = true,
    bonePreset  = 1,
    radius      = 500.0,
    skinGroups  = {
        { name='Grove',     ids='105,106,107',     color={0.0,  0.80, 0.27, 1.0} },
        { name='Ballas',    ids='102,103,104',     color={0.80, 0.27, 1.0,  1.0} },
        { name='Vagos',     ids='108,109,110',     color={1.0,  0.93, 0.0,  1.0} },
        { name='Aztecas',   ids='114,115,116',     color={0.0,  0.75, 1.0,  1.0} },
        { name='Rifa',      ids='173,174,175',     color={0.0,  0.85, 0.75, 1.0} },
        { name='DaNang',    ids='121,122,123',     color={0.27, 0.53, 1.0,  1.0} },
        { name='Triads',    ids='117,118,120',     color={0.13, 0.33, 1.0,  1.0} },
        { name='Mafia',     ids='111,112,113',     color={0.67, 0.67, 0.67, 1.0} },
        { name='VietMafia', ids='124,125,126,127', color={0.53, 0.67, 1.0,  1.0} },
        { name='Bikers',    ids='247,248',         color={1.0,  0.55, 0.13, 1.0} },
    }
}

local PREFIX = '{faa9ff}[WallHack]{ffffff} '
local AUTHOR = PREFIX .. 'Authors: {faa9ff}volodyapivo{ffffff} &.'
local function msg(text) sampAddChatMessage(PREFIX .. cp(text), 0xFFFFFF) end

local function saveConfig()
    local dir = getWorkingDirectory() .. '\\config'
    if not doesDirectoryExist(dir) then createDirectory(dir) end
    local f = io.open(CFG_PATH, 'w')
    if f then
        f:write(json.encode(cfg, {indent=true}))
        f:close()
        msg('{aaaaaa}Конфиг сохранён.')
    end
end

local function loadConfig()
    local f = io.open(CFG_PATH, 'r')
    if not f then return end
    local t = json.decode(f:read('*a')); f:close()
    if not t then return end
    for k,v in pairs(t) do cfg[k] = v end
end

-- ══════════════════════════════════════════════════════
--  Skin colour map
-- ══════════════════════════════════════════════════════
local skinColorMap = {}
local function rebuildColorMap()
    skinColorMap = {}
    for _, g in ipairs(cfg.skinGroups) do
        local c  = g.color
        local r  = math.floor(c[1]*255+0.5)
        local gv = math.floor(c[2]*255+0.5)
        local b  = math.floor(c[3]*255+0.5)
        local a  = math.floor(c[4]*255+0.5)
        local abgr = bit.bor(bit.lshift(a,24), bit.lshift(b,16), bit.lshift(gv,8), r)
        for idstr in (g.ids..','):gmatch('(%d+),') do
            local id = tonumber(idstr)
            if id then skinColorMap[id] = abgr end
        end
    end
end

local function skinColor(id) return skinColorMap[id] or 0xFFFFFFFF end

-- ══════════════════════════════════════════════════════
--  SAMP skin ID reader
-- ══════════════════════════════════════════════════════
local memory = require 'memory'
local function getSampSkinId(sampPlayerId)
    local ok, skin = pcall(sampGetPlayerSkin, sampPlayerId)
    if ok and skin and skin > 0 then return skin end
    local ok2, ptr = pcall(sampGetPlayerStructPtr, sampPlayerId)
    if ok2 and ptr and ptr ~= 0 then
        local ok3, v = pcall(memory.getint16, ptr + 0x12, false)
        if ok3 and v and v > 0 then return v end
    end
    return nil
end

local players = {}
local function getPlayerSkin(sampId, ped)
    local skin = getSampSkinId(sampId)
    if skin then return skin end
    return getCharModel(ped)
end

sampev.onPlayerStreamIn = function(id)
    local ok, ped = sampGetCharHandleBySampPlayerId(id)
    if ok then
        players[id] = { ped=ped, name=sampGetPlayerNickname(id), skin=getPlayerSkin(id, ped) }
    end
end
sampev.onPlayerStreamOut = function(id) players[id] = nil end

-- ══════════════════════════════════════════════════════
--  Colour helpers
-- ══════════════════════════════════════════════════════
local function v2(x,y)     return imgui.ImVec2(x,y) end
local function v4(r,g,b,a) return imgui.ImVec4(r,g,b,a) end
local function wa(col, a)
    return bit.bor(bit.band(col, 0x00FFFFFF), bit.lshift(a, 24))
end

local function lerpColor(c1, c2, t)
    local function ch(c,s) return bit.band(bit.rshift(c,s), 0xFF) end
    local function lp(a,b) return math.floor(a+(b-a)*t) end
    return bit.bor(
        bit.lshift(lp(ch(c1,24),ch(c2,24)),24),
        bit.lshift(lp(ch(c1,16),ch(c2,16)),16),
        bit.lshift(lp(ch(c1,8), ch(c2,8)), 8),
        lp(ch(c1,0),ch(c2,0))
    )
end

local function hpColor(pct)
    if pct > 0.5 then return lerpColor(0xFF00FFFF, 0xFF00FF00, (pct-0.5)*2)
    else              return lerpColor(0xFF0000FF, 0xFF00FFFF, pct*2) end
end

-- ══════════════════════════════════════════════════════
--  Skeleton
-- ══════════════════════════════════════════════════════
local function buildBoneCache(ptr, preset)
    local cache = {}
    for _, bid in ipairs(preset.ids) do
        local ok, x, y, z = pcall(bonePos, ptr, bid)
        if ok then
            local sx, sy = w2s(x, y, z)
            if sx then cache[bid] = {sx, sy} end
        end
    end
    return cache
end

local function drawSkeleton(dl, cache, col, boxH, preset)
    if not cfg.showBones then return end

    local s     = math.max(0.0, math.min(1.0, boxH / 100.0))
    local lineW = 0.8 + s * 1.4
    local glowW = lineW + 2.0
    local dotR  = math.max(0.8, 0.8 + s * 2.2)
    local glow  = wa(col, 0x35)
    local main  = wa(col, 0xDD)

    for _, p in ipairs(preset.pairs) do
        local a = cache[p[1]]
        local b = cache[p[2]]
        if a and b then
            dl:AddLine(v2(a[1],a[2]), v2(b[1],b[2]), glow, glowW)
            dl:AddLine(v2(a[1],a[2]), v2(b[1],b[2]), main, lineW)
        end
    end

    if cfg.showDots then
        local drawn = {}
        for _, p in ipairs(preset.pairs) do
            for _, bid in ipairs(p) do
                if not drawn[bid] then
                    drawn[bid] = true
                    local pt = cache[bid]
                    if pt then
                        dl:AddCircleFilled(v2(pt[1],pt[2]), dotR,     wa(col,0xFF))
                        dl:AddCircle(      v2(pt[1],pt[2]), dotR+0.5, wa(0xFF000000,0xAA), 12, 1.0)
                    end
                end
            end
        end
    end
end

-- ══════════════════════════════════════════════════════
--  3D Bounding Box Logic
-- ══════════════════════════════════════════════════════
local function draw3DBox(dl, ped, col)
    local heading = getCharHeading(ped)
    local rad = math.rad(heading)
   
    local cx, cy, cz = getCharCoordinates(ped)
   
    local minX, maxX = -0.4, 0.4
    local minY, maxY = -0.4, 0.4
    local minZ, maxZ = -0.9, 1.05

    local localCorners = {
        {minX, minY, minZ}, {maxX, minY, minZ}, {maxX, maxY, minZ}, {minX, maxY, minZ},
        {minX, minY, maxZ}, {maxX, minY, maxZ}, {maxX, maxY, maxZ}, {minX, maxY, maxZ}
    }

    local cosH, sinH = math.cos(rad), math.sin(rad)
    local worldCorners = {}

    for i, c in ipairs(localCorners) do
        local rx = c[1] * cosH - c[2] * sinH
        local ry = c[1] * sinH + c[2] * cosH
        worldCorners[i] = {cx + rx, cy + ry, cz + c[3]}
    end

    local edges = {
        {1,2}, {2,3}, {3,4}, {4,1},
        {5,6}, {6,7}, {7,8}, {8,5},
        {1,5}, {2,6}, {3,7}, {4,8}
    }

    local mainCol = wa(col, 0xFF)
    local glowCol = wa(col, 0x33)

    for _, e in ipairs(edges) do
        local p1 = worldCorners[e[1]]
        local p2 = worldCorners[e[2]]
        draw3DLine(dl, p1[1], p1[2], p1[3], p2[1], p2[2], p2[3], glowCol, 3.0)
        draw3DLine(dl, p1[1], p1[2], p1[3], p2[1], p2[2], p2[3], mainCol, 1.2)
    end
end

-- ══════════════════════════════════════════════════════
--  ESP
-- ══════════════════════════════════════════════════════
local function drawESP(dl, p, sampId)
    local ped = p.ped
    if not doesCharExist(ped) then return end
    local ok0, cx, cy, cz = pcall(getCharCoordinates, ped)
    if not ok0 then return end

    if not isPedVisible(cx, cy, cz) then return end

    local ptr = getCharPointer(ped)
    local headX, headY, headZ = cx, cy, cz + 0.9

    local preset = BONE_PRESETS[cfg.bonePreset] or BONE_PRESETS[1]
    local cache  = {}
    if ptr and ptr ~= 0 then
        cache = buildBoneCache(ptr, preset)
        local hb = cache[6]
        if hb then
            local ok, hx, hy, hz = pcall(bonePos, ptr, 6)
            if ok then headX, headY, headZ = hx, hy, hz + 0.12 end
        end
    end

    local hsx, hsy = w2s(headX, headY, headZ)
    local fsx, fsy = w2s(cx, cy, cz - 0.85)
    if not hsx or not fsx then return end
    if hsy >= fsy then return end

    local h  = fsy - hsy
    local w  = h * 0.32
    local x1, x2 = hsx - w, hsx + w
    local y1, y2 = hsy, fsy

    local col  = skinColor(p.skin)
    local cMid = wa(col, 0xBB)
    local cFul = wa(col, 0xFF)

    -- ── 3D Box Render ──
    if cfg.show3DBox then
        draw3DBox(dl, ped, col)
    end

    -- ── 2D Box Render ──
    if cfg.show2DBox then
        if cfg.showBoxFill then
            dl:AddRectFilled(v2(x1,y1), v2(x2,y2), wa(col, 0x22))
        end
        dl:AddRectFilled(v2(x1-2,y1-2), v2(x2+2,y2+2), wa(col, 0x18), 2)
        dl:AddRect(v2(x1-1,y1-1), v2(x2+1,y2+1), wa(0xFF000000,0xAA), 0, 15, 2.5)
        dl:AddRect(v2(x1,y1), v2(x2,y2), cMid, 0, 15, 1.0)

        local cl = math.max(4, h * 0.20)
        local bw = 2.0
        dl:AddLine(v2(x1,y1), v2(x1+cl,y1), cFul, bw)
        dl:AddLine(v2(x1,y1), v2(x1,y1+cl), cFul, bw)
        dl:AddLine(v2(x2,y1), v2(x2-cl,y1), cFul, bw)
        dl:AddLine(v2(x2,y1), v2(x2,y1+cl), cFul, bw)
        dl:AddLine(v2(x1,y2), v2(x1+cl,y2), cFul, bw)
        dl:AddLine(v2(x1,y2), v2(x1,y2-cl), cFul, bw)
        dl:AddLine(v2(x2,y2), v2(x2-cl,y2), cFul, bw)
        dl:AddLine(v2(x2,y2), v2(x2,y2-cl), cFul, bw)
    end

    -- ── Bars (HP & Armor) ──
    if cfg.show2DBox or cfg.show3DBox then
        local hp = nil
        if sampId then
            local ok, v = pcall(sampGetPlayerHealth, sampId)
            if ok and v then hp = v end
        end
        if not hp then
            local ok, v = pcall(getCharHealth, ped)
            hp = (ok and v) or 100
        end
        hp = math.max(0, math.min(100, hp))
        local pct = hp / 100
        local bh  = h * pct

        local barW = math.max(5, w * 0.22)
        local hbx2 = x1 - 4
        local hbx1 = hbx2 - barW
        dl:AddRectFilled(v2(hbx1,y1),    v2(hbx2,y2),    wa(0xFF000000,0xCC), 1)
        dl:AddRectFilled(v2(hbx1,y2-bh), v2(hbx2,y2),    hpColor(pct),        1)
        dl:AddRect(      v2(hbx1,y1),    v2(hbx2,y2),    wa(0xFF000000,0x88), 1, 15, 0.8)

        local ar = nil
        if sampId then
            local ok, v = pcall(sampGetPlayerArmor, sampId)
            if ok and v then ar = v end
        end
        if not ar then
            local ok, v = pcall(getCharArmour, ped)
            ar = (ok and v) or 0
        end
        ar = math.max(0, math.min(100, ar))
        if ar > 0 then
            local apct = ar / 100
            local abh  = h * apct
            local abx2 = hbx1 - 3
            local abx1 = abx2 - barW
            dl:AddRectFilled(v2(abx1,y1),     v2(abx2,y2), wa(0xFF000000,0xCC), 1)
            dl:AddRectFilled(v2(abx1,y2-abh), v2(abx2,y2), wa(0xFFFFAA33,0xFF), 1)
            dl:AddRect(      v2(abx1,y1),     v2(abx2,y2), wa(0xFF000000,0x88), 1, 15, 0.8)
        end
    end

    -- ── Skeleton ──
    drawSkeleton(dl, cache, col, h, preset)
end

-- ══════════════════════════════════════════════════════
--  ImGui menu
-- ══════════════════════════════════════════════════════
local menuOpen    = imgui.new.bool(false)
local menuWasOpen = false
local groupColorVec = {}
local function syncGroupColorVecs()
    groupColorVec = {}
    for i, g in ipairs(cfg.skinGroups) do
        local c = g.color
        groupColorVec[i] = imgui.new.float[4](c[1], c[2], c[3], c[4])
    end
end

local newGroupName  = imgui.new.char[64]('')
local newGroupIds   = imgui.new.char[256]('')
local newGroupColor = imgui.new.float[4](1, 1, 1, 1)

local chkEnabled    = imgui.new.bool(true)
local chk2DBox      = imgui.new.bool(true)
local chk3DBox      = imgui.new.bool(false)
local chkBoxFill    = imgui.new.bool(false)
local chkDots       = imgui.new.bool(true)
local chkBones      = imgui.new.bool(true)
local sldRadius     = imgui.new.float(500)

local function syncCheckboxes()
    chkEnabled[0] = cfg.enabled
    chk2DBox[0]   = cfg.show2DBox
    chk3DBox[0]   = cfg.show3DBox
    chkBoxFill[0] = cfg.showBoxFill
    chkDots[0]    = cfg.showDots
    chkBones[0]   = cfg.showBones
    sldRadius[0]  = cfg.radius
end

local function applyStyle()
    local s = imgui.GetStyle()
    s.WindowRounding = 6; s.FrameRounding = 4
    s.ScrollbarRounding = 4; s.GrabRounding = 4
    s.WindowBorderSize = 1; s.FrameBorderSize = 0
    local c = s.Colors
    local function set(i,r,g,b,a) c[i] = v4(r,g,b,a) end
    set(imgui.Col.WindowBg,        0.08,0.05,0.12,0.97)
    set(imgui.Col.TitleBg,         0.12,0.07,0.20,1.0)
    set(imgui.Col.TitleBgActive,   0.18,0.10,0.30,1.0)
    set(imgui.Col.FrameBg,         0.15,0.09,0.22,1.0)
    set(imgui.Col.FrameBgHovered,  0.22,0.13,0.32,1.0)
    set(imgui.Col.FrameBgActive,   0.28,0.17,0.40,1.0)
    set(imgui.Col.Button,          0.25,0.14,0.38,1.0)
    set(imgui.Col.ButtonHovered,   0.35,0.20,0.52,1.0)
    set(imgui.Col.ButtonActive,    0.45,0.27,0.65,1.0)
    set(imgui.Col.Header,          0.22,0.13,0.34,1.0)
    set(imgui.Col.HeaderHovered,   0.30,0.18,0.46,1.0)
    set(imgui.Col.HeaderActive,    0.38,0.23,0.58,1.0)
    set(imgui.Col.CheckMark,       0.75,0.45,1.00,1.0)
    set(imgui.Col.SliderGrab,      0.60,0.35,0.90,1.0)
    set(imgui.Col.SliderGrabActive,0.75,0.45,1.00,1.0)
    set(imgui.Col.Separator,       0.30,0.18,0.45,1.0)
    set(imgui.Col.ScrollbarBg,     0.06,0.04,0.10,1.0)
    set(imgui.Col.ScrollbarGrab,   0.30,0.18,0.45,1.0)
    set(imgui.Col.Text,            0.92,0.88,1.00,1.0)
    set(imgui.Col.Border,          0.35,0.20,0.55,0.6)
    set(imgui.Col.PopupBg,         0.10,0.06,0.16,0.97)
end

-- ══════════════════════════════════════════════════════
--  Render
-- ══════════════════════════════════════════════════════
local styleApplied = false

imgui.OnFrame(function() return true end, function(self)
    self.HideCursor = not menuOpen[0]

    if not sampLoaded then return end

    if not styleApplied then
        applyStyle()
        styleApplied = true
    end

    if menuWasOpen and not menuOpen[0] then
        for i, g in ipairs(cfg.skinGroups) do
            local v = groupColorVec[i]
            if v then g.color = {v[0],v[1],v[2],v[3]} end
        end
        rebuildColorMap()
        saveConfig()
    end
    menuWasOpen = menuOpen[0]

    -- ── ESP ──
    if cfg.enabled and isSampAvailable() then
        local dl = imgui.GetBackgroundDrawList()
        local ok0, mx, my, mz = pcall(getCharCoordinates, playerPed)
        if ok0 then
            for id, p in pairs(players) do
                if not doesCharExist(p.ped) then
                    players[id] = nil
                else
                    local ok, x, y, z = pcall(getCharCoordinates, p.ped)
                    if ok then
                        local gtaSkin = getCharModel(p.ped)
                        if gtaSkin > 311 then
                            local s = getSampSkinId(id)
                            p.skin = (s and s > 0) and s or gtaSkin
                        else
                            p.skin = gtaSkin
                        end
                        local dist = (x-mx)^2 + (y-my)^2 + (z-mz)^2
                        if dist <= cfg.radius * cfg.radius then
                            drawESP(dl, p, id)
                        end
                    end
                end
            end
        end
    end

    -- ── Menu ──
    if not menuOpen[0] then return end

    imgui.SetNextWindowSize(v2(480, 540), imgui.Cond.FirstUseEver)
    local sw, sh = getScreenResolution()
    imgui.SetNextWindowPos(v2(sw/2, sh/2), imgui.Cond.Appearing, v2(0.5, 0.5))

    if imgui.Begin('WH Admin', menuOpen, imgui.WindowFlags.NoCollapse) then
        if imgui.BeginTabBar('tabs') then

            -- ── Основное ──
            if imgui.BeginTabItem('Основное') then
                imgui.Spacing()
                if imgui.Checkbox('Включено', chkEnabled) then
                    cfg.enabled = chkEnabled[0]
                end
                imgui.Separator()
               
                if imgui.Checkbox('Включить 2D Box', chk2DBox) then
                    cfg.show2DBox = chk2DBox[0]
                end

                if cfg.show2DBox then
                    imgui.Indent()
                    if imgui.Checkbox('Заливка 2D Box', chkBoxFill) then
                        cfg.showBoxFill = chkBoxFill[0]
                    end
                    imgui.Unindent()
                end

                if imgui.Checkbox('Включить 3D Box', chk3DBox) then
                    cfg.show3DBox = chk3DBox[0]
                end

                imgui.Separator()
                if imgui.Checkbox('Включить точки на суставы', chkDots) then
                    cfg.showDots = chkDots[0]
                end
                if imgui.Checkbox('Включить Кости', chkBones) then
                    cfg.showBones = chkBones[0]
                end
                imgui.Spacing()
                imgui.Text('Вид костей:')
                for i, preset in ipairs(BONE_PRESETS) do
                    local selected = (cfg.bonePreset == i)
                    if imgui.Selectable(preset.name, selected) then
                        cfg.bonePreset = i
                    end
                end
                imgui.Separator()
                imgui.Text('Радиус (м):')
                imgui.SetNextItemWidth(200)
                if imgui.SliderFloat('##radius', sldRadius, 50, 500) then
                    cfg.radius = sldRadius[0]
                end
                imgui.EndTabItem()
            end

            if imgui.BeginTabItem('Цвета скинов') then
                imgui.Spacing()
                imgui.TextDisabled('Редактируй цвет и ID скинов для каждой группы.')
                imgui.Separator()
                imgui.BeginChild('##skinlist', v2(0, 300), false)

                local toRemove = nil
                for i, g in ipairs(cfg.skinGroups) do
                    local v = groupColorVec[i]
                    if v then
                        imgui.ColorEdit4('##col'..i, v,
                            imgui.ColorEditFlags.NoInputs + imgui.ColorEditFlags.NoLabel)
                        imgui.SameLine()
                    end
                    imgui.Text(g.name)
                    imgui.SameLine()
                    local idsBuf = imgui.new.char[256](g.ids)
                    imgui.SetNextItemWidth(150)
                    if imgui.InputText('##ids'..i, idsBuf, 256) then
                        g.ids = ffi.string(idsBuf)
                    end
                    imgui.SameLine()
                    if imgui.SmallButton('X##'..i) then toRemove = i end
                end

                imgui.EndChild()

                if toRemove then
                    table.remove(cfg.skinGroups, toRemove)
                    syncGroupColorVecs()
                end

                imgui.Separator()
                imgui.Text('Добавить группу:')
                imgui.SetNextItemWidth(90)
                imgui.InputText('##newname', newGroupName, 64)
                imgui.SameLine()
                imgui.SetNextItemWidth(150)
                imgui.InputText('##newids', newGroupIds, 256)
                imgui.SameLine()
                imgui.ColorEdit4('##newcol', newGroupColor,
                    imgui.ColorEditFlags.NoInputs + imgui.ColorEditFlags.NoLabel)
                imgui.SameLine()
                if imgui.Button('Добавить') then
                    local name = ffi.string(newGroupName)
                    local ids  = ffi.string(newGroupIds)
                    if name ~= '' and ids ~= '' then
                        table.insert(cfg.skinGroups, {
                            name  = name, ids = ids,
                            color = {newGroupColor[0],newGroupColor[1],
                                     newGroupColor[2],newGroupColor[3]},
                        })
                        syncGroupColorVecs()
                        imgui.StrCopy(newGroupName, '')
                        imgui.StrCopy(newGroupIds,  '')
                    end
                end
                imgui.EndTabItem()
            end

            imgui.EndTabBar()
        end
    end
    imgui.End()
end)

-- ══════════════════════════════════════════════════════
--  Main
-- ══════════════════════════════════════════════════════
function main()
    loadConfig()
    syncGroupColorVecs()
    syncCheckboxes()
    rebuildColorMap()

    while not isSampAvailable() do wait(1000) end
    msg('{aaaaaa}Конфиг загружен.')
    sampLoaded = true
    sampAddChatMessage(PREFIX .. cp('loaded  {aaaaaa}/whadmin'), 0xFFFFFF)
    sampAddChatMessage(AUTHOR, 0xFFFFFF)

    sampRegisterChatCommand('whadmin', function()
        menuOpen[0] = not menuOpen[0]
        if menuOpen[0] then
            syncCheckboxes()
            msg('Меню открыто.')
        else
            msg('Меню закрыто.')
        end
    end)

    sampRegisterChatCommand('checkskin', function(args)
        local targetId = tonumber(args)
        if not targetId then
            local ok0, mx, my, mz = pcall(getCharCoordinates, playerPed)
            for id, p in pairs(players) do
                if doesCharExist(p.ped) then
                    local ok, x, y, z = pcall(getCharCoordinates, p.ped)
                    local dist = ok and math.sqrt((x-mx)^2+(y-my)^2+(z-mz)^2) or 0
                    local gtaSkin  = getCharModel(p.ped)
                    local sampSkin = getSampSkinId(id)
                    local ptr      = getCharPointer(p.ped)
                    local memSkin  = 0
                    if ptr and ptr ~= 0 then
                        local ok2, v = pcall(memory.getint16, ptr + 0x22, false)
                        if ok2 then memSkin = v end
                    end
                    print(string.format('[checkskin] id=%d name=%s gta=%d samp=%s mem=%d dist=%.0fm',
                        id, p.name, gtaSkin, tostring(sampSkin), memSkin, dist))
                end
            end
        else
            local p = players[targetId]
            if not p or not doesCharExist(p.ped) then
                print('[checkskin] player '..targetId..' not found')
                return
            end
            local gtaSkin  = getCharModel(p.ped)
            local sampSkin = getSampSkinId(targetId)
            local ptr      = getCharPointer(p.ped)
            print(string.format('[checkskin] id=%d name=%s', targetId, p.name))
            print('  getCharModel   = '..gtaSkin)
            print('  getSampSkinId = '..tostring(sampSkin))
            local effectiveSkin = sampSkin or gtaSkin
            print('  effective skin used for color = '..tostring(effectiveSkin))
            print('  color in map = '..tostring(skinColorMap[effectiveSkin]))
            print('  >> /addskin '..tostring(effectiveSkin)..' GroupName  to add to a group')
            if ptr and ptr ~= 0 then
                for _, off in ipairs({0x20, 0x22, 0x24, 0x26, 0x28, 0x2A}) do
                    local ok, v = pcall(memory.getint16, ptr + off, false)
                    print(string.format('  ptr+0x%X = %s', off, ok and tostring(v) or 'err'))
                end
            end
        end
    end)

    sampRegisterChatCommand('addskin', function(args)
        local skinId, groupName = args:match('(%d+)%s+(.*)')
        if not skinId then
            sampAddChatMessage(PREFIX .. '/addskin {faa9ff}<skinId> <groupName>', 0xFFFFFF)
            return
        end
        skinId = tonumber(skinId)
        for _, g in ipairs(cfg.skinGroups) do
            if g.name == groupName then
                g.ids = g.ids .. ',' .. skinId
                rebuildColorMap()
                syncGroupColorVecs()
                saveConfig()
                msg(string.format('skin {faa9ff}%d{ffffff} добавлен в группу {faa9ff}%s', skinId, groupName))
                return
            end
        end
        table.insert(cfg.skinGroups, {
            name  = groupName,
            ids   = tostring(skinId),
            color = {1.0, 1.0, 1.0, 1.0},
        })
        rebuildColorMap()
        syncGroupColorVecs()
        saveConfig()
        msg(string.format('новая группа {faa9ff}%s{ffffff} создана со скином {faa9ff}%d', groupName, skinId))
    end)

    lua_thread.create(function()
        while true do
            wait(500)
            for id = 0, 1000 do
                if sampIsPlayerConnected(id) then
                    local ok, ped = sampGetCharHandleBySampPlayerId(id)
                    if ok and doesCharExist(ped) and ped ~= playerPed then
                        local skin = getPlayerSkin(id, ped)
                        if not players[id] then
                            players[id] = { ped=ped, name=sampGetPlayerNickname(id), skin=skin }
                        else
                            players[id].ped  = ped
                            players[id].skin = skin
                        end
                    end
                else
                    players[id] = nil
                end
            end
        end
    end)

    while true do wait(1000) end
end
да знаю, просто впадлу, и если нейронка косякнет разбираться в этом(