local Protocol = { Header = 0xFC, RequestPacketName = "DeathBoardRequest", PagePacketName = "DeathBoardPage", HudPacketName = "DeathBoardHud", PushPacketName = "DeathBoardPush", PacketType = { REQ_PAGE = 0x01, PAGE_META = 0x10, PAGE_ROW = 0x11, PAGE_END = 0x12, HUD_KILL = 0x20, PUSH_ROW = 0x21 } } local deps = {} function Protocol.Setup(newDeps) deps = newDeps or {} end local function getByte(packetName, offset) if not GetBytePacket then return 0 end local ok, value = pcall(GetBytePacket, packetName, offset) if not ok then return 0 end return tonumber(value) or 0 end local function readString(packetName, offset) local len = getByte(packetName, offset) offset = offset + 1 if len < 0 then len = 0 end local bytes = {} for i = 1, len do bytes[i] = string.char(getByte(packetName, offset)) offset = offset + 1 end return table.concat(bytes), offset end local function readGuildSide(packetName, offset) local name, newOffset = readString(packetName, offset) if name == "" then return "", "", newOffset end local markBytes = {} for i = 1, 32 do markBytes[i] = getByte(packetName, newOffset) newOffset = newOffset + 1 end return name, deps.BytesToHex(markBytes), newOffset end local function rowRef(idx) DeathBoardClient.Rows = DeathBoardClient.Rows or {} DeathBoardClient.Rows[idx] = DeathBoardClient.Rows[idx] or {} if DeathBoardClient.__db2 and DeathBoardClient.__db2.rows then DeathBoardClient.__db2.rows[idx] = DeathBoardClient.__db2.rows[idx] or {} end return DeathBoardClient.Rows[idx], DeathBoardClient.__db2 and DeathBoardClient.__db2.rows and DeathBoardClient.__db2.rows[idx] or nil end local function applyGuild(idx, side, name, hex) local row, prow = rowRef(idx) local normalized = deps.NormalizeHex((hex or "")) if side == "K" then if name ~= nil then row.KillerGuildName = name end if normalized ~= "" then row.KillerGuildMarkHex = normalized end if prow then if name ~= nil then prow.KillerGuildName = name end if normalized ~= "" then prow.KillerGuildMarkHex = normalized end end else if name ~= nil then row.VictimGuildName = name end if normalized ~= "" then row.VictimGuildMarkHex = normalized end if prow then if name ~= nil then prow.VictimGuildName = name end if normalized ~= "" then prow.VictimGuildMarkHex = normalized end end end end function Protocol.RequestPage(page) 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 > deps.MaxPages then page = deps.MaxPages 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 = deps.ShowCachedPage(page) end if fresh then return end if DeathBoardClient.IsLoading then local inflightPage = DeathBoardClient.__db2 and DeathBoardClient.__db2.page or DeathBoardClient.CurrentPage or 0 if page ~= inflightPage then DeathBoardClient.PendingPage = page deps.Log(string.format("RequestPage queued page=%d inflight=%d shown=%s", page, tonumber(inflightPage) or 0, tostring(shown == true))) end return end local ok = pcall(function() return CreatePacket(Protocol.RequestPacketName, Protocol.Header) end) if ok then pcall(function() SetBytePacket(Protocol.RequestPacketName, Protocol.PacketType.REQ_PAGE, 0) end) pcall(function() SetBytePacket(Protocol.RequestPacketName, page, 1) end) pcall(function() SendPacket(Protocol.RequestPacketName) end) pcall(function() ClearPacket(Protocol.RequestPacketName) end) end if not shown then DeathBoardClient.IsLoading = true DeathBoardClient.LoadingProgress = 0 DeathBoardClient.LoadingStartTs = deps.NowMs() end end function Protocol.Recv(Packet, PacketName) DeathBoardClient.__db2 = DeathBoardClient.__db2 or { expected = 0, page = 0 } local pName = tostring(PacketName or "") local function clearPkt() pcall(function() if ClearPacket then ClearPacket(PacketName) end end) end if pName == Protocol.PagePacketName then local subHeader = getByte(PacketName, 0) if subHeader == Protocol.PacketType.PAGE_META then local ip = getByte(PacketName, 1) local total = getByte(PacketName, 2) local count = getByte(PacketName, 3) local serverColumn = getByte(PacketName, 4) if ip < 1 then ip = 1 end if total < 1 then total = 1 end if total > deps.MaxPages then total = deps.MaxPages end if ip > total then ip = total end local maxRows = DeathBoardClient.RowsPerPage or 6 if count > maxRows then count = maxRows end DeathBoardClient.Features = DeathBoardClient.Features or {} DeathBoardClient.Features.ServerColumn = (serverColumn == 1) DeathBoardClient.SetPagination(ip, total) DeathBoardClient.LastPageRowCount = count DeathBoardClient.__db2.page = ip DeathBoardClient.__db2.expected = count DeathBoardClient.__db2.filled = 0 DeathBoardClient.__db2.rows = {} DeathBoardClient.Cache = DeathBoardClient.Cache or { pages = {}, totalPages = 0 } DeathBoardClient.Cache.totalPages = total clearPkt() return true elseif subHeader == Protocol.PacketType.PAGE_ROW then local idx = getByte(PacketName, 2) local flags = getByte(PacketName, 3) local serverCode = getByte(PacketName, 4) local offset = 5 local killerName, victimName, killerClass, victimClass, mapName, timeTxt killerName, offset = readString(PacketName, offset) victimName, offset = readString(PacketName, offset) killerClass, offset = readString(PacketName, offset) victimClass, offset = readString(PacketName, offset) mapName, offset = readString(PacketName, offset) timeTxt, offset = readString(PacketName, offset) if idx < 1 then idx = 1 end DeathBoardClient.SetRow(idx, killerName, victimName, timeTxt or "", mapName or "", killerClass, victimClass, "", "", "", "") DeathBoardClient.LastPageRowCount = math.max(DeathBoardClient.LastPageRowCount or 0, idx) if DeathBoardClient.__db2 and DeathBoardClient.__db2.rows then DeathBoardClient.__db2.rows[idx] = { KillerName = killerName, VictimName = victimName, TimeText = timeTxt or "", MapName = mapName or "", KillerClassKey = killerClass or "", VictimClassKey = victimClass or "", KillerGuildName = "", KillerGuildMarkHex = "", VictimGuildName = "", VictimGuildMarkHex = "", ServerCode = serverCode or 0 } end local row, prow = rowRef(idx) row.ServerCode = tonumber(serverCode) or 0 if prow then prow.ServerCode = tonumber(serverCode) or 0 end if (flags % 2) == 1 then local gName, gHex gName, gHex, offset = readGuildSide(PacketName, offset) applyGuild(idx, "K", gName, gHex) end if math.floor(flags / 2) % 2 == 1 then local gName, gHex gName, gHex, offset = readGuildSide(PacketName, offset) applyGuild(idx, "V", gName, gHex) end DeathBoardClient.__db2.filled = (DeathBoardClient.__db2.filled or 0) + 1 clearPkt() return true elseif subHeader == Protocol.PacketType.PAGE_END then local pg = getByte(PacketName, 1) local cnt = getByte(PacketName, 2) local now = deps.NowMs() if cnt < 0 then cnt = 0 end DeathBoardClient.LastPageRowCount = cnt DeathBoardClient.IsLoading = false DeathBoardClient.LoadingProgress = 100 if pg < 1 then pg = 1 end if pg > deps.MaxPages then pg = deps.MaxPages 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] = deps.CopyRowCache(src[i] or DeathBoardClient.Rows[i]) end DeathBoardClient.Cache.pages[pg] = { rows = rowsForCache, count = cnt, ts = now } deps.FlushPendingPage("page_end") clearPkt() return true end elseif pName == Protocol.HudPacketName then local subHeader = getByte(PacketName, 0) if subHeader == Protocol.PacketType.HUD_KILL then local offset = 1 local killerName, victimName, killerClass, victimClass local killerGuildName, killerGuildHex, victimGuildName, victimGuildHex killerName, offset = readString(PacketName, offset) victimName, offset = readString(PacketName, offset) killerClass, offset = readString(PacketName, offset) victimClass, offset = readString(PacketName, offset) killerGuildName, killerGuildHex, offset = readGuildSide(PacketName, offset) victimGuildName, victimGuildHex, offset = readGuildSide(PacketName, offset) DeathBoardClient.LastEventTs = deps.NowMs() if DeathBoardClient.ShowKillHud then DeathBoardClient.ShowKillHud(killerName, victimName, killerClass, victimClass, killerGuildHex, victimGuildHex) end local list = DeathBoardClient.HUDKill or {} local last = list[#list] if last then last.KillerGuildName = killerGuildName or "" last.VictimGuildName = victimGuildName or "" last.KillerGuildMarkHex = killerGuildHex or "" last.VictimGuildMarkHex = victimGuildHex or "" end clearPkt() return true end elseif pName == Protocol.PushPacketName then local subHeader = getByte(PacketName, 0) if subHeader == Protocol.PacketType.PUSH_ROW then local offset = 1 local kName, vName, kClass, vClass, mName, pTime local kGuildName, kGuildHex, vGuildName, vGuildHex kName, offset = readString(PacketName, offset) vName, offset = readString(PacketName, offset) kClass, offset = readString(PacketName, offset) vClass, offset = readString(PacketName, offset) mName, offset = readString(PacketName, offset) pTime, offset = readString(PacketName, offset) kGuildName, kGuildHex, offset = readGuildSide(PacketName, offset) vGuildName, vGuildHex, offset = readGuildSide(PacketName, offset) DeathBoardClient.LastEventTs = deps.NowMs() local curPage = DeathBoardClient.CurrentPage or 1 local gate = (DeathBoardClient.CheckOpen and DeathBoardClient.CheckOpen()) and (curPage == 1) if gate then 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 = kClass, VictimClassKey = vClass, MapName = mName, TimeText = pTime or "", KillerGuildName = kGuildName or "", KillerGuildMarkHex = kGuildHex or "", VictimGuildName = vGuildName or "", VictimGuildMarkHex = vGuildHex or "" } DeathBoardClient.Rows = rows local current = DeathBoardClient.LastPageRowCount or 0 if current < rowsPerPage then DeathBoardClient.LastPageRowCount = current + 1 end end clearPkt() return true end end return false end return Protocol