CEF Chatпуызыри

MSIshka

Участник
Автор темы
68
4
Версия SA-MP
  1. Любая
Пытаюсь создать CEF chatbubbles, но столкнулся с проблемой, что они не прикрепляются к персонажу, а пытаются его догнать. Если не двигать мышкой и не бегать, то все нормально. Но стоит сделать хоть какое-то движение, то chatbubbles начинает догонять голову персонажа. Возможно ли это как-то фиксануть?

Lua:
function sampev.onPlayerChatBubble(playerId, color, distance, duration, message)
    if not query[playerId] then
        query[playerId] = {}
    end
    
    local a, r, g, b = explode_argb(color)
    local newMessage = {
        message = u8:encode(message),
        create_time = os.clock(),
        duration = duration / 1000,
        distance = distance,
        r = r,
        g = g,
        b = b,
        a = a
    }
    
    table.insert(query[playerId], 1, newMessage)
    
    while #query[playerId] > 1 do
        table.remove(query[playerId])
    end

    lua_thread.create(function ()
        while true do
            wait(0)
            if not browser then return end

            local current_time = os.clock()
            local bubblesData = {}
        
            for playerId, messages in pairs(query) do
                local res, handle = sampGetCharHandleBySampPlayerId(tonumber(playerId))
                if handle and doesCharExist(handle) and wallPlayer(handle, messages[1] and messages[1].distance or 50) then
                    local x, y, z = getNameTagPosForText(handle)
                    local screenX, screenY = convert3DCoordsToScreen(x, y, z)
                    
                    local playerBubbles = {}
                    
                    for i, data in ipairs(messages) do
                        local elapsed = current_time - data.create_time
                        
                        if elapsed > data.duration then
                            table.remove(messages, i)
                        else
                            
                            table.insert(playerBubbles, {
                                text = data.message,
                                r = data.r,
                                g = data.g,
                                b = data.b,
                                alpha = 1.0,
                                index = i - 1
                            })
                        end
                    end
                    
                    if #playerBubbles > 0 then
                        table.insert(bubblesData, {
                            playerId = playerId,
                            x = screenX,
                            y = screenY,
                            messages = playerBubbles
                        })
                    end
                end
            end
        
            if browser then
                local jsonData = encodeJson(bubblesData)
                browser:execute_js(string.format("updateBubbles(%s)", jsonData))
            end
        end
    end)

    return false
end

local getBonePosition = ffi.cast("int (__thiscall*)(void*, float*, int, bool)", 0x5E4280)
function getBodyPartCoordinates(id, handle)
    local pedptr = getCharPointer(handle)
    local vec = ffi.new("float[3]")
    getBonePosition(ffi.cast("void*", pedptr), vec, id, true)
    return vec[0], vec[1], vec[2]
end

function getRealCameraCoordinates()
    local CCamera = ffi.cast("float*", 0xB6F028)
    return CCamera[0x20F], CCamera[0x210], CCamera[0x211]
end

function getNameTagPosForText(handle)
    local localPlayerPos = vector3d(getRealCameraCoordinates())
    local pPlayerPos = vector3d(getBodyPartCoordinates(5, handle))
    return pPlayerPos.x, pPlayerPos.y,
        pPlayerPos.z + 0.37 +
        (getDistanceBetweenCoords3d(localPlayerPos.x, localPlayerPos.y, localPlayerPos.z,
         pPlayerPos.x, pPlayerPos.y, pPlayerPos.z) * 0.025)
end

function wallPlayer(handle, distance)
    if doesCharExist(handle) then
        local camX, camY, camZ = getRealCameraCoordinates()
        local x, y, z = getCharCoordinates(handle)
        local maxDistance = false and distance or 50
        local withinDistance = getDistanceBetweenCoords3d(camX, camY, camZ, x, y, z) <= maxDistance

        if not (withinDistance and isCharOnScreen(handle)) then
            return false
        end

        return false or isLineOfSightClear(camX, camY, camZ, x, y, z, true, false, false, true, false)
    end
end


JavaScript:
const bubblesContainer = document.getElementById('bubbles-container');
const activeBubbles = new Map();

function getMessageDiv(wrapper, index) {
    let el = wrapper.children[index];
    
    if (!el) {
        el = document.createElement('div');
        el.className = 'bubble-message';
        wrapper.appendChild(el);
    }

    return el;
}

function updateBubbles(bubblesData) {
    const currentIds = new Set();

    bubblesData.forEach(playerData => {
        const key = `player_${playerData.playerId}`;
        currentIds.add(key);

        let wrapper = activeBubbles.get(key);

        if (!wrapper) {
            wrapper = document.createElement('div');
            wrapper.className = 'bubble-wrapper';
            bubblesContainer.appendChild(wrapper);
            activeBubbles.set(key, wrapper);
        }

        wrapper.style.left = playerData.x + 'px';
        wrapper.style.top  = playerData.y + 'px';

        const count = playerData.messages.length;
        wrapper.dataset.count = count;

        for (let i = 0; i < count; i++) {
            const msg = playerData.messages[i];
            const messageDiv = getMessageDiv(wrapper, i);

            messageDiv.style.display = 'block';
            messageDiv.style.color   = `rgb(${msg.r}, ${msg.g}, ${msg.b})`;
            messageDiv.style.opacity = msg.alpha;
            messageDiv.innerHTML     = msg.text;

            messageDiv.style.marginBottom = (i < count - 1) ? '8px' : '0';

            messageDiv.style.textShadow = `-1px -1px 0 #000`;
        }

        const children = wrapper.children;
        for (let i = count; i < children.length; i++) {
            children[i].style.display = 'none';
        }
    });

    activeBubbles.forEach((wrapper, key) => {
        if (!currentIds.has(key)) {
            wrapper.remove();
            activeBubbles.delete(key);
        }
    });
}