используя примеры из моего кода

local ffi = require 'ffi'

-- Structure definition for CVector, as it is used in the function
ffi.cdef[[
typedef struct {
float x;
float y;
float z;
} CVector;
]]

--HOOKS
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

-- Define the function address for CPlayerTags::DrawHealthBar
local drawHealthBarFuncAddress = getModuleHandle('samp.dll') + 0x689C0
local drawHealthBarFunc = ffi.cast("void(__thiscall*)(void*, CVector*, float, float, float)", drawHealthBarFuncAddress)

local getBonePositionFunc = ffi.cast("int (__thiscall*)(void*, float*, int, bool)", 0x5E4280)

-- Получить координаты части тела
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

-- Define the CPlayerTags instance
local CPlayerTags = ffi.cast("void*", 0x04388ee0)

--GET SIZE
-- Объявляем структуру SIZE
ffi.cdef[[
typedef struct {
long cx;
long cy;
} SIZE;

void* GetModuleHandleA(const char* moduleName);
]]

-- Базовый адрес модуля SAMP
local function sampGetBase()
return tonumber(ffi.cast("uintptr_t", ffi.C.GetModuleHandleA("samp.dll")))
end

-- Предположим, у нас есть следующие оффсеты из структуры samp_addressess
local OFFSET_CFONTS = 0x21A0FC -- замените реальным значением
local OFFSET_MEASURE_TEXT_FUNC = 0x66BD0 -- замените реальным значением

-- Основная функция, аналог sampGetMeasuredTextSize в C++
local function sampGetMeasuredTextSize(text)
local base = sampGetBase()
if base == 0 then
error("Не удалось получить базовый адрес модуля SAMP.")
end


-- Получаем указатель на глобальную переменную CFonts
local g_fonts_address = ffi.cast("void**", base + OFFSET_CFONTS)
local g_fonts = ffi.cast("void*", g_fonts_address[0])

-- Определяем функцию измерения текста
local measureTextFunc = ffi.cast("void(__thiscall*)(void*, SIZE*, const char*, int)", base + OFFSET_MEASURE_TEXT_FUNC)

-- Структура для размера текста
local size = ffi.new("SIZE")

-- Вызов функции
measureTextFunc(g_fonts, size, text, bit.bor(0x0000, 0x400)) -- DT_LEFT | DT_NOCLIP

return size
end
--GET SIZE

function f(x)
return 0.84 + 0.0216625 * (x - 10)
end

local textId = 2014

function CPedRenderHook(obj)
local charPointer = tonumber(ffi.cast("uintptr_t", obj))


    local handle    = getCharPointerHandle(charPointer)
    local res, id   = sampGetPlayerIdByCharHandle(handle)
    
    if id == 67 then -- == playerPed then   
            if sampIs3dTextDefined(textId) then
                    sampDestroy3dText(textId)
            end
            
            local charX, charY, charZ = getCharCoordinates(handle)
            local limbX, limbY, limbZ = getBodyPartCoordinates(8, handle)
            
            local cameraX, cameraY, cameraZ = getActiveCameraCoordinates()
            local cameraDistance = getDistanceBetweenCoords3d(charX, charY, charZ, cameraX, cameraY, cameraZ)
            
            local text = "{ffffff}pururin (30)"
            local measuredSize = sampGetMeasuredTextSize(text)
            
            local offse = f(measuredSize.cy)
            
            local position = ffi.new("CVector")
            position.x, position.y, position.z = limbX, limbY, limbZ
            
            renderSetRenderState(19, 2) -- D3DRS_SRCBLEND to D3DBLEND_SRCALPHA
    renderSetRenderState(20, 3) -- D3DRS_DESTBLEND to D3DBLEND_INVSRCALPHA
    renderSetRenderState(27, 1) -- D3DRS_ALPHABLENDENABLE to TRUE
            
            drawHealthBarFunc(CPlayerTags, position, 56.0, 0.0, cameraDistance)
            
            limbZ = limbZ + 0.21 + ((cameraDistance * 0.047499999) * offse)
            sampCreate3dTextEx(textId, text, 0xFFFFFFFF, limbX, limbY, limbZ, 600, false, -1, -1)
    end
    
    return CPedRender(obj)
end

function main()
local renderFuncAddress = 0x5E7680 -- Address for CPed::Render


CPedRender = hook.new(
    'void(__thiscall *)(void *obj)', 
    CPedRenderHook, 
    renderFuncAddress
)
end

напиши вызов C(си) функций через FFI

//----- (10068670) --------------------------------------------------------
IDirect3DStateBlock9 *__thiscall CPlayerTags::EndHealthBar(CPlayerTags *this)
{
ID3DXSprite *m_pSprite; // eax
IDirect3DStateBlock9 *result; // eax

m_pSprite = this->m_pSprite;
if ( m_pSprite )
(*(void (__stdcall **)(ID3DXSprite ))((_DWORD *)m_pSprite + 44))(m_pSprite);
result = this->m_pStates;
if ( result )
return (IDirect3DStateBlock9 *)result->lpVtbl->Apply(result);
return result;
}

//----- (10068FD0) --------------------------------------------------------
HRESULT __thiscall CPlayerTags::BeginHealthBar(CPlayerTags *this)
{
IDirect3DStateBlock9 **p_m_pStates; // edi
ID3DXSprite *m_pSprite; // eax
ID3DXSprite *v4; // eax

p_m_pStates = &this->m_pStates;
if ( this->m_pStates )
goto LABEL_6;
m_pSprite = this->m_pSprite;
if ( m_pSprite )
(*(void (__stdcall **)(ID3DXSprite ))((_DWORD *)m_pSprite + 52))(this->m_pSprite);
if ( *p_m_pStates
|| (this->m_pDevice->lpVtbl->CreateStateBlock(this->m_pDevice, D3DSBT_ALL, p_m_pStates), *p_m_pStates) )
{
LABEL_6:
(*p_m_pStates)->lpVtbl->Capture(p_m_pStates);
}
v4 = this->m_pSprite;
if ( v4 )
((void (__stdcall **)(ID3DXSprite , int))((_DWORD *)v4 + 32))(v4, 1);
this->m_pDevice->lpVtbl->SetRenderState(this->m_pDevice, D3DRS_ALPHABLENDENABLE, 1);
this->m_pDevice->lpVtbl->SetRenderState(this->m_pDevice, D3DRS_SRCBLEND, 5);
this->m_pDevice->lpVtbl->SetRenderState(this->m_pDevice, D3DRS_DESTBLEND, 6);
this->m_pDevice->lpVtbl->SetRenderState(this->m_pDevice, D3DRS_ALPHATESTENABLE, 0);
this->m_pDevice->lpVtbl->SetTextureStageState(this->m_pDevice, 0, D3DTSS_COLOROP, 3);
this->m_pDevice->lpVtbl->SetTextureStageState(this->m_pDevice, 0, D3DTSS_COLORARG2, 0);
this->m_pDevice->lpVtbl->SetTextureStageState(this->m_pDevice, 0, D3DTSS_ALPHAOP, 2);
this->m_pDevice->lpVtbl->SetTextureStageState(this->m_pDevice, 0, D3DTSS_ALPHAARG1, 0);
this->m_pDevice->lpVtbl->SetVertexShader(this->m_pDevice, 0);
this->m_pDevice->lpVtbl->SetPixelShader(this->m_pDevice, 0);
return this->m_pDevice->lpVtbl->SetRenderState(this->m_pDevice, D3DRS_ZENABLE, 0);
}
вот этих