DeathBoardClient = DeathBoardClient or {} local Constants = DeathBoardClient.Constants or {} local Layout = Constants.Layout or {} local Colors = Constants.Colors or {} DeathBoardClient.RowsPerPage = DeathBoardClient.RowsPerPage or 6 DeathBoardClient.CurrentPage = DeathBoardClient.CurrentPage or 1 DeathBoardClient.TotalPages = DeathBoardClient.TotalPages or 1 DeathBoardClient.Rows = DeathBoardClient.Rows or {} DeathBoardClient.IsLoading = DeathBoardClient.IsLoading or false DeathBoardClient.LastPageRowCount = DeathBoardClient.LastPageRowCount or 0 DeathBoardClient.HUDKill = DeathBoardClient.HUDKill or {} DeathBoardClient.Features = DeathBoardClient.Features or { ServerColumn = false } DeathBoardClient.Cooldown = DeathBoardClient.Cooldown or { Open = 0, Page = 0 } local function DB2LogUI(msg) local dbg = DeathBoardClient and DeathBoardClient.Config and DeathBoardClient.Config.Debug if not (dbg and dbg.Enabled) then return end local now = (DB2_NowMs and DB2_NowMs()) or (os.clock and math.floor(os.clock() * 1000)) or 0 local last = DeathBoardClient.__dbg_last_ts or 0 local cnt = DeathBoardClient.__dbg_count or 0 if now - last >= 1000 then DeathBoardClient.__dbg_last_ts = now DeathBoardClient.__dbg_count = 0 cnt = 0 end if cnt >= 20 then return end DeathBoardClient.__dbg_count = cnt + 1 if Console then Console(1, "[DeathBoard][TRACE] " .. tostring(msg)) end end local function DB2_NowMs() if GetTime then local ok, t = pcall(GetTime) if ok and t then return t end end if os.clock then return math.floor(os.clock() * 1000) end return 0 end local function SafeGLColor4(r, g, b, a) if glColor4f then glColor4f(r, g, b, a) elseif SetColor then local R = math.floor((r or 1) * 255) local G = math.floor((g or 1) * 255) local B = math.floor((b or 1) * 255) local A = math.floor((a or 1) * 255) SetColor(R, G, B, A) end end local function SafeGLColor3(r, g, b) if glColor3f then glColor3f(r, g, b) end end local function DB2_SafeCall(fn, ...) local ok = false if type(fn) == "function" then ok = pcall(fn, ...) end if not ok then DeathBoardClient.__degraded = true end return ok end local function DB2_LogOnce(tag, msg) local dbg = DeathBoardClient and DeathBoardClient.Config and DeathBoardClient.Config.Debug if not (dbg and dbg.Enabled) then return end DeathBoardClient.__once = DeathBoardClient.__once or {} if not DeathBoardClient.__once[tag] then DB2LogUI(msg) DeathBoardClient.__once[tag] = true end end local function DB2_NormalizeHex64(hex) if type(hex) ~= "string" then return "" end local h = tostring(hex or "") h = string.gsub(h, "[^0-9a-fA-F]", "") if (#h % 2) == 1 then h = "0" .. h end if #h < 64 then h = string.rep("0", 64 - #h) .. h elseif #h > 64 then h = string.sub(h, #h - 64 + 1) end return h end local function HasImage(id) if DeathBoardClient.__panic_no_images or DeathBoardClient.__degraded or DeathBoardClient.__no_images then return false end return type(id) == "number" and id > 0 end local MAX_PAGES = 15 DeathBoardClient.Cache = DeathBoardClient.Cache or { pages = {}, totalPages = 0 } local function DB2_CopyRowCache(r) if not r then return nil end return { KillerName = r.KillerName or "", VictimName = r.VictimName or "", TimeText = r.TimeText or "", MapName = r.MapName or "", KillerClassKey = r.KillerClassKey or "SM", VictimClassKey = r.VictimClassKey or "DL", KillerGuildName = r.KillerGuildName or "", KillerGuildMarkHex = r.KillerGuildMarkHex or "", VictimGuildName = r.VictimGuildName or "", VictimGuildMarkHex = r.VictimGuildMarkHex or "", ServerCode = r.ServerCode or 0 } end local function DB2_ShowCachedPage(page) local c = DeathBoardClient.Cache and DeathBoardClient.Cache.pages and DeathBoardClient.Cache.pages[page] if not c or not c.rows then return false end local out = {} for i=1,#c.rows do out[i] = DB2_CopyRowCache(c.rows[i]) end DeathBoardClient.Rows = out DeathBoardClient.LastPageRowCount = c.count or #out DeathBoardClient.SetPagination(page, DeathBoardClient.Cache.totalPages or DeathBoardClient.TotalPages or 1) DeathBoardClient.IsLoading = false return true end DeathBoardClient.ServerNameMapLoaded = DeathBoardClient.ServerNameMapLoaded or false local function DB2_TryLoadServerNames() if DeathBoardClient.ServerNameMapLoaded then return end local names = {} local list = DeathBoardClient.Config and DeathBoardClient.Config.Servers if list and type(list) == "table" then for _, srv in pairs(list) do local id = tonumber(srv and srv.ID) local nm = srv and srv.Name if id ~= nil and nm and nm ~= "" then names[id] = tostring(nm) end end end if next(names) == nil then names[0] = "Server 1" names[1] = "Server 2" end DeathBoardClient.ServerNames = names DeathBoardClient.ServerNameMapLoaded = true end DB2_TryLoadServerNames() local function DB2_CopyArray(a) local r = {} for i=1,#a do r[i]=a[i] end return r end local function DB2_ComputeLayout(x, y, width) local tbar = Layout.TitleBar or {} local baseBarW = tbar.Width or 346 local baseInnerW = tbar.InnerWidth or 314 local baseColW = math.floor(baseInnerW / 5) local baseWidths = { baseColW, baseColW, baseColW, baseColW, baseInnerW - baseColW * 4 } local edge = baseBarW - baseInnerW local serverOn = DeathBoardClient.Features and DeathBoardClient.Features.ServerColumn local colWidths, barW, innerW if serverOn then local w6 = DB2_CopyArray(baseWidths) w6[6] = baseWidths[5] colWidths = w6 innerW = baseInnerW + baseWidths[5] barW = innerW + edge else colWidths = baseWidths innerW = baseInnerW barW = baseBarW end local barX = x + math.floor((width - barW) / 2) local innerX = barX + math.floor((barW - innerW) / 2) return { barW=barW, innerW=innerW, barX=barX, innerX=innerX, barH=tbar.Height or 23, colWidths=colWidths } end local function DB2_SafeLayout(x, y, width) local ok, res = pcall(DB2_ComputeLayout, x, y, width) if ok and res and res.colWidths then return res end local tbar = Layout.TitleBar or {} local barW = tbar.Width or 346 local innerW = tbar.InnerWidth or 314 local baseColW = math.floor(innerW / 5) local colWidths = { baseColW, baseColW, baseColW, baseColW, innerW - baseColW * 4 } local barX = x + math.floor((width - barW) / 2) local innerX = barX + math.floor((barW - innerW) / 2) return { barW=barW, innerW=innerW, barX=barX, innerX=innerX, barH=tbar.Height or 23, colWidths=colWidths } end local function DB2_ComputeRowLayout(x, y, width, height) local L = DB2_SafeLayout(x, y, width) local rowLayout = Layout.Row or {} local rowH = rowLayout.Height or 18 local gapTop = (rowLayout.GapTopFromTitleBar or 2) + (rowLayout.TopExtra or 2) local baseGap = rowLayout.GapBetweenRows or 2 local rowsPerPage = DeathBoardClient.RowsPerPage or 6 local pag = Layout.Pagination or {} local pageBtnSize = pag.ButtonSize or 17 local bottomOffset = pag.BottomOffset or 15 local bottomMin = pag.BottomMin or 8 local barYOffset = (Layout.TitleBar and Layout.TitleBar.YOffset) or 46 local barH = (Layout.TitleBar and Layout.TitleBar.Height) or 23 local barY = y + barYOffset local bottomY = y + height local pageY = bottomY - bottomOffset - pageBtnSize local available = pageY - (barY + barH) - gapTop - bottomMin if available < 0 then available = 0 end local totalRowsH = rowsPerPage * rowH local remaining = available - totalRowsH local gapBetween = baseGap if rowsPerPage > 1 and remaining >= 0 then gapBetween = math.max(baseGap, math.floor(remaining / (rowsPerPage - 1))) end local baseRowY = barY + barH + gapTop return { rowH=rowH, gapTop=gapTop, gapBetween=gapBetween, baseRowY=baseRowY } end local ExclusiveWindows = { 1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 20, 18, 19, 21, 33, 35, 36, 57, 65, 73, 74, 75, 77, 78, 121 } DeathBoardClient.Window = DeathBoardClient.Window or {} DeathBoardClient.ButtonOpenState = DeathBoardClient.ButtonOpenState or 0 DeathBoardClient.ButtonCloseState = DeathBoardClient.ButtonCloseState or 0 DeathBoardClient.ButtonPagePrevState = DeathBoardClient.ButtonPagePrevState or 0 DeathBoardClient.ButtonPageNextState = DeathBoardClient.ButtonPageNextState or 0 local Window = DeathBoardClient.Window Window.IsOpen = Window.IsOpen or false local function IsCharacterLoggedIn() if GetMainScene then local okScene, scene = pcall(GetMainScene) if not okScene or scene ~= 5 then return false end return true end if GetViewMaxHP then local okHp, maxHP = pcall(GetViewMaxHP) if okHp and (maxHP == nil or maxHP <= 0) then return false end end if GetMap then local okMap, m = pcall(GetMap) if okMap and (m == nil or m == -1) then return false end end local name = "" if GetMyName then local okName, n = pcall(GetMyName) if okName and n then name = n end end if name == "" and UserGetName then local okUser, n = pcall(UserGetName) if okUser and n then name = n end end if name == "" and GetPlayerName then local okPlayer, n = pcall(GetPlayerName) if okPlayer and n then name = n end end if name == "" then return false end return true end local function IsSmartWindowOpen() if SmartWindowClient and SmartWindowClient.CheckOpen and SmartWindowClient.CheckOpen() then return true end if UICustomInterface == 121 then return true end return false end local function RenderOpenButton() local cfg = DeathBoardClient.Config and DeathBoardClient.Config.Button or {} local btnW = cfg.Width if btnW == nil then btnW = (Constants.Window and Constants.Window.ButtonOpenW) or 25 end local btnH = cfg.Height if btnH == nil then btnH = (Constants.Window and Constants.Window.ButtonOpenH) or 25 end local btnX = cfg.X if btnX == nil then btnX = (Constants.Window and Constants.Window.ButtonOpenX) or 80 end local btnY = cfg.Y if btnY == nil then btnY = (Constants.Window and Constants.Window.ButtonOpenY) or 20 end local btnImg = Constants.Images and Constants.Images.BTN_OPEN local btnGroup = Constants.UV and Constants.UV.ButtonOpen local btnState = DeathBoardClient.ButtonOpenState or 0 local btnUVs if btnGroup then if btnState == 2 and btnGroup.Pressed then btnUVs = btnGroup.Pressed elseif btnState == 1 and btnGroup.Hover then btnUVs = btnGroup.Hover else btnUVs = btnGroup.Normal or btnGroup.Hover or btnGroup.Pressed end end if RenderImage2 and HasImage(btnImg) and btnUVs then SafeGLColor4(1.0, 1.0, 1.0, 1.0) RenderImage2(btnImg, btnX, btnY, btnW, btnH, btnUVs.U, btnUVs.V, btnUVs.UW, btnUVs.VH, 1, 1, 1.0) elseif RenderImage and HasImage(btnImg) then SafeGLColor3(1.0, 1.0, 1.0) RenderImage(btnImg, btnX, btnY, btnW, btnH) end end local function GetClassStyle(key) if key == "BK" then return {1.0, 0.2, 0.2}, "Blade Knight" elseif key == "SM" then return {0.2, 0.5, 1.0}, "Soul Master" elseif key == "ELF" then return {0.2, 1.0, 0.2}, "Muse Elf" elseif key == "MG" then return {1.0, 0.4, 1.0}, "Magic Gladiator" elseif key == "DL" then return {1.0, 0.8, 0.2}, "Dark Lord" elseif key == "SU" then return {1.0, 0.2, 0.8}, "Summoner" elseif key == "RF" then return {0.5, 0.5, 0.5}, "Rage Fighter" end return {1.0, 1.0, 1.0}, "Unknown" end local GuildMarkPalette = { {0, 0, 0, 0}, {0, 0, 0, 255}, {128, 128, 128, 255}, {255, 255, 255, 255}, {255, 0, 0, 255}, {255, 165, 0, 255}, {255, 255, 0, 255}, {128, 255, 0, 255}, {0, 255, 0, 255}, {0, 255, 128, 255}, {0, 255, 255, 255}, {0, 128, 255, 255}, {0, 0, 255, 255}, {128, 0, 255, 255}, {255, 0, 255, 255}, {255, 0, 128, 255} } local function DecodeGuildMarkHex(hex) if type(hex) ~= "string" then return nil end local h = tostring(hex or "") h = string.gsub(h, "[^0-9a-fA-F]", "") if (#h % 2) == 1 then h = "0" .. h end if #h < 64 then h = string.rep("0", 64 - #h) .. h elseif #h > 64 then h = string.sub(h, #h - 64 + 1) end if #h ~= 64 or not string.match(h, "^[0-9a-fA-F]+$") then return nil end -- Se o hex for todo zero, considerar como "sem guild": não desenhar nada if string.match(h, "^0+$") then return nil end local pixels = {} local pIndex = 1 for i = 1, 64, 2 do local byteHex = string.sub(h, i, i + 1) local byte = tonumber(byteHex, 16) if not byte then return nil end local p1 = math.floor(byte / 16) local p2 = byte % 16 pixels[pIndex] = p1 pixels[pIndex + 1] = p2 pIndex = pIndex + 2 end -- Se todos os pixels forem zero, também tratar como "sem guild" local allZero = true for i=1,64 do if (pixels[i] or 0) ~= 0 then allZero = false break end end if allZero then return nil end return pixels end local function EnsureGuildPixels(rowData, fieldHexName, fieldPixelsName) if not rowData then return nil end local cached = rowData[fieldPixelsName] if cached then return cached end local hex = rowData[fieldHexName] local pixels = DecodeGuildMarkHex(hex) rowData[fieldPixelsName] = pixels return pixels end local function RenderGuildMarkPixels(pixels, x, y, size, alpha) if not pixels or not alpha or alpha <= 0 then return end if not DrawBar or not glColor4f then return end if SetBlend then SetBlend(1) end -- Desenhar borda de 2px (Preta) glColor4f(0.0, 0.0, 0.0, alpha) DrawBar(x - 2, y - 2, size + 4, size + 4) local cellSize = size / 8 local idx = 1 for py = 0, 7 do local cellY = y + py * cellSize for px = 0, 7 do local colorIndex = pixels[idx] or 0 idx = idx + 1 if colorIndex >= 0 and colorIndex <= 15 then local c = GuildMarkPalette[colorIndex + 1] if c and c[4] > 0 then glColor4f(c[1] / 255.0, c[2] / 255.0, c[3] / 255.0, (c[4] / 255.0) * alpha) local cellX = x + px * cellSize DrawBar(cellX, cellY, cellSize, cellSize) end end end end if EndDrawBar then EndDrawBar() end if SetBlend then SetBlend(0) end end local function RenderKillHud() local list = DeathBoardClient.HUDKill if not list or #list == 0 then return end local img = Constants.Images and Constants.Images.HUD_KILL_TGA local uv = Constants.UV and Constants.UV.HUDKill local uvClass = Constants.UV and (Constants.UV.HUDClassIcon or Constants.UV.ClassIcon) if not img or not uv or not HasImage(img) then return end local now = (os.clock and os.clock()) or 0 local hudScale = 1.0 do local cfg = DeathBoardClient.Config and DeathBoardClient.Config.HUD local s = cfg and cfg.Scale s = tonumber(s) if s and s > 0 and s <= 2 then hudScale = s end end local baseX = 40 local baseY = 300 local width = math.floor(250 * hudScale) local height = math.floor(49 * hudScale) local spacing = math.max(1, math.floor(4 * hudScale)) local enterDuration = 0.4 local holdDuration = 2.0 local exitDuration = 0.35 local maxItems = 3 local alive = {} local count = #list if count > maxItems then local startIndex = count - maxItems + 1 local trimmed = {} local ti = 1 for i = startIndex, count do trimmed[ti] = list[i] ti = ti + 1 end list = trimmed count = #list end local hadBlend = false if SetBlend and glColor4f then SetBlend(1) hadBlend = true end for i = 1, count do local item = list[i] local startTime = item.StartTime or now local t = now - startTime if t < 0 then t = 0 end local totalDuration = enterDuration + holdDuration + exitDuration if t < totalDuration then local index = #alive + 1 local targetY = baseY + (index - 1) * (height + spacing) local x = math.floor(baseX) local y = math.floor(targetY) local alpha = 1.0 if t < enterDuration then local p = t / enterDuration if p < 0 then p = 0 end if p > 1 then p = 1 end alpha = p local startOffset = math.floor(30 * hudScale) y = math.floor(targetY + (startOffset * (1.0 - p))) elseif t < enterDuration + holdDuration then alpha = 1.0 else local p = (t - enterDuration - holdDuration) / exitDuration if p < 0 then p = 0 end if p > 1 then p = 1 end alpha = 1.0 - p local exitOffset = math.floor(150 * hudScale) x = math.floor(baseX - (exitOffset * p)) end if alpha > 0 then if RenderImage2 then if glColor4f then glColor4f(1.0, 1.0, 1.0, alpha) end RenderImage2(img, x, y, width, height, uv.U, uv.V, uv.UW, uv.VH, 1, 1, 1.0) elseif RenderImage then if glColor4f then glColor4f(1.0, 1.0, 1.0, alpha) elseif glColor3f then glColor3f(alpha, alpha, alpha) end RenderImage(img, x, y, width, height) end if uvClass and RenderImage2 then local sizeClass = math.max(12, math.floor(48 * hudScale)) local halfClass = math.floor(sizeClass / 2) local centerY = y + math.floor((height - sizeClass) / 2) local leftCenterX = x local rightCenterX = x + width local leftX = leftCenterX - halfClass local rightX = rightCenterX - halfClass local killerKey = item.KillerClassKey or "SM" local victimKey = item.VictimClassKey or "DL" local imgKiller = Constants.Images and (Constants.Images["CLASS_" .. tostring(killerKey) .. "_TGA"] or Constants.Images["CLASS_" .. tostring(killerKey)]) local imgVictim = Constants.Images and (Constants.Images["CLASS_" .. tostring(victimKey) .. "_TGA"] or Constants.Images["CLASS_" .. tostring(victimKey)]) local pulse = 0.7 + 0.3 * math.sin(now * 8) -- Pulsa alpha entre 0.4 e 1.0 if imgKiller and HasImage(imgKiller) then -- Efeito Zoom Impact local scale = 1.0 if t < 0.3 then scale = 1.0 + (1.0 - (t / 0.3)) end local drawSize = math.floor(sizeClass * scale) local drawX = leftCenterX - math.floor(drawSize / 2) local drawY = centerY + math.floor((sizeClass - drawSize) / 2) RenderImage2(imgKiller, drawX, drawY, drawSize, drawSize, uvClass.U, uvClass.V, uvClass.UW, uvClass.VH, 1, 1, 1.0) end if imgVictim and HasImage(imgVictim) then local scale = 1.0 if t < 0.3 then scale = 1.0 + (1.0 - (t / 0.3)) end local drawSize = math.floor(sizeClass * scale) local drawX = rightCenterX - math.floor(drawSize / 2) local drawY = centerY + math.floor((sizeClass - drawSize) / 2) RenderImage2(imgVictim, drawX, drawY, drawSize, drawSize, uvClass.U, uvClass.V, uvClass.UW, uvClass.VH, 1, 1, 1.0) end local killer = item.KillerName or "" local victim = item.VictimName or "" local alphaInt = math.floor(255 * alpha) if alphaInt < 0 then alphaInt = 0 end if alphaInt > 255 then alphaInt = 255 end if SetFontType and SetTextColor and SetTextBg and RenderText3 then SetTextBg(0, 0, 0, 0) local textY = y + math.floor(height / 2) local skullCenterX = x + math.floor(width / 2) local skullOffset = math.floor(20 * hudScale) -- Killer local killerAreaStartX = leftX + sizeClass local killerAreaEndX = skullCenterX - skullOffset local killerAreaWidth = killerAreaEndX - killerAreaStartX if killer ~= "" then local centerX_Killer = killerAreaStartX + math.floor(killerAreaWidth / 2) -- Nome agora BRANCO conforme solicitado SetFontType(3) SetTextColor(255, 255, 255, alphaInt) RenderText3(centerX_Killer, textY, killer, killerAreaWidth, 8) end -- Victim local victimAreaStartX = skullCenterX + skullOffset local victimAreaEndX = rightX local victimAreaWidth = victimAreaEndX - victimAreaStartX if victim ~= "" then local centerX_Victim = victimAreaStartX + math.floor(victimAreaWidth / 2) -- Nome agora BRANCO conforme solicitado SetFontType(3) SetTextColor(255, 255, 255, alphaInt) RenderText3(centerX_Victim, textY, victim, victimAreaWidth, 8) end end -- Renderização das Guild Marks local sizeGuild = math.max(8, math.floor(16 * hudScale)) local guildPixelsKiller = EnsureGuildPixels(item, "KillerGuildMarkHex", "KillerGuildPixels") local guildPixelsVictim = EnsureGuildPixels(item, "VictimGuildMarkHex", "VictimGuildPixels") if guildPixelsKiller then local gx = leftCenterX + math.floor(8 * hudScale) local gy = y + height - sizeGuild RenderGuildMarkPixels(guildPixelsKiller, gx, gy, sizeGuild, alpha) end if guildPixelsVictim then local gx = rightCenterX - math.floor(8 * hudScale) - sizeGuild local gy = y + height - sizeGuild RenderGuildMarkPixels(guildPixelsVictim, gx, gy, sizeGuild, alpha) end end end alive[#alive + 1] = item end end DeathBoardClient.HUDKill = alive if glColor4f then glColor4f(1.0, 1.0, 1.0, 1.0) elseif glColor3f then glColor3f(1.0, 1.0, 1.0) end if hadBlend and SetBlend then SetBlend(0) end end local function EnsureExclusiveWindows(smartWindowOpen) if not Window.IsOpen then return end if DeathBoardClient.IsLoading then return end local mustClose = false if CheckWindowOpen then for _, w in ipairs(ExclusiveWindows) do if CheckWindowOpen(w) == 1 then mustClose = true break end end end if smartWindowOpen then mustClose = true end if mustClose then DeathBoardClient.Close() end end local function GetWindowRect() local width = (Constants.Window and Constants.Window.Width) or 380 local height = (Constants.Window and Constants.Window.Height) or 251 height = height + 10 if DeathBoardClient.Features and DeathBoardClient.Features.ServerColumn then local tbar = Layout.TitleBar or {} local baseBarW = tbar.Width or 346 local factor = 6 / 5 local scaledBarW = math.floor(baseBarW * factor) local desiredW = scaledBarW + 20 if desiredW > width then width = desiredW end local hf = 1.06 height = math.floor(height * hf) + 4 end local centerX = (Constants.GetCenterX and Constants.GetCenterX(width)) or 0 local centerY = (Constants.GetCenterY and Constants.GetCenterY(height)) or 0 local x = centerX local y = centerY DB2LogUI(string.format("Window x=%s y=%s w=%s h=%s", tostring(x), tostring(y), tostring(width), tostring(height))) return x, y, width, height end local function RenderLoadingOverlay(x, y, width, height) local barW = 136 local barH = 2 local barX = x + math.floor((width - barW) / 2) local barY = y + math.floor(height / 2) local p = DeathBoardClient.LoadingProgress or 0 if p < 90 then p = p + 3 if p > 90 then p = 90 end DeathBoardClient.LoadingProgress = p end if DrawBar then if SetBlend then SetBlend(1) end SafeGLColor4(0.1, 0.1, 0.1, 1.0) DrawBar(barX, barY, barW, barH) SafeGLColor4(1.0, 0.5, 0.0, 1.0) DrawBar(barX, barY, barW * (p / 100), barH) if EndDrawBar then EndDrawBar() end SafeGLColor4(1.0, 1.0, 1.0, 1.0) if SetBlend then SetBlend(0) end end if RenderText3 and SetFontType and SetTextColor and SetTextBg then SetFontType(3) SetTextColor(255, 255, 255, 255) SetTextBg(0, 0, 0, 0) local percentText = tostring(p) .. "%" RenderText3(x + math.floor(width / 2), barY + 8, percentText, 100, 8) end end local function RenderBackground(x, y, width, height) local bgId = Constants.Images and Constants.Images.BACKGROUND local uvBg = Constants.UV and Constants.UV.Background local layoutTitleBar = Layout.TitleBar or {} local barW = layoutTitleBar.Width or 346 local barH = layoutTitleBar.Height or 23 local barYOffset = layoutTitleBar.YOffset or 46 local innerW = layoutTitleBar.InnerWidth or 314 if DeathBoardClient.Features and DeathBoardClient.Features.ServerColumn then local factor = 6 / 5 barW = math.floor(barW * factor) innerW = math.floor(innerW * factor) end local bgW = barW + 40 local bgX = x + math.floor((width - bgW) / 2) if RenderImage2 and HasImage(bgId) and uvBg then SafeGLColor4(1.0, 1.0, 1.0, 1.0) RenderImage2(bgId, bgX, y, bgW, height, uvBg.U, uvBg.V, uvBg.UW, uvBg.VH, 1, 1, 1.0) elseif RenderImage and HasImage(bgId) then SafeGLColor3(1.0, 1.0, 1.0) RenderImage(bgId, bgX, y, bgW, height) end DB2LogUI(string.format("Background x=%s y=%s w=%s h=%s barW=%s bgW=%s", tostring(bgX), tostring(y), tostring(bgW), tostring(height), tostring(barW), tostring(bgW))) end local function RenderTitleBarAndColumns(x, y, width) local titleBarImg = Constants.Images and Constants.Images.TITLE_BAR local uvTitleBar = Constants.UV and Constants.UV.TitleBar if not titleBarImg or not uvTitleBar then return end local layoutTitleBar = Layout.TitleBar or {} local L = DB2_SafeLayout(x, y, width) local barW = L.barW local barH = layoutTitleBar.Height or 23 local barYOffset = layoutTitleBar.YOffset or 46 local innerW = L.innerW local barX = L.barX local barY = y + barYOffset if RenderImage2 and HasImage(titleBarImg) and uvTitleBar then glColor4f(1.0, 1.0, 1.0, 1.0) RenderImage2(titleBarImg, barX, barY, barW, barH, uvTitleBar.U, uvTitleBar.V, uvTitleBar.UW, uvTitleBar.VH, 1, 1, 1.0) elseif RenderImage and HasImage(titleBarImg) then glColor3f(1.0, 1.0, 1.0) RenderImage(titleBarImg, barX, barY, barW, barH) end local innerX = L.innerX local columnsLayout = Layout.Columns or {} local colWidths = L.colWidths local colCount = #colWidths do local t = {} for i=1,#colWidths do t[#t+1] = tostring(colWidths[i]) end DB2LogUI(string.format("TitleBar barX=%s barY=%s barW=%s barH=%s innerX=%s innerW=%s cols=%s widths=[%s]", tostring(barX), tostring(barY), tostring(barW), tostring(barH), tostring(innerX), tostring(innerW), tostring(colCount), table.concat(t, ","))) end local colTitles = columnsLayout.Titles or { "KILLER", "VICTIM", "CLASS", "TIME", "MAP" } if DeathBoardClient.Features and DeathBoardClient.Features.ServerColumn then colTitles = { colTitles[1], colTitles[2], colTitles[3], colTitles[4], colTitles[5], "SERVER" } end local cfgText = DeathBoardClient.Config and DeathBoardClient.Config.TextKeys if cfgText and cfgText.Columns then if DeathBoardClient.Features and DeathBoardClient.Features.ServerColumn then colTitles = { cfgText.Columns.Killer or colTitles[1], cfgText.Columns.Victim or colTitles[2], cfgText.Columns.Class or colTitles[3], cfgText.Columns.Time or colTitles[4], cfgText.Columns.Map or colTitles[5], (cfgText.Columns.Server or "SERVER") } else colTitles = { cfgText.Columns.Killer or colTitles[1], cfgText.Columns.Victim or colTitles[2], cfgText.Columns.Class or colTitles[3], cfgText.Columns.Time or colTitles[4], cfgText.Columns.Map or colTitles[5] } end end local textOffsetY = columnsLayout.TitleOffsetY or 8 local textY = barY + textOffsetY local headerColor = Colors.HeaderGold or { 237, 214, 161, 255 } SetFontType(1) SetTextBg(0, 0, 0, 0) SetTextColor(headerColor[1], headerColor[2], headerColor[3], headerColor[4]) if DrawBar and glColor4f then local sepLayout = Layout.Separators or {} local sepH = sepLayout.Height or 10 local sepW = sepLayout.Width or 0.5 local sepAlpha = sepLayout.Alpha or 0.7 local sepY = barY + math.floor((barH - sepH) / 2) if SetBlend then SetBlend(1) end glColor4f(headerColor[1] / 255.0, headerColor[2] / 255.0, headerColor[3] / 255.0, sepAlpha) local currentX = innerX for i = 1, colCount - 1 do local colW = colWidths[i] local sepX = math.floor(currentX + colW) DrawBar(sepX, sepY, sepW, sepH) currentX = currentX + colW end if EndDrawBar then EndDrawBar() end if SetBlend then SetBlend(0) end end local currentX = innerX for i = 1, colCount do local colW = colWidths[i] local centerX = currentX + math.floor(colW / 2) if i == 1 then centerX = centerX + (columnsLayout.TitleXOffsetFirst or -2) elseif i == colCount then centerX = centerX + (columnsLayout.TitleXOffsetLast or 2) end local cx = math.floor(centerX) local ty = math.floor(textY) DB2LogUI(string.format("TitleTxt i=%s cx=%s y=%s w=%s title=\"%s\"", tostring(i), tostring(cx), tostring(ty), tostring(colW), tostring(colTitles[i] or ""))) if RenderText3 then RenderText3(cx, ty, colTitles[i], colW, 8) elseif RenderText2 then RenderText2(cx, ty, colTitles[i], colW, 8) elseif RenderText then RenderText(cx, ty, colTitles[i], colW, 1) end currentX = currentX + colW end end local function RenderRowBackground(x, y, width, rowIndex) local titleBarImg = Constants.Images and Constants.Images.TITLE_BAR local uvTitleBar = Constants.UV and Constants.UV.TitleBar if not titleBarImg or not uvTitleBar then return end local L = DB2_SafeLayout(x, y, width) local layoutTitleBar = Layout.TitleBar or {} local barH = layoutTitleBar.Height or 23 local barYOffset = layoutTitleBar.YOffset or 46 local barW = L.barW local innerW = L.innerW local barX = L.barX local barY = y + barYOffset local innerX = L.innerX local rowLayout = Layout.Row or {} local rl = DeathBoardClient.__rows_layout local rowH = (rl and rl.rowH) or (rowLayout.Height or 18) local rowGapY = (rl and rl.gapTop) or (rowLayout.GapTopFromTitleBar or 2) local rowGapBetween = (rl and rl.gapBetween) or (rowLayout.GapBetweenRows or 2) local rowIndexValue = rowIndex or 1 local baseRowY = (rl and rl.baseRowY) or (barY + barH + rowGapY) local rowY = baseRowY + (rowIndexValue - 1) * (rowH + rowGapBetween) local hoverIndex = DeathBoardClient.HoverRowIndex or 0 local isHover = (hoverIndex == rowIndexValue) local drawRowH = rowH local drawRowY = rowY local columnsLayout = Layout.Columns or {} local colWidths = L.colWidths local colCount = #colWidths local leftExtra = innerX - barX local rightExtra = (barX + barW) - (innerX + innerW) local gap = 1 local cellImg = Constants.Images and Constants.Images.BG_INFO local uvCell = Constants.UV and Constants.UV.BgInfo if not cellImg or not uvCell or not HasImage(cellImg) then return end local currentX = barX for i = 1, colCount do local colW = colWidths[i] local fullW = colW if i == 1 then fullW = colW + leftExtra elseif i == colCount then fullW = colW + rightExtra end local drawX = math.floor(currentX) local drawW if i == colCount then drawW = (barX + barW) - drawX else drawW = math.floor(fullW - gap) end if drawW < 0 then drawW = 0 end if RenderImage2 then local baseAlpha = 1.0 local hoverAlpha = 1.0 local alpha = isHover and hoverAlpha or baseAlpha glColor4f(1.0, 1.0, 1.0, alpha) RenderImage2(cellImg, drawX, drawRowY, drawW, drawRowH, uvCell.U, uvCell.V, uvCell.UW, uvCell.VH, 1, 1, 1.0) elseif RenderImage then glColor3f(1.0, 1.0, 1.0) RenderImage(cellImg, drawX, drawRowY, drawW, drawRowH) end currentX = currentX + drawW if i < colCount then currentX = currentX + gap end DB2LogUI(string.format("RowBG r=%s cell=%s x=%s w=%s", tostring(rowIndexValue), tostring(i), tostring(drawX), tostring(drawW))) end end local function RenderRowVerticalSeparators(x, y, width, rowIndex) if not DrawBar or not glColor4f then return end local layoutTitleBar = Layout.TitleBar or {} local barW = layoutTitleBar.Width or 346 local barH = layoutTitleBar.Height or 23 local barYOffset = layoutTitleBar.YOffset or 46 local innerW = layoutTitleBar.InnerWidth or 314 if DeathBoardClient.Features and DeathBoardClient.Features.ServerColumn then local factor = 6 / 5 barW = math.floor(barW * factor) innerW = math.floor(innerW * factor) end local barX = x + math.floor((width - barW) / 2) local barY = y + barYOffset local innerX = barX + math.floor((barW - innerW) / 2) local rowLayout = Layout.Row or {} local rl = DeathBoardClient.__rows_layout local rowH = (rl and rl.rowH) or (rowLayout.Height or 18) local rowGapY = (rl and rl.gapTop) or (rowLayout.GapTopFromTitleBar or 2) local rowGapBetween = (rl and rl.gapBetween) or (rowLayout.GapBetweenRows or 2) local rowIndexValue = rowIndex or 1 local baseRowY = (rl and rl.baseRowY) or (barY + barH + rowGapY) local rowY = baseRowY + (rowIndexValue - 1) * (rowH + rowGapBetween) local drawRowH = rowH local drawRowY = math.floor(rowY) local columnsLayout = Layout.Columns or {} local colCount = (DeathBoardClient.Features and DeathBoardClient.Features.ServerColumn) and 6 or (columnsLayout.Count or 5) local baseColW = math.floor(innerW / colCount) local colWidths = {} local remaining = innerW for i = 1, colCount - 1 do colWidths[i] = baseColW remaining = remaining - baseColW end colWidths[colCount] = remaining local sepLayout = Layout.Separators or {} local sepW = sepLayout.Width or 0.5 local sepAlpha = sepLayout.Alpha or 0.7 local gap = 1 local sepH = drawRowH - (gap * 2) local sepY = drawRowY + gap local headerColor = Colors.HeaderGold or { 237, 214, 161, 255 } if SetBlend then SetBlend(1) end glColor4f(headerColor[1] / 255.0, headerColor[2] / 255.0, headerColor[3] / 255.0, sepAlpha) local currentX = innerX for i = 1, colCount - 1 do local colW = colWidths[i] local sepX = math.floor(currentX + colW) DrawBar(sepX, sepY, sepW, sepH) currentX = currentX + colW DB2LogUI(string.format("RowSep r=%s i=%s x=%s h=%s", tostring(rowIndexValue), tostring(i), tostring(sepX), tostring(sepH))) end if EndDrawBar then EndDrawBar() end if SetBlend then SetBlend(0) end end local function RenderRowHorizontalSeparator(x, y, width, rowIndex) if not DrawBar or not glColor4f then return end local layoutTitleBar = Layout.TitleBar or {} local barW = layoutTitleBar.Width or 346 local barH = layoutTitleBar.Height or 23 local barYOffset = layoutTitleBar.YOffset or 46 local innerW = layoutTitleBar.InnerWidth or 314 if DeathBoardClient.Features and DeathBoardClient.Features.ServerColumn then local factor = 6 / 5 barW = math.floor(barW * factor) innerW = math.floor(innerW * factor) end local barX = x + math.floor((width - barW) / 2) local barY = y + barYOffset local innerX = barX + math.floor((barW - innerW) / 2) local rowLayout = Layout.Row or {} local rl = DeathBoardClient.__rows_layout local rowH = (rl and rl.rowH) or (rowLayout.Height or 18) local rowGapY = (rl and rl.gapTop) or (rowLayout.GapTopFromTitleBar or 2) local rowGapBetween = (rl and rl.gapBetween) or (rowLayout.GapBetweenRows or 2) local rowIndexValue = rowIndex or 1 local baseRowY = (rl and rl.baseRowY) or (barY + barH + rowGapY) local rowY = baseRowY + (rowIndexValue - 1) * (rowH + rowGapBetween) local sepLayout = Layout.Separators or {} local thickness = sepLayout.Width or 0.5 local sepAlpha = sepLayout.Alpha or 0.7 local gapCenter = rowY + rowH + (rowGapBetween / 2.0) local sepY = math.floor(gapCenter - (thickness / 2.0)) if SetBlend then SetBlend(1) end local gray = 0.55 glColor4f(gray, gray, gray, sepAlpha) DrawBar(barX, sepY, barW, thickness) if EndDrawBar then EndDrawBar() end if SetBlend then SetBlend(0) end end local function RenderRowClassIcons(x, y, width, rowIndex) local titleBarImg = Constants.Images and Constants.Images.TITLE_BAR local uvTitleBar = Constants.UV and Constants.UV.TitleBar if not titleBarImg or not uvTitleBar then return end local layoutTitleBar = Layout.TitleBar or {} local L = DB2_SafeLayout(x, y, width) local barW = L.barW local barH = layoutTitleBar.Height or 23 local barYOffset = layoutTitleBar.YOffset or 46 local barX = L.barX local barY = y + barYOffset local innerW = L.innerW local innerX = L.innerX local rowLayout = Layout.Row or {} local rl = DeathBoardClient.__rows_layout local rowH = (rl and rl.rowH) or (rowLayout.Height or 18) local rowGapY = (rl and rl.gapTop) or (rowLayout.GapTopFromTitleBar or 2) local rowGapBetween = (rl and rl.gapBetween) or (rowLayout.GapBetweenRows or 2) local rowIndexValue = rowIndex or 1 local baseRowY = (rl and rl.baseRowY) or (barY + barH + rowGapY) local rowY = baseRowY + (rowIndexValue - 1) * (rowH + rowGapBetween) local drawRowH = rowH local drawRowY = rowY local columnsLayout = Layout.Columns or {} local colCount = (DeathBoardClient.Features and DeathBoardClient.Features.ServerColumn) and 6 or (columnsLayout.Count or 5) local baseColW = math.floor(innerW / colCount) local colWidths = {} local remaining = innerW for i = 1, colCount - 1 do colWidths[i] = baseColW remaining = remaining - baseColW end colWidths[colCount] = remaining local leftExtra = innerX - barX local rightExtra = (barX + barW) - (innerX + innerW) local gap = 1 local classIndex = 3 local currentX = barX for i = 1, classIndex - 1 do local colW = colWidths[i] local fullW = colW if i == 1 then fullW = colW + leftExtra elseif i == colCount then fullW = colW + rightExtra end local drawWPrev = math.floor(fullW - gap) currentX = currentX + drawWPrev + gap end local colW = colWidths[classIndex] local fullW = colW if classIndex == 1 then fullW = colW + leftExtra elseif classIndex == colCount then fullW = colW + rightExtra end local drawX = math.floor(currentX) local drawW = math.floor(fullW - gap) local iconsLayout = Layout.Icons or {} local sizeClass = iconsLayout.ClassSize or 14 local sizeKill = iconsLayout.KillSize or 14 local gapIcons = iconsLayout.Gap or 2 local killImg = Constants.Images and Constants.Images.KILL_ICON local uvKill = Constants.UV and Constants.UV.KillIcon local uvClass = Constants.UV and Constants.UV.ClassIcon if not killImg or not uvKill or not uvClass or not HasImage(killImg) then return end local classLeftImg local classRightImg local rows = DeathBoardClient.Rows or {} local rowData = rows[rowIndexValue] if rowData and Constants.Images then local killerKey = rowData.KillerClassKey or "SM" local victimKey = rowData.VictimClassKey or "DL" classLeftImg = Constants.Images["CLASS_" .. tostring(killerKey)] classRightImg = Constants.Images["CLASS_" .. tostring(victimKey)] end if not classLeftImg or not classRightImg then local rowIdx = rowIndex or 1 if rowIdx == 1 then classLeftImg = Constants.Images and Constants.Images.CLASS_SM classRightImg = Constants.Images and Constants.Images.CLASS_DL else classLeftImg = Constants.Images and Constants.Images.CLASS_ELF classRightImg = Constants.Images and Constants.Images.CLASS_RF end end if not classLeftImg or not classRightImg then return end local centerX = drawX + math.floor(drawW / 2) local killX = centerX - math.floor(sizeKill / 2) local killY = drawRowY + math.floor((drawRowH - sizeKill) / 2) local leftX = killX - gapIcons - sizeClass local rightX = killX + sizeKill + gapIcons local classY = drawRowY + math.floor((drawRowH - sizeClass) / 2) DB2LogUI(string.format("Icons r=%s drawX=%s drawW=%s killX=%s leftX=%s rightX=%s y=%s", tostring(rowIndexValue or 0), tostring(drawX), tostring(drawW), tostring(killX), tostring(leftX), tostring(rightX), tostring(classY))) if RenderImage2 and HasImage(classLeftImg) and HasImage(classRightImg) then glColor4f(1.0, 1.0, 1.0, 1.0) RenderImage2(classLeftImg, leftX, classY, sizeClass, sizeClass, uvClass.U, uvClass.V, uvClass.UW, uvClass.VH, 1, 1, 1.0) RenderImage2(killImg, killX, killY, sizeKill, sizeKill, uvKill.U, uvKill.V, uvKill.UW, uvKill.VH, 1, 1, 1.0) RenderImage2(classRightImg, rightX, classY, sizeClass, sizeClass, uvClass.U, uvClass.V, uvClass.UW, uvClass.VH, 1, 1, 1.0) elseif RenderImage and HasImage(classLeftImg) and HasImage(classRightImg) then glColor3f(1.0, 1.0, 1.0) RenderImage(classLeftImg, leftX, classY, sizeClass, sizeClass) RenderImage(killImg, killX, killY, sizeKill, sizeKill) RenderImage(classRightImg, rightX, classY, sizeClass, sizeClass) end end local function RenderRowGuildMarks(x, y, width, rowIndex) local layoutTitleBar = Layout.TitleBar or {} local L = DB2_SafeLayout(x, y, width) local barW = L.barW local barH = layoutTitleBar.Height or 23 local barYOffset = layoutTitleBar.YOffset or 46 local barX = L.barX local barY = y + barYOffset local innerW = L.innerW local innerX = L.innerX local rowLayout = Layout.Row or {} local rl = DeathBoardClient.__rows_layout local rowH = (rl and rl.rowH) or (rowLayout.Height or 18) local rowGapY = (rl and rl.gapTop) or (rowLayout.GapTopFromTitleBar or 2) local rowGapBetween = (rl and rl.gapBetween) or (rowLayout.GapBetweenRows or 2) local rowIndexValue = rowIndex or 1 local baseRowY = (rl and rl.baseRowY) or (barY + barH + rowGapY) local rowY = baseRowY + (rowIndexValue - 1) * (rowH + rowGapBetween) local drawRowH = rowH local drawRowY = rowY local columnsLayout = Layout.Columns or {} local colCount = (DeathBoardClient.Features and DeathBoardClient.Features.ServerColumn) and 6 or (columnsLayout.Count or 5) local baseColW = math.floor(innerW / colCount) local colWidths = {} local remaining = innerW for i = 1, colCount - 1 do colWidths[i] = baseColW remaining = remaining - baseColW end colWidths[colCount] = remaining local leftExtra = innerX - barX local rightExtra = (barX + barW) - (innerX + innerW) local gap = 1 local iconsLayout = Layout.Icons or {} local sizeClass = iconsLayout.ClassSize or 14 local sizeGuild = (iconsLayout.GuildSize or (sizeClass - 2)) if sizeGuild < 10 then sizeGuild = 10 end local gapIcons = iconsLayout.Gap or 2 local rows = DeathBoardClient.Rows or {} local rowData = rows[rowIndexValue] if not rowData then return end local killerGuildName = rowData.KillerGuildName or "" local victimGuildName = rowData.VictimGuildName or "" local killerGuildMarkHex = rowData.KillerGuildMarkHex or "" local victimGuildMarkHex = rowData.VictimGuildMarkHex or "" DeathBoardClient.GuildHexCache = DeathBoardClient.GuildHexCache or {} local cache = DeathBoardClient.GuildHexCache local function is_hex64(hex) return type(hex) == "string" and #hex == 64 and string.match(hex, "^[0-9a-fA-F]+$") ~= nil end if cache and type(cache) == "table" then local vn = rowData.VictimName or "" local kn = rowData.KillerName or "" if kn ~= "" and is_hex64(cache[kn]) then killerGuildMarkHex = cache[kn] rowData.KillerGuildMarkHex = killerGuildMarkHex rowData.KillerGuildPixels = nil end if vn ~= "" and is_hex64(cache[vn]) then victimGuildMarkHex = cache[vn] rowData.VictimGuildMarkHex = victimGuildMarkHex rowData.VictimGuildPixels = nil end end local function hasNonBlank(str) return type(str) == "string" and string.find(str, "%S") ~= nil end local function hasValidMark(hex) return DecodeGuildMarkHex(hex) ~= nil end local function hasGuild(name, hex) if not hasNonBlank(name) then return false end return hasValidMark(hex) end local hasKillerGuild = hasGuild(killerGuildName, killerGuildMarkHex) local hasVictimGuild = hasGuild(victimGuildName, victimGuildMarkHex) local function GetColumnRect(index) local currentX = barX for i = 1, index - 1 do local colW = colWidths[i] local fullW = colW if i == 1 then fullW = colW + leftExtra elseif i == colCount then fullW = colW + rightExtra end local drawWPrev = math.floor(fullW - gap) currentX = currentX + drawWPrev + gap end local colW = colWidths[index] local fullW = colW if index == 1 then fullW = colW + leftExtra elseif index == colCount then fullW = colW + rightExtra end local drawX = math.floor(currentX) local drawW = math.floor(fullW - gap) return drawX, drawW end local c1x, c1w = GetColumnRect(1) local c2x, c2w = GetColumnRect(2) local guildY = drawRowY + math.floor((drawRowH - sizeGuild) / 2) local colPad = (Layout.Icons and Layout.Icons.GuildPad) or 8 local victimPad = colPad > 2 and (colPad - 2) or colPad local killerX = c1x + colPad local victimX = c2x + victimPad if killerX < c1x + 1 then killerX = c1x + 1 end if killerX + sizeGuild > c1x + c1w - 1 then killerX = (c1x + c1w - 1) - sizeGuild end if victimX < c2x + 1 then victimX = c2x + 1 end if victimX + sizeGuild > c2x + c2w - 1 then victimX = (c2x + c2w - 1) - sizeGuild end DB2LogUI(string.format("Guild r=%s c1x=%s c1w=%s c2x=%s c2w=%s killerX=%s victimX=%s y=%s", tostring(rowIndexValue), tostring(c1x), tostring(c1w), tostring(c2x), tostring(c2w), tostring(killerX), tostring(victimX), tostring(guildY))) local placeholderId = Constants.Images and Constants.Images.GUILD_PLACEHOLDER local cellSize = (sizeGuild / 8) local killerPixels = hasKillerGuild and EnsureGuildPixels(rowData, "KillerGuildMarkHex", "KillerGuildPixels") or nil local victimPixels = hasVictimGuild and EnsureGuildPixels(rowData, "VictimGuildMarkHex", "VictimGuildPixels") or nil local function pixels_has_visible(pixels) if not pixels then return false end local any = false for i=1,64 do local idx = pixels[i] or 0 local c = GuildMarkPalette[(idx or 0) + 1] if c and c[4] and c[4] > 0 then any = true break end end return any end if hasKillerGuild and (not killerPixels or not pixels_has_visible(killerPixels)) then hasKillerGuild = false end if hasVictimGuild and (not victimPixels or not pixels_has_visible(victimPixels)) then hasVictimGuild = false end if DrawBar and glColor4f then if SetBlend then SetBlend(1) end if killerPixels and hasKillerGuild then local idx = 1 for py = 0, 7 do local cellY = guildY + py * cellSize for px = 0, 7 do local colorIndex = killerPixels[idx] or 0 idx = idx + 1 if colorIndex and colorIndex >= 0 and colorIndex <= 15 then local c = GuildMarkPalette[colorIndex + 1] if c and c[4] and c[4] > 0 then glColor4f(c[1] / 255.0, c[2] / 255.0, c[3] / 255.0, c[4] / 255.0) local cellX = killerX + px * cellSize DrawBar(cellX, cellY, cellSize, cellSize) end end end end -- Cache hex válido visível local kn = rowData.KillerName or "" if kn ~= "" and is_hex64(rowData.KillerGuildMarkHex) then cache[kn] = rowData.KillerGuildMarkHex end elseif hasKillerGuild then glColor4f(0.2, 0.8, 1.0, 1.0) DrawBar(killerX, guildY, sizeGuild, sizeGuild) end if victimPixels and hasVictimGuild then local idx = 1 for py = 0, 7 do local cellY = guildY + py * cellSize for px = 0, 7 do local colorIndex = victimPixels[idx] or 0 idx = idx + 1 if colorIndex and colorIndex >= 0 and colorIndex <= 15 then local c = GuildMarkPalette[colorIndex + 1] if c and c[4] and c[4] > 0 then glColor4f(c[1] / 255.0, c[2] / 255.0, c[3] / 255.0, c[4] / 255.0) local cellX = victimX + px * cellSize DrawBar(cellX, cellY, cellSize, cellSize) end end end end -- Cache hex válido visível local vn = rowData.VictimName or "" if vn ~= "" and is_hex64(rowData.VictimGuildMarkHex) then cache[vn] = rowData.VictimGuildMarkHex end elseif hasVictimGuild then glColor4f(1.0, 0.6, 0.2, 1.0) DrawBar(victimX, guildY, sizeGuild, sizeGuild) end if EndDrawBar then EndDrawBar() end if SetBlend then SetBlend(0) end end if placeholderId and HasImage(placeholderId) then local uvGuild = Constants.UV and Constants.UV.ClassIcon if RenderImage2 and uvGuild then glColor4f(1.0, 1.0, 1.0, 1.0) if not hasKillerGuild then RenderImage2(placeholderId, killerX, guildY, sizeGuild, sizeGuild, uvGuild.U, uvGuild.V, uvGuild.UW, uvGuild.VH, 1, 1, 1.0) end if not hasVictimGuild then RenderImage2(placeholderId, victimX, guildY, sizeGuild, sizeGuild, uvGuild.U, uvGuild.V, uvGuild.UW, uvGuild.VH, 1, 1, 1.0) end elseif RenderImage then glColor3f(1.0, 1.0, 1.0) if not hasKillerGuild then RenderImage(placeholderId, killerX, guildY, sizeGuild, sizeGuild) end if not hasVictimGuild then RenderImage(placeholderId, victimX, guildY, sizeGuild, sizeGuild) end end end end local function RenderRowTexts(x, y, width, rowIndex) local layoutTitleBar = Layout.TitleBar or {} local L = DB2_SafeLayout(x, y, width) local barW = L.barW local barH = layoutTitleBar.Height or 23 local barYOffset = layoutTitleBar.YOffset or 46 local barX = L.barX local barY = y + barYOffset local innerW = L.innerW local innerX = L.innerX local rowLayout = Layout.Row or {} local rl = DeathBoardClient.__rows_layout local rowH = (rl and rl.rowH) or (rowLayout.Height or 18) local rowGapY = (rl and rl.gapTop) or (rowLayout.GapTopFromTitleBar or 2) local rowGapBetween = (rl and rl.gapBetween) or (rowLayout.GapBetweenRows or 2) local rowIndexValue = rowIndex or 1 local baseRowY = (rl and rl.baseRowY) or (barY + barH + rowGapY) local rowY = baseRowY + (rowIndexValue - 1) * (rowH + rowGapBetween) local drawRowH = rowH local drawRowY = rowY local columnsLayout = Layout.Columns or {} local colCount = (DeathBoardClient.Features and DeathBoardClient.Features.ServerColumn) and 6 or (columnsLayout.Count or 5) local baseColW = math.floor(innerW / colCount) local colWidths = {} local remaining = innerW for i = 1, colCount - 1 do colWidths[i] = baseColW remaining = remaining - baseColW end colWidths[colCount] = remaining local leftExtra = innerX - barX local rightExtra = (barX + barW) - (innerX + innerW) local gap = 1 local texts local rows = DeathBoardClient.Rows or {} local rowData = rows[rowIndexValue] if rowData then if DeathBoardClient.Features and DeathBoardClient.Features.ServerColumn then local function getServerLabel(code) DB2_TryLoadServerNames() local names = DeathBoardClient.ServerNames local key = tonumber(code or -1) if names and key and names[key] then return tostring(names[key] or "") end if code == nil then return "" end return "S" .. tostring(code) end texts = { rowData.KillerName or "", rowData.VictimName or "", "", rowData.TimeText or "", rowData.MapName or "", getServerLabel(rowData.ServerCode) } else texts = { rowData.KillerName or "", rowData.VictimName or "", "", rowData.TimeText or "", rowData.MapName or "" } end else if DeathBoardClient.Features and DeathBoardClient.Features.ServerColumn then texts = { "", "", "", "", "", "" } else texts = { "", "", "", "", "" } end end if SetFontType then SetFontType(3) end if SetTextBg then SetTextBg(0, 0, 0, 0) end local white = {255, 255, 255, 255} local killerColor = (Constants.Colors and Constants.Colors.KillerOrange) or {255, 128, 0, 255} local titleColor = (Constants.Colors and Constants.Colors.TitleGold) or {255, 215, 0, 255} local currentX = barX for i = 1, colCount do local colW = colWidths[i] local fullW = colW if i == 1 then fullW = colW + leftExtra elseif i == colCount then fullW = colW + rightExtra end local drawX = math.floor(currentX) local drawW if i == colCount then drawW = (barX + barW) - drawX else drawW = math.floor(fullW - gap) end local marginLeft = 0 if i == 1 or i == 2 then local sizeGuild = (Layout.Icons and Layout.Icons.ClassSize) or 14 local padKiller = (Layout.Icons and Layout.Icons.GuildPad) or 8 local padVictim = padKiller > 2 and (padKiller - 2) or padKiller local hasKIcon = false local hasVIcon = false if rowData then local killerName = rowData.KillerName or "" local victimName = rowData.VictimName or "" local kgn = rowData.KillerGuildName or "" local vgn = rowData.VictimGuildName or "" local kgm = rowData.KillerGuildMarkHex or "" local vgm = rowData.VictimGuildMarkHex or "" local function hasNonBlank(str) return type(str) == "string" and string.find(str, "%S") ~= nil end local function hasValidMark(hex) return DecodeGuildMarkHex(hex) ~= nil end local function hasGuild(name, hex) if not hasNonBlank(name) then return false end return hasValidMark(hex) end hasKIcon = hasGuild(kgn, kgm) or hasNonBlank(killerName) hasVIcon = hasGuild(vgn, vgm) or hasNonBlank(victimName) end if i == 1 and hasKIcon then marginLeft = padKiller + sizeGuild + 2 elseif i == 2 and hasVIcon then marginLeft = padVictim + sizeGuild + 2 end end local drawWAdj = drawW - marginLeft if drawWAdj < 0 then drawWAdj = 0 end local centerX = math.floor(drawX + marginLeft + math.floor(drawWAdj / 2)) local textY = math.floor(drawRowY + math.floor((drawRowH - 10) / 2) + 2) if i ~= 3 then textY = math.floor(textY - 1) end local content = texts[i] or "" DB2LogUI(string.format("Text r=%s c=%s x=%s w=%s ml=%s cx=%s txt=%s", tostring(rowIndexValue), tostring(i), tostring(drawX), tostring(drawW), tostring(marginLeft), tostring(centerX), tostring(content))) if i ~= 3 and content ~= "" and RenderText3 then if SetTextColor then local c if i == 1 then c = killerColor elseif i == 4 then c = titleColor else c = white end SetTextColor(c[1], c[2], c[3], c[4]) end RenderText3(centerX, textY, content, drawWAdj, 8) end currentX = currentX + drawW if i < colCount then currentX = currentX + gap end end end local function RenderWindowTitle(x, y, width) local titleOffsetY = Layout.TitleOffsetY or 17 local L = DB2_SafeLayout(x, y, width) local titleX = L.barX + math.floor(L.barW / 2) local titleY = y + titleOffsetY local titleColor = Colors.TitleGold or { 255, 215, 0, 255 } if SetFontType then SetFontType(1) end if SetTextBg then SetTextBg(0, 0, 0, 0) end if SetTextColor then SetTextColor(titleColor[1], titleColor[2], titleColor[3], titleColor[4]) end local titleText = "DEATH BOARD" local cfgText = DeathBoardClient.Config and DeathBoardClient.Config.TextKeys if cfgText and cfgText.Title then titleText = cfgText.Title end local tx = math.floor(titleX) local ty = math.floor(titleY) DB2LogUI(string.format("WinTitle x=%s y=%s w=%s text=\"%s\"", tostring(tx), tostring(ty), tostring(L.barW), tostring(titleText or ""))) if RenderText3 then RenderText3(tx, ty, titleText, L.barW, 8) elseif RenderText2 then RenderText2(tx, ty, titleText, L.barW, 8) elseif RenderText then RenderText(tx, ty, titleText, L.barW, 1) end end local function RenderCloseButton(x, y, width) local layoutClose = Layout.CloseButton or {} local closeW = layoutClose.Width or 16 local closeH = layoutClose.Height or 16 local closeMarginX = layoutClose.MarginX or 11 local closeMarginY = layoutClose.MarginY or 11 local closeX = x + width - closeW - closeMarginX local closeY = y + closeMarginY local closeImg = Constants.Images and Constants.Images.BTN_CLOSE local closeGroup = Constants.UV and Constants.UV.ButtonClose local closeState = DeathBoardClient.ButtonCloseState or 0 local closeUVs if closeGroup then if closeState == 2 and closeGroup.Pressed then closeUVs = closeGroup.Pressed elseif closeState == 1 and closeGroup.Hover then closeUVs = closeGroup.Hover else closeUVs = closeGroup.Normal or closeGroup.Hover or closeGroup.Pressed end end if RenderImage2 and HasImage(closeImg) and closeUVs then SafeGLColor4(1.0, 1.0, 1.0, 1.0) RenderImage2(closeImg, closeX, closeY, closeW, closeH, closeUVs.U, closeUVs.V, closeUVs.UW, closeUVs.VH, 1, 1, 1.0) elseif RenderImage and HasImage(closeImg) then SafeGLColor3(1.0, 1.0, 1.0) RenderImage(closeImg, closeX, closeY, closeW, closeH) end end local function RenderPagination(x, y, width, height) local layoutPagination = Layout.Pagination or {} local pageBtnSize = layoutPagination.ButtonSize or 17 local spacingBtns = layoutPagination.Spacing or 40 local bottomOffset = layoutPagination.BottomOffset or 15 local bottomY = y + height local pageY = bottomY - bottomOffset - pageBtnSize local centerXBtn = x + math.floor(width / 2) local prevX = centerXBtn - math.floor(spacingBtns / 2) - pageBtnSize local nextX = centerXBtn + math.floor(spacingBtns / 2) local pageUV = Constants.UV and Constants.UV.ButtonPage local imgPrev = Constants.Images and Constants.Images.BTN_PAGE_PREV local imgNext = Constants.Images and Constants.Images.BTN_PAGE_NEXT local prevState = DeathBoardClient.ButtonPagePrevState or 0 local nextState = DeathBoardClient.ButtonPageNextState or 0 local currentPage = DeathBoardClient.CurrentPage or 1 local totalPages = DeathBoardClient.TotalPages or 4 if RenderImage2 and pageUV and imgPrev and imgNext then local uvPrev = pageUV.Normal local uvNext = pageUV.Normal if prevState == 1 and pageUV.Hover then uvPrev = pageUV.Hover end if prevState == 2 and pageUV.Pressed then uvPrev = pageUV.Pressed end if nextState == 1 and pageUV.Hover then uvNext = pageUV.Hover end if nextState == 2 and pageUV.Pressed then uvNext = pageUV.Pressed end glColor4f(1.0, 1.0, 1.0, 1.0) RenderImage2(imgPrev, prevX, pageY, pageBtnSize, pageBtnSize, uvPrev.U, uvPrev.V, uvPrev.UW, uvPrev.VH, 1, 1, 1.0) RenderImage2(imgNext, nextX, pageY, pageBtnSize, pageBtnSize, uvNext.U, uvNext.V, uvNext.UW, uvNext.VH, 1, 1, 1.0) elseif RenderImage and imgPrev and imgNext then glColor3f(1.0, 1.0, 1.0) RenderImage(imgPrev, prevX, pageY, pageBtnSize, pageBtnSize) RenderImage(imgNext, nextX, pageY, pageBtnSize, pageBtnSize) end if SetFontType and SetTextBg and SetTextColor and RenderText3 then SetFontType(3) SetTextBg(0, 0, 0, 0) SetTextColor(255, 255, 255, 255) local text = tostring(currentPage) .. " / " .. tostring(totalPages) local textCenterX = math.floor(centerXBtn) local textY = math.floor(pageY + (pageBtnSize / 2)) DB2LogUI(string.format("PageText cx=%s y=%s w=%s text=\"%s\"", tostring(textCenterX), tostring(textY), tostring(spacingBtns), tostring(text))) RenderText3(textCenterX, textY, text, math.max(10, spacingBtns - 2), 8) end end function DeathBoardClient.CheckOpen() return Window.IsOpen == true end function DeathBoardClient.ClearRows() DeathBoardClient.Rows = {} DeathBoardClient.LastPageRowCount = 0 end function DeathBoardClient.SetRow(rowIndex, killerName, victimName, timeText, mapName, killerClassKey, victimClassKey, killerGuildName, killerGuildMarkHex, victimGuildName, victimGuildMarkHex) if not rowIndex or rowIndex < 1 then return end local rows = DeathBoardClient.Rows or {} local row = rows[rowIndex] or {} row.KillerName = killerName or "" row.VictimName = victimName or "" row.TimeText = timeText or "" local resolvedMapName = mapName or "" local cfg = DeathBoardClient.Config and DeathBoardClient.Config.MapNames if cfg and resolvedMapName ~= "" then local id = tonumber(string.match(resolvedMapName, "^Map#(%d+)$") or "") if id and cfg[id] then resolvedMapName = cfg[id] elseif cfg[resolvedMapName] then resolvedMapName = cfg[resolvedMapName] end end row.MapName = resolvedMapName row.KillerClassKey = killerClassKey or "SM" row.VictimClassKey = victimClassKey or "DL" row.KillerGuildName = killerGuildName or "" row.KillerGuildMarkHex = killerGuildMarkHex or "" row.VictimGuildName = victimGuildName or "" row.VictimGuildMarkHex = victimGuildMarkHex or "" rows[rowIndex] = row DeathBoardClient.Rows = rows local function hasNonBlank(str) return type(str) == "string" and string.find(str, "%S") ~= nil end local function hasValidMark(hex) if type(hex) ~= "string" then return false end if string.len(hex) ~= 64 then return false end if not string.match(hex, "^[0-9a-fA-F]+$") then return false end return true end local function hasGuild(name, hex) if not hasNonBlank(name) then return false end return hasValidMark(hex) end end function DeathBoardClient.SetPagination(currentPage, totalPages) local tp = tonumber(totalPages) or 1 tp = math.max(1, math.min(tp, MAX_PAGES)) local cp = tonumber(currentPage) or 1 cp = math.max(1, math.min(cp, tp)) DeathBoardClient.CurrentPage = cp DeathBoardClient.TotalPages = tp end function DeathBoardClient.ShowKillHud(killerName, victimName, killerClassKey, victimClassKey, killerGuildMarkHex, victimGuildMarkHex) local list = DeathBoardClient.HUDKill or {} local now = (os.clock and os.clock()) or 0 local count = #list if count >= 3 then local trimmed = {} local ti = 1 for i = (count - 3 + 2), count do trimmed[ti] = list[i] ti = ti + 1 end list = trimmed end local item = {} item.KillerName = killerName or "" item.VictimName = victimName or "" item.KillerClassKey = killerClassKey or "SM" item.VictimClassKey = victimClassKey or "DL" item.KillerGuildMarkHex = killerGuildMarkHex or "" item.VictimGuildMarkHex = victimGuildMarkHex or "" item.StartTime = now list[#list + 1] = item DeathBoardClient.HUDKill = list end function DeathBoardClient.RequestPage(page) local proto = Constants.Protocol if not proto then return end if not CreatePacket or not SendPacket or not SetBytePacket or not ClearPacket then return end page = tonumber(page) or 1 if page < 1 then page = 1 end if page > MAX_PAGES then page = MAX_PAGES end local cache = DeathBoardClient.Cache and DeathBoardClient.Cache.pages and DeathBoardClient.Cache.pages[page] local lastEvent = DeathBoardClient.LastEventTs or 0 local cacheTs = cache and cache.ts or 0 local fresh = cache and (cacheTs >= lastEvent) local shown = false if cache then shown = DB2_ShowCachedPage(page) end if fresh then return end local header = proto.Header or 0xFC local nameStr = string.format("DB2-REQ-%d", page) local ok = pcall(function() return CreatePacket(nameStr, header) end) if ok then pcall(function() SendPacket(nameStr) end) pcall(function() ClearPacket(nameStr) end) end if not shown then DeathBoardClient.IsLoading = true DeathBoardClient.LoadingProgress = 0 DeathBoardClient.LoadingStartTs = DB2_NowMs() DB2_LogOnce("req_page", "REQ sent for page "..tostring(page)) end end function DeathBoardClient.Open() local now = DB2_NowMs() local enterTs = DeathBoardClient.EnterWorldTs or 0 if enterTs == 0 or (now - enterTs) < 5000 then return end if not DeathBoardClient.ImagesReady then if DeathBoardClient.LoadImages then DeathBoardClient.LoadImages() end if not DeathBoardClient.ImagesReady then DeathBoardClient.Cooldown.Open = now + 500 return end end local openDelay = 1000 if now < (DeathBoardClient.Cooldown.Open or 0) then return end DeathBoardClient.Cooldown.Open = now + openDelay if DeathBoardClient.CheckOpen and DeathBoardClient.CheckOpen() then return end if CheckWindowOpen and CheckWindowOpen(33) == 1 then return end if CloseAllCustomWindow then CloseAllCustomWindow() end if SmartWindowClient and SmartWindowClient.CheckOpen and SmartWindowClient.CheckOpen() and SmartWindowClient.Close then SmartWindowClient.Close() end if PvPBetClient and PvPBetClient.Modules and PvPBetClient.Modules.PvPBet then local pvpBet = PvPBetClient.Modules.PvPBet if pvpBet.CheckOpen and pvpBet.CheckOpen() and pvpBet.Close then pvpBet.Close() end elseif PvPBet and PvPBet.CheckOpen and PvPBet.Close and PvPBet.CheckOpen() then PvPBet.Close() end if CloseWindow then for _, w in ipairs(ExclusiveWindows) do CloseWindow(w) end end if Constants.Window and Constants.Window.WindowID then UICustomInterface = Constants.Window.WindowID end Window.IsOpen = true DeathBoardClient.ClearRows() DeathBoardClient.SetPagination(1, 1) DeathBoardClient.RequestPage(1) end function DeathBoardClient.Close() if Constants.Window and Constants.Window.WindowID then if UICustomInterface == Constants.Window.WindowID then UICustomInterface = 0 end end Window.IsOpen = false end function DeathBoardClient.Toggle() if Window.IsOpen then DeathBoardClient.Close() else DeathBoardClient.Open() end end function DeathBoardClient.Render() if not IsCharacterLoggedIn() then if Window.IsOpen then DeathBoardClient.Close() end DeathBoardClient.ButtonOpenState = 0 DeathBoardClient.EnterWorldTs = 0 return end if not DeathBoardClient.EnterWorldTs or DeathBoardClient.EnterWorldTs == 0 then DeathBoardClient.EnterWorldTs = DB2_NowMs() end local smartWindowOpen = IsSmartWindowOpen() if not smartWindowOpen then RenderOpenButton() else DeathBoardClient.ButtonOpenState = 0 end EnsureExclusiveWindows(smartWindowOpen) RenderKillHud() if not Window.IsOpen then return end local x, y, width, height = GetWindowRect() DB2LogUI(string.format("AFTER_LOADING rect x=%s y=%s w=%s h=%s", tostring(x or 0), tostring(y or 0), tostring(width or 0), tostring(height or 0))) if DeathBoardClient.IsLoading then local startTs = DeathBoardClient.LoadingStartTs or 0 local nowTs = DB2_NowMs() if startTs > 0 and (nowTs - startTs) > 5000 then DeathBoardClient.IsLoading = false DeathBoardClient.LoadingProgress = 100 DeathBoardClient.LastPageRowCount = DeathBoardClient.LastPageRowCount or 0 end end DB2LogUI("BG start") DB2_SafeCall(RenderBackground, x, y, width, height) DB2LogUI("BG done") DB2LogUI("Title start") DB2_SafeCall(RenderTitleBarAndColumns, x, y, width) DB2LogUI("Title done") DeathBoardClient.__rows_layout = DB2_ComputeRowLayout(x, y, width, height) if DeathBoardClient.__rows_layout then DB2LogUI(string.format("RowLayout h=%s gapTop=%s gapBetween=%s baseY=%s", tostring(DeathBoardClient.__rows_layout.rowH or -1), tostring(DeathBoardClient.__rows_layout.gapTop or -1), tostring(DeathBoardClient.__rows_layout.gapBetween or -1), tostring(DeathBoardClient.__rows_layout.baseRowY or -1))) else DB2LogUI("RowLayout nil") end local rowsPerPage = DeathBoardClient.RowsPerPage or 6 local totalRows = DeathBoardClient.LastPageRowCount or 0 if totalRows < 0 then totalRows = 0 end local drawRows = totalRows if drawRows > rowsPerPage then drawRows = rowsPerPage end DB2LogUI(string.format("Rows draw=%d total=%d perPage=%d isLoading=%s", drawRows or 0, totalRows or 0, rowsPerPage or 0, tostring(DeathBoardClient.IsLoading))) if DeathBoardClient.IsLoading then RenderLoadingOverlay(x, y, width, height) return end if drawRows == 0 then DB2LogUI("Draw empty state") if RenderText3 and SetFontType and SetTextColor and SetTextBg then SetFontType(1) SetTextColor(150, 150, 150, 255) SetTextBg(0, 0, 0, 0) local msg = "Nenhuma morte registrada ainda." local cfgText = DeathBoardClient.Config and DeathBoardClient.Config.TextKeys if cfgText and cfgText.EmptyMessage then msg = cfgText.EmptyMessage end RenderText3(x + math.floor(width / 2), y + math.floor(height / 2), msg, width, 8) end RenderWindowTitle(x, y, width) RenderCloseButton(x, y, width) RenderPagination(x, y, width, height) DB2LogUI("Empty state drawn with pagination") return end DB2LogUI("Rows loop start") for rowIndex = 1, drawRows do DB2LogUI(string.format("Row %d: bg", rowIndex)) DB2_SafeCall(RenderRowBackground, x, y, width, rowIndex) DB2LogUI(string.format("Row %d: vsep", rowIndex)) DB2_SafeCall(RenderRowVerticalSeparators, x, y, width, rowIndex) DB2LogUI(string.format("Row %d: class", rowIndex)) DB2_SafeCall(RenderRowClassIcons, x, y, width, rowIndex) DB2LogUI(string.format("Row %d: texts", rowIndex)) DB2_SafeCall(RenderRowTexts, x, y, width, rowIndex) if rowIndex < drawRows then DB2LogUI(string.format("Row %d: hsep", rowIndex)) DB2_SafeCall(RenderRowHorizontalSeparator, x, y, width, rowIndex) end end DB2LogUI("Rows loop end") DB2_SafeCall(RenderWindowTitle, x, y, width) DB2LogUI("Title re-render done") DB2_SafeCall(RenderCloseButton, x, y, width) DB2LogUI("Close button done") DB2_SafeCall(RenderPagination, x, y, width, height) DB2LogUI("Pagination done") for rowIndex = 1, drawRows do DB2_SafeCall(RenderRowGuildMarks, x, y, width, rowIndex) end DB2LogUI("Guild marks done") end function DeathBoardClient.UpdateMouse() if not IsCharacterLoggedIn() then DeathBoardClient.ButtonOpenState = 0 return 0 end local smartWindowOpen = (SmartWindowClient and SmartWindowClient.CheckOpen and SmartWindowClient.CheckOpen()) or (UICustomInterface == 121) local cfg = DeathBoardClient.Config and DeathBoardClient.Config.Button or {} local btnW = cfg.Width if btnW == nil then btnW = (Constants.Window and Constants.Window.ButtonOpenW) or 25 end local btnH = cfg.Height if btnH == nil then btnH = (Constants.Window and Constants.Window.ButtonOpenH) or 25 end local btnX = cfg.X if btnX == nil then btnX = (Constants.Window and Constants.Window.ButtonOpenX) or 80 end local btnY = cfg.Y if btnY == nil then btnY = (Constants.Window and Constants.Window.ButtonOpenY) or 20 end local mx = (MousePosX and MousePosX()) or (GetMousePosX and GetMousePosX()) or 0 local my = (MousePosY and MousePosY()) or (GetMousePosY and GetMousePosY()) or 0 local consumed = 0 if not smartWindowOpen then local insideOpen = (mx >= btnX and mx <= (btnX + btnW) and my >= btnY and my <= (btnY + btnH)) if insideOpen then if DisableClickClient then DisableClickClient() end if CheckRepeatKey and CheckRepeatKey(1) == 1 then DeathBoardClient.ButtonOpenState = 2 else DeathBoardClient.ButtonOpenState = 1 end consumed = 1 if CheckReleasedKey and CheckReleasedKey(1) == 1 then DeathBoardClient.Toggle() end else DeathBoardClient.ButtonOpenState = 0 end else DeathBoardClient.ButtonOpenState = 0 end if not Window.IsOpen then return consumed end local x, y, width, height = GetWindowRect() local closeW = 16 local closeH = 16 local closeMarginX = 11 local closeMarginY = 11 local closeX = x + width - closeW - closeMarginX local closeY = y + closeMarginY local insideClose = (mx >= closeX and mx <= closeX + closeW and my >= closeY and my <= closeY + closeH) if insideClose then if DisableClickClient then DisableClickClient() end if CheckRepeatKey and CheckRepeatKey(1) == 1 then DeathBoardClient.ButtonCloseState = 2 else DeathBoardClient.ButtonCloseState = 1 end consumed = 1 if CheckReleasedKey and CheckReleasedKey(1) == 1 then DeathBoardClient.Close() end else DeathBoardClient.ButtonCloseState = 0 end if mx >= x and mx <= x + width and my >= y and my <= y + height then if DisableClickClient then DisableClickClient() end consumed = 1 end local layoutTitleBar = Layout.TitleBar or {} local barW = layoutTitleBar.Width or 346 local barH = layoutTitleBar.Height or 23 local barYOffset = layoutTitleBar.YOffset or 46 local innerW = layoutTitleBar.InnerWidth or 314 local barX = x + math.floor((width - barW) / 2) local barY = y + barYOffset local rl = DB2_ComputeRowLayout(x, y, width, height) local rowH = rl.rowH local rowGapBetween = rl.gapBetween local baseRowY = rl.baseRowY local hoverRow = 0 local rowsPerPage = DeathBoardClient.RowsPerPage or 6 local totalRows = DeathBoardClient.LastPageRowCount or 0 if totalRows < 0 then totalRows = 0 end local drawRows = rowsPerPage if totalRows > 0 and totalRows < rowsPerPage then drawRows = totalRows end for rowIndex = 1, drawRows do local rowY = baseRowY + (rowIndex - 1) * (rowH + rowGapBetween) local rowX1 = barX local rowX2 = barX + barW local rowY1 = rowY local rowY2 = rowY + rowH if mx >= rowX1 and mx <= rowX2 and my >= rowY1 and my <= rowY2 then hoverRow = rowIndex break end end DeathBoardClient.HoverRowIndex = hoverRow local pageBtnSize = 17 local spacingBtns = 40 local bottomY = y + height local pageY = bottomY - 15 - pageBtnSize local centerXBtn = x + math.floor(width / 2) local prevX = centerXBtn - math.floor(spacingBtns / 2) - pageBtnSize local nextX = centerXBtn + math.floor(spacingBtns / 2) local overPrev = (mx >= prevX and mx <= prevX + pageBtnSize and my >= pageY and my <= pageY + pageBtnSize) local overNext = (mx >= nextX and mx <= nextX + pageBtnSize and my >= pageY and my <= pageY + pageBtnSize) if overPrev then if DisableClickClient then DisableClickClient() end if CheckRepeatKey and CheckRepeatKey(1) == 1 then DeathBoardClient.ButtonPagePrevState = 2 else DeathBoardClient.ButtonPagePrevState = 1 end consumed = 1 if CheckReleasedKey and CheckReleasedKey(1) == 1 then local now = DB2_NowMs() local pageDelay = 500 if now < (DeathBoardClient.Cooldown.Page or 0) then -- ignore else DeathBoardClient.Cooldown.Page = now + pageDelay local currentPage = DeathBoardClient.CurrentPage or 1 if currentPage > 1 then local newPage = currentPage - 1 DeathBoardClient.RequestPage(newPage) end end end else DeathBoardClient.ButtonPagePrevState = 0 end if overNext then if DisableClickClient then DisableClickClient() end if CheckRepeatKey and CheckRepeatKey(1) == 1 then DeathBoardClient.ButtonPageNextState = 2 else DeathBoardClient.ButtonPageNextState = 1 end consumed = 1 if CheckReleasedKey and CheckReleasedKey(1) == 1 then local now = DB2_NowMs() local pageDelay = 500 if now < (DeathBoardClient.Cooldown.Page or 0) then -- ignore else DeathBoardClient.Cooldown.Page = now + pageDelay local currentPage = DeathBoardClient.CurrentPage or 1 local totalPages = DeathBoardClient.TotalPages or 1 if currentPage < totalPages then local newPage = currentPage + 1 DeathBoardClient.RequestPage(newPage) end end end else DeathBoardClient.ButtonPageNextState = 0 end return consumed end function DeathBoardClient_Protocol_Recv(Packet, PacketName) DeathBoardClient.__db2 = DeathBoardClient.__db2 or { expected = 0, page = 0 } do local p = tostring(PacketName or "") local pg, tp, ct = string.match(p, "^DB2%-ANS%-(%d+)%-(%d+)%-(%d+)$") if pg and tp and ct then local ip = tonumber(pg) or 1 local total = tonumber(tp) or 1 total = math.max(1, math.min(total, MAX_PAGES)) if ip > total then ip = total end local count = tonumber(ct) or 0 if ip < 1 then ip = 1 end if total < 1 then total = 1 end local maxRows = DeathBoardClient.RowsPerPage or 6 if count < 0 then count = 0 end if count > maxRows then count = maxRows end DeathBoardClient.SetPagination(ip, total) DeathBoardClient.LastPageRowCount = count DeathBoardClient.__db2.page = ip DeathBoardClient.__db2.expected = count DeathBoardClient.__db2.rows = {} DeathBoardClient.Cache = DeathBoardClient.Cache or { pages = {}, totalPages = 0 } DeathBoardClient.Cache.totalPages = total DB2_LogOnce("ans_meta", "ANS page="..tostring(ip).." total="..tostring(total).." count="..tostring(count)) pcall(function() if ClearPacket then ClearPacket(PacketName) end end) return true end -- Feature: coluna Servidor (on/off) local featOn = string.match(p, "^DB2%-FEAT%-SRV%-1$") if featOn then DeathBoardClient.Features = DeathBoardClient.Features or {} DeathBoardClient.Features.ServerColumn = true pcall(function() if ClearPacket then ClearPacket(PacketName) end end) return true end local featOff = string.match(p, "^DB2%-FEAT%-SRV%-0$") if featOff then DeathBoardClient.Features = DeathBoardClient.Features or {} DeathBoardClient.Features.ServerColumn = false pcall(function() if ClearPacket then ClearPacket(PacketName) end end) return true end -- Pacote auxiliar com Victim GuildMark (para evitar truncagem do nome no DB2-ROW) local vPage, vIdx, vGB64, vGHex = string.match(p, "^DB2%-VGM%-(%d+)%-(%d+)%-([A-Za-z0-9_%-%=]+)%-([A-Fa-f0-9]+)[^%w_%-%=]*$") if vPage and vIdx and vGB64 and vGHex then local vGName = (function(s) local function b64url_decode(data) data = tostring(data or ""):gsub("-", "+"):gsub("_", "/") local pad = #data % 4 if pad == 2 then data = data .. "==" end if pad == 3 then data = data .. "=" end local b='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' local t = {} data:gsub(".", function(x) if x == "=" then return end local r = b:find(x, 1, true) if r then table.insert(t, r-1) end end) local out = {} for i=1, #t, 4 do local n = (t[i] or 0) * 262144 + (t[i+1] or 0) * 4096 + (t[i+2] or 0) * 64 + (t[i+3] or 0) local c1 = math.floor(n / 65536) % 256 local c2 = math.floor(n / 256) % 256 local c3 = n % 256 if t[i+1] then table.insert(out, string.char(c1)) end if t[i+2] then table.insert(out, string.char(c2)) end if t[i+3] then table.insert(out, string.char(c3)) end end return table.concat(out) end return b64url_decode(s or "") or "" end)(vGB64) local idx = tonumber(vIdx) or 1 if idx < 1 then idx = 1 end DeathBoardClient.Rows = DeathBoardClient.Rows or {} DeathBoardClient.Rows[idx] = DeathBoardClient.Rows[idx] or {} DeathBoardClient.Rows[idx].VictimGuildName = vGName DeathBoardClient.Rows[idx].VictimGuildMarkHex = vGHex if DeathBoardClient.__db2 and DeathBoardClient.__db2.rows then DeathBoardClient.__db2.rows[idx] = DeathBoardClient.__db2.rows[idx] or {} DeathBoardClient.__db2.rows[idx].VictimGuildName = vGName DeathBoardClient.__db2.rows[idx].VictimGuildMarkHex = vGHex end pcall(function() if ClearPacket then ClearPacket(PacketName) end end) return true end -- Pacote auxiliar com Killer GuildMark (simétrico ao VGM) local kPage, kIdx, kGB64, kGHex = string.match(p, "^DB2%-KGM%-(%d+)%-(%d+)%-([A-Za-z0-9_%-%=]+)%-([A-Fa-f0-9]+)[^%w_%-%=]*$") if kPage and kIdx and kGB64 and kGHex then local kGName = (function(s) local function b64url_decode(data) data = tostring(data or ""):gsub("-", "+"):gsub("_", "/") local pad = #data % 4 if pad == 2 then data = data .. "==" end if pad == 3 then data = data .. "=" end local b='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' local t = {} data:gsub(".", function(x) if x == "=" then return end local r = b:find(x, 1, true) if r then table.insert(t, r-1) end end) local out = {} for i=1, #t, 4 do local n = (t[i] or 0) * 262144 + (t[i+1] or 0) * 4096 + (t[i+2] or 0) * 64 + (t[i+3] or 0) local c1 = math.floor(n / 65536) % 256 local c2 = math.floor(n / 256) % 256 local c3 = n % 256 if t[i+1] then table.insert(out, string.char(c1)) end if t[i+2] then table.insert(out, string.char(c2)) end if t[i+3] then table.insert(out, string.char(c3)) end end return table.concat(out) end return b64url_decode(s or "") or "" end)(kGB64) local idx = tonumber(kIdx) or 1 if idx < 1 then idx = 1 end DeathBoardClient.Rows = DeathBoardClient.Rows or {} DeathBoardClient.Rows[idx] = DeathBoardClient.Rows[idx] or {} DeathBoardClient.Rows[idx].KillerGuildName = kGName DeathBoardClient.Rows[idx].KillerGuildMarkHex = kGHex if DeathBoardClient.__db2 and DeathBoardClient.__db2.rows then DeathBoardClient.__db2.rows[idx] = DeathBoardClient.__db2.rows[idx] or {} DeathBoardClient.__db2.rows[idx].KillerGuildName = kGName DeathBoardClient.__db2.rows[idx].KillerGuildMarkHex = kGHex end pcall(function() if ClearPacket then ClearPacket(PacketName) end end) return true end local pageStr, idxStr, kB64, vB64, kClass, vClass, mapB64, timeTxt, kGB64, kgHex, vGB64, vgHex = string.match(p, "^DB2%-ROW%-(%d+)%-(%d+)%-([A-Za-z0-9_%-%=]+)%-([A-Za-z0-9_%-%=]+)%-([A-Za-z]+)%-([A-Za-z]+)%-([A-Za-z0-9_%-%=]*)%-([0-9:]+)%-([A-Za-z0-9_%-%=]*)%-([A-Fa-f0-9]*)%-([A-Za-z0-9_%-%=]*)%-([A-Fa-f0-9]*)[^%w_%-%=]*$") if not (pageStr and idxStr and kB64 and vB64 and kClass and vClass) then -- Variante curta: sem campos de guild da vítima (10 campos) pageStr, idxStr, kB64, vB64, kClass, vClass, mapB64, timeTxt, kGB64, kgHex = string.match(p, "^DB2%-ROW%-(%d+)%-(%d+)%-([A-Za-z0-9_%-%=]+)%-([A-Za-z0-9_%-%=]+)%-([A-Za-z]+)%-([A-Za-z]+)%-([A-Za-z0-9_%-%=]*)%-([0-9:]+)%-([A-Za-z0-9_%-%=]*)%-([A-Fa-f0-9]*)[^%w_%-%=]*$") vGB64, vgHex = "", "" end if not (pageStr and idxStr and kB64 and vB64 and kClass and vClass) then -- Variante ultra-curta: sem campos de guild (8 campos) pageStr, idxStr, kB64, vB64, kClass, vClass, mapB64, timeTxt = string.match(p, "^DB2%-ROW%-(%d+)%-(%d+)%-([A-Za-z0-9_%-%=]+)%-([A-Za-z0-9_%-%=]+)%-([A-Za-z]+)%-([A-Za-z]+)%-([A-Za-z0-9_%-%=]*)%-([0-9:]+)[^%w_%-%=]*$") kGB64, kgHex, vGB64, vgHex = "", "", "", "" end if pageStr and idxStr and kB64 and vB64 and kClass and vClass then local function b64url_decode(data) data = tostring(data or ""):gsub("-", "+"):gsub("_", "/") local pad = #data % 4 if pad == 2 then data = data .. "==" end if pad == 3 then data = data .. "=" end local b='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' local t = {} data:gsub(".", function(x) if x == "=" then return end local r = b:find(x, 1, true) if r then table.insert(t, r-1) end end) local out = {} for i=1, #t, 4 do local n = (t[i] or 0) * 262144 + (t[i+1] or 0) * 4096 + (t[i+2] or 0) * 64 + (t[i+3] or 0) local c1 = math.floor(n / 65536) % 256 local c2 = math.floor(n / 256) % 256 local c3 = n % 256 if t[i+1] then table.insert(out, string.char(c1)) end if t[i+2] then table.insert(out, string.char(c2)) end if t[i+3] then table.insert(out, string.char(c3)) end end return table.concat(out) end local killerName = b64url_decode(kB64) or "" local victimName = b64url_decode(vB64) or "" local mapName = b64url_decode(mapB64 or "") or "" local kGName = b64url_decode(kGB64 or "") or "" local vGName = b64url_decode(vGB64 or "") or "" local idx = tonumber(idxStr) or 1 if idx < 1 then idx = 1 end if idx <= 2 then DB2_LogOnce("row_start_"..tostring(idx), "ROW start idx="..tostring(idx)) end kgHex = DB2_NormalizeHex64(kgHex or "") vgHex = DB2_NormalizeHex64(vgHex or "") DeathBoardClient.SetRow(idx, killerName, victimName, timeTxt or "", mapName or "", kClass, vClass, kGName, kgHex or "", vGName, vgHex or "") DeathBoardClient.LastPageRowCount = math.max(DeathBoardClient.LastPageRowCount or 0, idx) DeathBoardClient.__db2.filled = (DeathBoardClient.__db2.filled or 0) + 1 if DeathBoardClient.__db2 and DeathBoardClient.__db2.rows then DeathBoardClient.__db2.rows[idx] = { KillerName = killerName, VictimName = victimName, TimeText = timeTxt or "", MapName = mapName or "", KillerClassKey = kClass, VictimClassKey = vClass, KillerGuildName = kGName, KillerGuildMarkHex = kgHex or "", VictimGuildName = vGName, VictimGuildMarkHex = vgHex or "", ServerCode = 0 } end if idx <= 2 then DB2_LogOnce("row_done_"..tostring(idx), "ROW done idx="..tostring(idx)) end if (DeathBoardClient.__db2.expected or 0) > 0 and (DeathBoardClient.__db2.filled or 0) >= (DeathBoardClient.__db2.expected or 0) then DeathBoardClient.IsLoading = false DeathBoardClient.LoadingProgress = 100 end pcall(function() if ClearPacket then ClearPacket(PacketName) end end) return true end -- ServerCode por linha local sPage, sIdx, sCode = string.match(p, "^DB2%-SRV%-(%d+)%-(%d+)%-(%d+)$") if sPage and sIdx and sCode then local idx = tonumber(sIdx) or 1 if idx < 1 then idx = 1 end DeathBoardClient.Rows = DeathBoardClient.Rows or {} DeathBoardClient.Rows[idx] = DeathBoardClient.Rows[idx] or {} DeathBoardClient.Rows[idx].ServerCode = tonumber(sCode) or 0 if DeathBoardClient.__db2 and DeathBoardClient.__db2.rows then DeathBoardClient.__db2.rows[idx] = DeathBoardClient.__db2.rows[idx] or {} DeathBoardClient.__db2.rows[idx].ServerCode = tonumber(sCode) or 0 end pcall(function() if ClearPacket then ClearPacket(PacketName) end end) return true end local endPage, endCount = string.match(p, "^DB2%-END%-(%d+)%-(%d+)[^%w_%-%=]*$") if endPage and endCount then local now = DB2_NowMs() local cnt = tonumber(endCount) or 0 if cnt < 0 then cnt = 0 end DeathBoardClient.LastPageRowCount = cnt DeathBoardClient.IsLoading = false DeathBoardClient.LoadingProgress = 100 local pg = tonumber(endPage) or (DeathBoardClient.__db2 and DeathBoardClient.__db2.page) or 1 if pg < 1 then pg = 1 end if pg > MAX_PAGES then pg = MAX_PAGES end DeathBoardClient.Cache = DeathBoardClient.Cache or { pages = {}, totalPages = 0 } local rowsForCache = {} local src = DeathBoardClient.__db2 and DeathBoardClient.__db2.rows or {} for i=1,cnt do rowsForCache[i] = DB2_CopyRowCache(src[i] or DeathBoardClient.Rows[i]) end DeathBoardClient.Cache.pages[pg] = { rows = rowsForCache, count = cnt, ts = now } DB2_LogOnce("ans_end", "END page="..tostring(pg).." count="..tostring(cnt)) pcall(function() if ClearPacket then ClearPacket(PacketName) end end) return true end -- HUD curto (meta) + marks em pacotes separados local kmB64, vmB64, kmClass, vmClass = string.match(p, "^DB2%-HUDM%-([A-Za-z0-9_%-%=]+)%-([A-Za-z0-9_%-%=]+)%-([A-Za-z]+)%-([A-Za-z]+)[^%w_%-%=]*$") if kmB64 and vmB64 and kmClass and vmClass then local function b64url_decode(data) data = tostring(data or ""):gsub("-", "+"):gsub("_", "/") local pad = #data % 4 if pad == 2 then data = data .. "==" end if pad == 3 then data = data .. "=" end local b='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' local t = {} data:gsub(".", function(x) if x == "=" then return end local r = b:find(x, 1, true) if r then table.insert(t, r-1) end end) local out = {} for i=1, #t, 4 do local n = (t[i] or 0) * 262144 + (t[i+1] or 0) * 4096 + (t[i+2] or 0) * 64 + (t[i+3] or 0) local c1 = math.floor(n / 65536) % 256 local c2 = math.floor(n / 256) % 256 local c3 = n % 256 if t[i+1] then table.insert(out, string.char(c1)) end if t[i+2] then table.insert(out, string.char(c2)) end if t[i+3] then table.insert(out, string.char(c3)) end end return table.concat(out) end local killerName = b64url_decode(kmB64) or "" local victimName = b64url_decode(vmB64) or "" DeathBoardClient.LastEventTs = DB2_NowMs() if DeathBoardClient.ShowKillHud then DeathBoardClient.ShowKillHud(killerName, victimName, kmClass, vmClass, "", "") end pcall(function() if ClearPacket then ClearPacket(PacketName) end end) return true end -- PUSH meta: inserir no topo da página atual local pmKB64, pmVB64, pmKClass, pmVClass, pmMapB64, pmTime = string.match(p, "^DB2%-PUSHM%-([A-Za-z0-9_%-%=]+)%-([A-Za-z0-9_%-%=]+)%-([A-Za-z]+)%-([A-Za-z]+)%-([A-Za-z0-9_%-%=]+)%-([0-9:]+)[^%w_%-%=]*$") if pmKB64 and pmVB64 and pmKClass and pmVClass and pmMapB64 and pmTime then local function b64url_decode(data) data = tostring(data or ""):gsub("-", "+"):gsub("_", "/") local pad = #data % 4 if pad == 2 then data = data .. "==" end if pad == 3 then data = data .. "=" end local b='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' local t = {} data:gsub(".", function(x) if x == "=" then return end local r = b:find(x, 1, true) if r then table.insert(t, r-1) end end) local out = {} for i=1, #t, 4 do local n = (t[i] or 0) * 262144 + (t[i+1] or 0) * 4096 + (t[i+2] or 0) * 64 + (t[i+3] or 0) local c1 = math.floor(n / 65536) % 256 local c2 = math.floor(n / 256) % 256 local c3 = n % 256 if t[i+1] then table.insert(out, string.char(c1)) end if t[i+2] then table.insert(out, string.char(c2)) end if t[i+3] then table.insert(out, string.char(c3)) end end return table.concat(out) end local kName = b64url_decode(pmKB64) or "" local vName = b64url_decode(pmVB64) or "" local mName = b64url_decode(pmMapB64) or "" DeathBoardClient.LastEventTs = DB2_NowMs() local curPage = DeathBoardClient.CurrentPage or 1 local gate = (DeathBoardClient.CheckOpen and DeathBoardClient.CheckOpen()) and (curPage == 1) if not gate then pcall(function() if ClearPacket then ClearPacket(PacketName) end end) return true end local rows = DeathBoardClient.Rows or {} local rowsPerPage = DeathBoardClient.RowsPerPage or 6 for i = math.min(#rows, rowsPerPage - 1), 1, -1 do rows[i + 1] = rows[i] end rows[1] = { KillerName = kName, VictimName = vName, KillerClassKey = pmKClass, VictimClassKey = pmVClass, MapName = mName, TimeText = pmTime or "", KillerGuildName = "", KillerGuildMarkHex = "", VictimGuildName = "", VictimGuildMarkHex = "" } DeathBoardClient.Rows = rows local current = DeathBoardClient.LastPageRowCount or 0 if current < rowsPerPage then DeathBoardClient.LastPageRowCount = current + 1 end if RenderText3 then -- trigger re-render next frame end pcall(function() if ClearPacket then ClearPacket(PacketName) end end) return true end -- PUSH guild marks (atualizam a linha 1) local pkGB64, pkHex = string.match(p, "^DB2%-PKGM%-([A-Za-z0-9_%-%=]+)%-([A-Fa-f0-9]+)[^%w_%-%=]*$") if pkGB64 and pkHex then local function b64url_decode(data) data = tostring(data or ""):gsub("-", "+"):gsub("_", "/") local pad = #data % 4 if pad == 2 then data = data .. "==" end if pad == 3 then data = data .. "=" end local b='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' local t = {} data:gsub(".", function(x) if x == "=" then return end local r = b:find(x, 1, true) if r then table.insert(t, r-1) end end) local out = {} for i=1, #t, 4 do local n = (t[i] or 0) * 262144 + (t[i+1] or 0) * 4096 + (t[i+2] or 0) * 64 + (t[i+3] or 0) local c1 = math.floor(n / 65536) % 256 local c2 = math.floor(n / 256) % 256 local c3 = n % 256 if t[i+1] then table.insert(out, string.char(c1)) end if t[i+2] then table.insert(out, string.char(c2)) end if t[i+3] then table.insert(out, string.char(c3)) end end return table.concat(out) end local gName = b64url_decode(pkGB64) or "" local curPage = DeathBoardClient.CurrentPage or 1 local gate = (DeathBoardClient.CheckOpen and DeathBoardClient.CheckOpen()) and (curPage == 1) if not gate then pcall(function() if ClearPacket then ClearPacket(PacketName) end end) return true end if DeathBoardClient.Rows and DeathBoardClient.Rows[1] then DeathBoardClient.Rows[1].KillerGuildName = gName DeathBoardClient.Rows[1].KillerGuildMarkHex = pkHex end pcall(function() if ClearPacket then ClearPacket(PacketName) end end) return true end local pvGB64, pvHex = string.match(p, "^DB2%-PVGM%-([A-Za-z0-9_%-%=]+)%-([A-Fa-f0-9]+)[^%w_%-%=]*$") if pvGB64 and pvHex then local function b64url_decode(data) data = tostring(data or ""):gsub("-", "+"):gsub("_", "/") local pad = #data % 4 if pad == 2 then data = data .. "==" end if pad == 3 then data = data .. "=" end local b='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' local t = {} data:gsub(".", function(x) if x == "=" then return end local r = b:find(x, 1, true) if r then table.insert(t, r-1) end end) local out = {} for i=1, #t, 4 do local n = (t[i] or 0) * 262144 + (t[i+1] or 0) * 4096 + (t[i+2] or 0) * 64 + (t[i+3] or 0) local c1 = math.floor(n / 65536) % 256 local c2 = math.floor(n / 256) % 256 local c3 = n % 256 if t[i+1] then table.insert(out, string.char(c1)) end if t[i+2] then table.insert(out, string.char(c2)) end if t[i+3] then table.insert(out, string.char(c3)) end end return table.concat(out) end local gName = b64url_decode(pvGB64) or "" local curPage = DeathBoardClient.CurrentPage or 1 local gate = (DeathBoardClient.CheckOpen and DeathBoardClient.CheckOpen()) and (curPage == 1) if not gate then pcall(function() if ClearPacket then ClearPacket(PacketName) end end) return true end if DeathBoardClient.Rows and DeathBoardClient.Rows[1] then DeathBoardClient.Rows[1].VictimGuildName = gName DeathBoardClient.Rows[1].VictimGuildMarkHex = pvHex end pcall(function() if ClearPacket then ClearPacket(PacketName) end end) return true end local onlyKg = string.match(p, "^DB2%-HKGM%-([A-Fa-f0-9]+)[^%w_%-%=]*$") if onlyKg then local list = DeathBoardClient.HUDKill or {} local last = list[#list] if last then last.KillerGuildMarkHex = onlyKg end pcall(function() if ClearPacket then ClearPacket(PacketName) end end) return true end local onlyVg = string.match(p, "^DB2%-HVGM%-([A-Fa-f0-9]+)[^%w_%-%=]*$") if onlyVg then local list = DeathBoardClient.HUDKill or {} local last = list[#list] if last then last.VictimGuildMarkHex = onlyVg end pcall(function() if ClearPacket then ClearPacket(PacketName) end end) return true end end return false end function DeathBoardClient.UpdateKey() if not Window.IsOpen then return 0 end if CheckPressedKey and Keys and CheckPressedKey(Keys.Escape) == 1 then DeathBoardClient.Close() if DisableClickClient then DisableClickClient() end return 1 end return 0 end