local ffi = require 'ffi'

-- FFI Definitions
ffi.cdef[[
typedef struct {
    float x;
    float y;
    float z;
} CVector;

typedef struct {
    long cx;
    long cy;
} SIZE;
]]


--HOOKS 
-- https://www.blast.hk/threads/39138
local hook = {hooks = {}}
addEventHandler('onScriptTerminate', function(scr)
    if scr == script.this then
        for i, hook in ipairs(hook.hooks) do
            if hook.status then
                hook.stop()
            end
        end
    end
end)
ffi.cdef [[
    int VirtualProtect(void* lpAddress, unsigned long dwSize, unsigned long flNewProtect, unsigned long* lpflOldProtect);
]]
function hook.new(cast, callback, hook_addr, size)
    jit.off(callback, true) --off jit compilation | thx FYP
    local size = size or 5
    local new_hook = {}
    local detour_addr = tonumber(ffi.cast('intptr_t', ffi.cast('void*', ffi.cast(cast, callback))))
    local void_addr = ffi.cast('void*', hook_addr)
    local old_prot = ffi.new('unsigned long[1]')
    local org_bytes = ffi.new('uint8_t[?]', size)
    ffi.copy(org_bytes, void_addr, size)
    local hook_bytes = ffi.new('uint8_t[?]', size, 0x90)
    hook_bytes[0] = 0xE9
    ffi.cast('uint32_t*', hook_bytes + 1)[0] = detour_addr - hook_addr - 5
    new_hook.call = ffi.cast(cast, hook_addr)
    new_hook.status = false
    local function set_status(bool)
        new_hook.status = bool
        ffi.C.VirtualProtect(void_addr, size, 0x40, old_prot)
        ffi.copy(void_addr, bool and hook_bytes or org_bytes, size)
        ffi.C.VirtualProtect(void_addr, size, old_prot[0], old_prot)
    end
    new_hook.stop = function() set_status(false) end
    new_hook.start = function() set_status(true) end
    new_hook.start()
    table.insert(hook.hooks, new_hook)
    return setmetatable(new_hook, {
        __call = function(self, ...)
            self.stop()
            local res = self.call(...)
            self.start()
            return res
        end
    })
end
--HOOKS


-- Constants
local SAMP_BASE = getModuleHandle('samp.dll')
local DRAW_HEALTH_BAR_FUNC_ADDRESS = SAMP_BASE + 0x689C0
local END_HEALTH_BAR_FUNC_ADDRESS = SAMP_BASE + 0x68670
local SAMP_OFFSET = {0x68610, 0x70F96, 0x68F70, 0x68FA0, 0x21A0FC, 0x66E00, 0x66BD0, 0x12C980, 0x12C940, 0x21A0EC, 0x65F10, 0x9A7D0, 0x70E10, 0x6FD00}

-- Function Signatures
local drawHealthBarFunc = ffi.cast("void(__thiscall*)(void*, CVector*, float, float, float)", DRAW_HEALTH_BAR_FUNC_ADDRESS)
local getBonePositionFunc = ffi.cast("int (__thiscall*)(void*, float*, int, bool)", 0x5E4280)

-- Global Variables
local textId = 2014
local position = ffi.new("CVector")
local size = ffi.new("SIZE")

-- Helper Function to Measure Text Size
-- раньше крашила игру в рандомные моменты... требует тестов короч, вот так вот бля :(
-- _DWORD *__thiscall CFonts::GetLittleTextScreenSize(CFonts *this, _DWORD *pSize, const char *szText, int nFormat)
local function sampGetMeasuredTextSize(text)
    local getTextSizeFuncAddr = SAMP_BASE + SAMP_OFFSET[7]
    local getTextSizeFunc = ffi.cast("void (__thiscall*)(void*, SIZE*, const char*, int)", getTextSizeFuncAddr)
    local fontsInstancePtr = ffi.cast("void**", SAMP_BASE + SAMP_OFFSET[5])[0]
    
    size.cx, size.cy = 0, 0
    local flags = bit.bor(0x00000000, 0x00000100) -- DT_LEFT and DT_NOCLIP
    getTextSizeFunc(fontsInstancePtr, size, text, flags)
    
    return size
end

-- Correction Function for Vertical Alignment
local function verticalAlignmentCorrection(x)
    return 0.84 + 0.0216625 * (x - 10)
end

-- Get Body Part Coordinates
local function getBodyPartCoordinates(boneId, pedHandle)
    local pedPointer = getCharPointer(pedHandle)
    local coordinates = ffi.new("float[3]")
    getBonePositionFunc(ffi.cast("void*", pedPointer), coordinates, boneId, true)
    return coordinates[0], coordinates[1], coordinates[2]
end

-- Hook for customizing EndHealthBar
local function EndHealthBarHook(playerTags)
    if sampIs3dTextDefined(textId) then
        sampDestroy3dText(textId)
    end
    
    if sampIs3dTextDefined(textId + 1) then
        sampDestroy3dText(textId + 1)
    end
    
    local charX, charY, charZ		= getCharCoordinates(playerPed)
    local limbX, limbY, limbZ		= getBodyPartCoordinates(8, playerPed)
	
    local cameraX, cameraY, cameraZ	= getActiveCameraCoordinates()
    local cameraDistance			= getDistanceBetweenCoords3d(charX, charY, charZ, cameraX, cameraY, cameraZ)
	
	local result, id				= sampGetPlayerIdByCharHandle(playerPed)
	if not result then
		print("sampGetPlayerIdByCharHandle error in EndHealthBarHook"); return EndHealthBar(playerTags)
	end

	local nickname					= sampGetPlayerNickname(id)
	local color						= sampGetPlayerColor(id)
	
    local text						= string.format("%s (%d)", nickname, id) --"{ffffff}pururin (30)"
    local measuredSize				= sampGetMeasuredTextSize(text)
    local offset					= verticalAlignmentCorrection(measuredSize.cy)
    
    position.x,
	position.y,
	position.z						= limbX, limbY, limbZ
	
	local health					= sampGetPlayerHealth(id)
	local armor						= sampGetPlayerArmor(id)
	
    drawHealthBarFunc(playerTags, position, health, armor, cameraDistance)
    
    limbZ = limbZ + 0.21 + ((cameraDistance * 0.047499999) * offset)
    local limbZ2 = limbZ + ((cameraDistance * 0.047499999) * offset) / 2
    
    sampCreate3dTextEx(textId, text, color, limbX, limbY, limbZ, 600, false, -1, -1)
    sampCreate3dTextEx(textId + 1, "{d0aeeb} * смотрите, я ролевик", 0xFFFFFFFF, limbX, limbY, limbZ2, 600, false, -1, -1)
    
    return EndHealthBar(playerTags)
end

-- Main Function to Initialize the Hook
function main()
    while not isPlayerPlaying(playerPed) do
        wait(100)
    end
    
    EndHealthBar = hook.new(
        'void* (__thiscall *)(void *this)',
        EndHealthBarHook,
        END_HEALTH_BAR_FUNC_ADDRESS
    )
end

добавь МАКСИМУМ дебажных принтов. не меняй сам код