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