-- 百战成神(DB) 跨服数据层 -- -- 运行环境: 仅跨服进程 _G.is_middle == true -- 存储: Mongo 集合 DB.db_bzcs -- -- 全服积分榜 rankCache + uuid2rank(与 rankDirty 同步重建); 排序 score 降序 -> scoreTime 升序 -> uuid -- 客户端展示榜仅前 BZCS_RANK_MAX 名, 与匹配用的全服榜不是同一展示范围 --[=[ BzcsData = { activityStartTime, activityEndTime, -- 本轮活动起止时间戳 lastResetTime, -- 上次开轮时间(用于 BZCS_CYCLE_DAYS 周期判定) rewardIssued, -- 本轮是否已发奖 playerList = { [uuid] = { uuid, serverId, score, -- 积分, 默认 BZCS_INIT_SCORE scoreTime, -- 达到当前积分的时间戳(同分比较用, 越小名次越靠前) isRobot, -- 1=机器人 firstJoinTime, -- 首次挑战时间, >0 才参与周期发奖 showInfo, -- { name,head,headFrame,body, heroArr={[race]=真人阵容|机器人{monsterOutID,racePower}} } }, }, serverList = { [serverId] = { uuid, ... } }, -- 按服索引, 发奖 WL 路由用 pendingRewards = { [serverId] = { {uuid, rank}, ... } }, -- 逻辑服断连时待发奖队列 } ]=] local LuaMongo = _G.lua_mongo local DB = require("common.DB") local Util = require("common.Util") local CreateRole = require("role.CreateRole") local BaiZhanChengShenDefine = require("baiZhanChengShen.BaiZhanChengShenDefine") local BzcsLog = require("baiZhanChengShen.BaiZhanChengShenLog") local BzcsConfig = require("excel.baiZhanChengShen") BzcsData = BzcsData or {} local dbUpdate = {_id = nil} local dbUpdateField = {} local rankCache = nil local uuid2rank = nil local rankDirty = true -- 增量更新 Mongo 字段 local function updateValue(key, value) if not key then return end if value then dbUpdateField["$set"] = {[key] = value} dbUpdateField["$unset"] = nil else dbUpdateField["$set"] = nil dbUpdateField["$unset"] = {[key] = 1} end dbUpdate._id = BzcsData._id LuaMongo.update(DB.db_bzcs, dbUpdate, dbUpdateField) end -- 从 db_bzcs 加载或初始化空档 local function loadData() LuaMongo.find(DB.db_bzcs) local data = {} if LuaMongo.next(data) then BzcsData = data else BzcsData.activityStartTime = 0 BzcsData.activityEndTime = 0 BzcsData.lastResetTime = 0 BzcsData.rewardIssued = true BzcsData.playerList = {} LuaMongo.insert(DB.db_bzcs, BzcsData) end rankDirty = true end -- 排行同分比较用时间(达到当前积分时刻) local function getRankScoreTime(pinfo) return pinfo.scoreTime or 0 end -- 按积分降序重建排行缓存; 同分按 scoreTime 升序(先达到该积分者靠前), 仍同则 uuid local function sortRankCache() if not rankDirty then return end rankCache = {} uuid2rank = {} for uuid, pinfo in pairs(BzcsData.playerList or {}) do rankCache[#rankCache + 1] = { uuid = uuid, score = pinfo.score or BaiZhanChengShenDefine.BZCS_INIT_SCORE, scoreTime = getRankScoreTime(pinfo), } end table.sort(rankCache, function(a, b) if a.score ~= b.score then return a.score > b.score end if a.scoreTime ~= b.scoreTime then return a.scoreTime < b.scoreTime end return a.uuid < b.uuid end) for rank, entry in ipairs(rankCache) do uuid2rank[entry.uuid] = rank end rankDirty = false end -- 保证 rankCache / uuid2rank 已按当前 playerList 重建 local function ensureRankIndex() sortRankCache() end -- 保证 showInfo 结构完整 local function normalizePlayer(pinfo) if not pinfo then return end pinfo.showInfo = pinfo.showInfo or {} pinfo.showInfo.heroArr = pinfo.showInfo.heroArr or {} end -- 按 serverId 重建 serverList(仅真人, 机器人不参与发奖路由) local function rebuildServerList() BzcsData.serverList = BzcsData.serverList or {} Util.cleanTable(BzcsData.serverList) for uuid, pinfo in pairs(BzcsData.playerList or {}) do if pinfo.isRobot ~= 1 then local sid = pinfo.serverId if sid then BzcsData.serverList[sid] = BzcsData.serverList[sid] or {} table.insert(BzcsData.serverList[sid], uuid) end end end end -- 机器单族仅存 monsterOutID + racePower(由 monsterOut 算出); 展示由 ExpandBzcsRaceShow 用时展开 local function genRobotRaceShow(robotListIdx, race) local cfg = BzcsConfig.robotList[robotListIdx] if not cfg or not cfg.monsterOutIDs then return {} end local monsterOutID = cfg.monsterOutIDs[race] if not monsterOutID then return {} end return { monsterOutID = monsterOutID, formation = 1, racePower = BaiZhanChengShenDefine.CalcMonsterOutPower(monsterOutID), } end -- 按当前 robotList 重算已存在机器人的五族 monsterOutID + racePower(保留 name/head 等展示字段) local function syncRobotHeroArr(robotListIdx, pinfo) if not robotListIdx or not pinfo then return end normalizePlayer(pinfo) local heroArr = {} for _, race in ipairs(BaiZhanChengShenDefine.BZCS_RACE_ORDER) do heroArr[race] = genRobotRaceShow(robotListIdx, race) end pinfo.showInfo.heroArr = heroArr end local function getRobotScore(robotListIdx) local cfg = BzcsConfig.robotList[robotListIdx] return (cfg and cfg.score) or BaiZhanChengShenDefine.BZCS_INIT_SCORE end -- 生成机器人池: 数量 = robotList 条数, bzcs_robot_i 对应 robotList[i] function GenerateRobots(cnt) cnt = cnt or BaiZhanChengShenDefine.GetRobotListCount() if cnt < 1 then return end BzcsData.playerList = BzcsData.playerList or {} local now = os.time() for i = 1, cnt do local uuid = "bzcs_robot_" .. i if not BzcsData.playerList[uuid] then local heroArr = {} for _, race in ipairs(BaiZhanChengShenDefine.BZCS_RACE_ORDER) do heroArr[race] = genRobotRaceShow(i, race) end BzcsData.playerList[uuid] = { uuid = uuid, isRobot = 1, score = getRobotScore(i), scoreTime = now + i, firstJoinTime = now, showInfo = { name = CreateRole.getRandomName(), head = CreateRole.getRandomHead(), headFrame = CreateRole.getRandomHeadFrame(), body = CreateRole.getRandomBody(), heroArr = heroArr, }, } else syncRobotHeroArr(i, BzcsData.playerList[uuid]) end end rankDirty = true rebuildServerList() updateValue("playerList", BzcsData.playerList) BzcsLog.logAction("robot_gen", string.format("cnt=%s robotList=%s", cnt, BaiZhanChengShenDefine.GetRobotListCount())) end -- 逻辑服断连期间缓存的周期奖励, 重连后补发 function AddPendingReward(serverId, uuid, rank) if not serverId or not uuid then return end AddPendingRewards(serverId, {{uuid, rank}}) end -- 逻辑服未连接时缓存整批待发奖 function AddPendingRewards(serverId, rewardList) if not serverId or not rewardList or #rewardList == 0 then return end BzcsData.pendingRewards = BzcsData.pendingRewards or {} local list = BzcsData.pendingRewards[serverId] if not list then list = {} BzcsData.pendingRewards[serverId] = list end local addCnt, skipCnt = 0, 0 for _, info in ipairs(rewardList) do local uuid, rank = info[1], info[2] if uuid and uuid ~= "" and rank and rank > 0 then list[#list + 1] = {uuid, rank} addCnt = addCnt + 1 else skipCnt = skipCnt + 1 BzcsLog.logAction("reward_pending_skip", string.format( "serverId=%s uuid=%s rank=%s", serverId, uuid or "", rank or 0 )) end end if addCnt < 1 then return end updateValue("pendingRewards." .. serverId, list) BzcsLog.logAction("reward_pending", string.format( "serverId=%s add=%s skip=%s total=%s", serverId, addCnt, skipCnt, #list )) end -- 清空全部待发奖缓存(放弃上轮发奖开新轮时用) function ClearAllPendingRewards() if not BzcsData.pendingRewards or not next(BzcsData.pendingRewards) then return end BzcsData.pendingRewards = {} updateValue("pendingRewards", BzcsData.pendingRewards) BzcsLog.logAction("reward_pending_clear", "all") end -- 取出并清空某逻辑服待发奖列表, 返回 {{uuid, rank}, ...} function TakePendingRewards(serverId) BzcsData.pendingRewards = BzcsData.pendingRewards or {} local list = BzcsData.pendingRewards[serverId] if not list or #list == 0 then return {} end BzcsData.pendingRewards[serverId] = nil updateValue("pendingRewards." .. serverId, nil) return list end -- 跨服启动: 加载数据, 同步/补全机器人池, 并向已连接逻辑服同步活动状态 function initAfterStart() if _G.is_middle ~= true then return end loadData() GenerateRobots() rebuildServerList() BzcsLog.logAction("db_init", string.format("playerCnt=%s", Util.getTableCount(BzcsData.playerList or {}))) local BaiZhanChengShenCS = require("baiZhanChengShen.BaiZhanChengShenCS") BaiZhanChengShenCS.syncActStateToAllConnected() end -------------------------------------------------------------------------------- -- 活动周期 -------------------------------------------------------------------------------- -- 获取本轮活动起止时间戳 function GetActivityTimes() return BzcsData.activityStartTime or 0, BzcsData.activityEndTime or 0 end -- 获取上次开新轮时间(BZCS_CYCLE_DAYS 周期判定) function GetLastResetTime() return BzcsData.lastResetTime or 0 end -- 本轮周期奖励是否已发放 function IsRewardIssued() return BzcsData.rewardIssued == true end -- 设置活动时间并清除发奖标记(开新轮) function SetActivityTimes(startTime, endTime) BzcsData.activityStartTime = startTime BzcsData.activityEndTime = endTime BzcsData.lastResetTime = startTime BzcsData.rewardIssued = false updateValue("activityStartTime", startTime) updateValue("activityEndTime", endTime) updateValue("lastResetTime", startTime) updateValue("rewardIssued", false) end -- 标记本轮是否已发奖 function SetRewardIssued(flag) BzcsData.rewardIssued = flag updateValue("rewardIssued", flag) end -- 按 uuid 查询跨服玩家/机器人 function GetPlayer(uuid) local pinfo = BzcsData.playerList and BzcsData.playerList[uuid] if pinfo then normalizePlayer(pinfo) end return pinfo end -------------------------------------------------------------------------------- -- 玩家数据 -------------------------------------------------------------------------------- -- 新增或合并玩家跨服数据 function UpsertPlayer(uuid, data) BzcsData.playerList = BzcsData.playerList or {} local old = BzcsData.playerList[uuid] if old then for k, v in pairs(data) do old[k] = v end data = old else BzcsData.playerList[uuid] = data end data.uuid = uuid normalizePlayer(data) rankDirty = true rebuildServerList() updateValue("playerList." .. uuid, data) return data end -- 增减积分并落库(积分变化时刷新 scoreTime 供同分排序) function UpdateScore(uuid, delta) local pinfo = GetPlayer(uuid) if not pinfo then BzcsLog.logAction("score_miss", string.format("uuid=%s delta=%s", uuid or "", delta or 0)) return end pinfo.score = (pinfo.score or BaiZhanChengShenDefine.BZCS_INIT_SCORE) + delta pinfo.scoreTime = os.time() rankDirty = true updateValue("playerList." .. uuid .. ".score", pinfo.score) updateValue("playerList." .. uuid .. ".scoreTime", pinfo.scoreTime) BzcsLog.logAction("score", string.format("uuid=%s delta=%s score=%s isRobot=%s", uuid, delta, pinfo.score, pinfo.isRobot or 0)) return pinfo.score end -------------------------------------------------------------------------------- -- 排行与匹配 -------------------------------------------------------------------------------- -- 查询玩家当前全服名次, 0=未上榜 function GetRankByUuid(uuid) if not uuid then return 0 end ensureRankIndex() return uuid2rank[uuid] or 0 end -- 按全服名次取玩家(含机器人), rank 从 1 起 function GetPlayerByRank(rank) if not rank or rank < 1 then return nil end ensureRankIndex() local entry = rankCache and rankCache[rank] if not entry then return nil end return GetPlayer(entry.uuid) end -- 单条排行榜展示结构(榜单与 myRankInfo 共用) function BuildRankInfoEntry(pinfo, rank) if not pinfo then return nil end local si = pinfo.showInfo or {} return { rank = rank or 0, uuid = pinfo.uuid, name = si.name or "", head = si.head or 0, headFrame = si.headFrame or 0, serverId = pinfo.serverId or 0, power = BaiZhanChengShenDefine.CalcPlayerPower(si, pinfo), score = pinfo.score or BaiZhanChengShenDefine.BZCS_INIT_SCORE, isRobot = pinfo.isRobot, } end -- 指定玩家排行展示(未注册跨服时仅 rank/score 等默认值) function BuildPlayerRankInfo(uuid) local rank = GetRankByUuid(uuid) local pinfo = GetPlayer(uuid) local info = BuildRankInfoEntry(pinfo, rank) if info then return info end return { rank = rank, uuid = uuid or "", name = "", head = 0, headFrame = 0, serverId = 0, power = 0, score = BaiZhanChengShenDefine.BZCS_INIT_SCORE, } end -- 对手详情 WL 回包(仅 GC_BZCS_OPPONENT_INFO 所需字段) function BuildOpponentInfoSnapshot(pinfo) if not pinfo then return nil end local si = pinfo.showInfo or {} return { name = si.name or "", head = si.head or 0, headFrame = si.headFrame or 0, power = BaiZhanChengShenDefine.CalcPlayerPower(si, pinfo), score = pinfo.score or BaiZhanChengShenDefine.BZCS_INIT_SCORE, } end -- 客户端展示榜前 limit 名(默认100), 含 rank/uuid/展示字段 function GetRankList(limit) ensureRankIndex() limit = limit or BaiZhanChengShenDefine.BZCS_RANK_MAX local ret = {} for i = 1, math.min(limit, #(rankCache or {})) do local entry = rankCache[i] local pinfo = GetPlayer(entry.uuid) local info = BuildRankInfoEntry(pinfo, i) if info then ret[#ret + 1] = info end end return ret end -- 形象仅读跨服 showInfo.body(逻辑服 REGISTER/UPDATE_SHOW 同步), 不可查逻辑服 RoleDB local function getOpponentBody(pinfo) local si = pinfo.showInfo or {} return si.body or 0 end local function makeMatchOpponentEntry(uuid, pinfo) local si = pinfo.showInfo or {} local score = pinfo.score or BaiZhanChengShenDefine.BZCS_INIT_SCORE return { rank = uuid2rank[uuid] or 0, uuid = uuid, serverId = pinfo.serverId or 0, name = si.name, body = getOpponentBody(pinfo), power = BaiZhanChengShenDefine.CalcPlayerPower(si, pinfo), score = score, isRobot = pinfo.isRobot, } end -- 收集当前积分窗口内可匹配候选(已选/排除名单不入列) local function collectWindowCandidates(minScore, maxScore, excludeTb) local candidates = {} for uuid, pinfo in pairs(BzcsData.playerList or {}) do if not excludeTb[uuid] then local score = pinfo.score or BaiZhanChengShenDefine.BZCS_INIT_SCORE if score >= minScore and score <= maxScore then local oppRank = uuid2rank[uuid] or 0 if oppRank > 0 then candidates[#candidates + 1] = {uuid = uuid, pinfo = pinfo} end end end end return candidates end -- 从候选中随机抽取至多 need 名加入结果 local function pickRandomFromCandidates(selected, excludeTb, candidates, need) if need <= 0 or #candidates < 1 then return end table.shuffle(candidates) for i = 1, #candidates do if need <= 0 then break end local c = candidates[i] excludeTb[c.uuid] = true selected[#selected + 1] = makeMatchOpponentEntry(c.uuid, c.pinfo) need = need - 1 end end -- 步进匹配不足时: 按与己方积分差升序补满(真人与机器人一视同仁, 可超出步进窗口) local function fillMatchOpponentsFallback(selected, excludeTb, myScore) local need = BaiZhanChengShenDefine.BZCS_OPPONENT_CNT - #selected if need <= 0 then return end local candidates = {} for uuid, pinfo in pairs(BzcsData.playerList or {}) do if not excludeTb[uuid] then local oppRank = uuid2rank[uuid] or 0 if oppRank > 0 then local score = pinfo.score or BaiZhanChengShenDefine.BZCS_INIT_SCORE candidates[#candidates + 1] = { uuid = uuid, pinfo = pinfo, scoreDist = math.abs(score - myScore), rank = oppRank, } end end end table.sort(candidates, function(a, b) if a.scoreDist ~= b.scoreDist then return a.scoreDist < b.scoreDist end return a.rank < b.rank end) for _, c in ipairs(candidates) do if need <= 0 then break end excludeTb[c.uuid] = true selected[#selected + 1] = makeMatchOpponentEntry(c.uuid, c.pinfo) need = need - 1 end end -- 按积分±step*500 步进扩大(±500/±1000/±1500...); 每步窗口内随机抽选; 不足3人时按积分差兜底补满 -- 返回 {rank,uuid,serverId,name,power,score,isRobot}[], rank 为全服积分榜名次(匹配标识) function GetMatchOpponents(myUuid, myScore, excludeTb) excludeTb = excludeTb or {} excludeTb[myUuid] = true ensureRankIndex() local selected = {} local need = BaiZhanChengShenDefine.BZCS_OPPONENT_CNT local step = 1 while need > 0 and step <= BaiZhanChengShenDefine.BZCS_MATCH_MAX_STEP do local minScore = myScore - step * BaiZhanChengShenDefine.BZCS_MATCH_STEP local maxScore = myScore + step * BaiZhanChengShenDefine.BZCS_MATCH_STEP local candidates = collectWindowCandidates(minScore, maxScore, excludeTb) pickRandomFromCandidates(selected, excludeTb, candidates, need) need = BaiZhanChengShenDefine.BZCS_OPPONENT_CNT - #selected step = step + 1 end fillMatchOpponentsFallback(selected, excludeTb, myScore) table.sort(selected, function(a, b) return (a.rank or 0) < (b.rank or 0) end) return selected end -- 按全服名次列表取对手摘要(仅刷新展示, 不重算匹配) function GetMatchOpponentsByRanks(ranks) if not ranks or #ranks == 0 then return {} end ensureRankIndex() local ret = {} for _, rank in ipairs(ranks) do local pinfo = GetPlayerByRank(rank) if pinfo then ret[#ret + 1] = makeMatchOpponentEntry(pinfo.uuid, pinfo) end end table.sort(ret, function(a, b) return (a.rank or 0) < (b.rank or 0) end) return ret end -- 新周期: 清除真人跨服数据(删号/合服后避免残留), 重置机器人池与发奖标记 function ResetForNewRound(newStart, newEnd) newStart = newStart or os.time() local robotIdx = 0 local removedCnt = 0 for uuid, pinfo in pairs(BzcsData.playerList or {}) do if pinfo.isRobot == 1 then robotIdx = robotIdx + 1 local listIdx = BaiZhanChengShenDefine.GetRobotListIndex(uuid) pinfo.score = getRobotScore(listIdx) pinfo.scoreTime = newStart + robotIdx pinfo.firstJoinTime = 0 else BzcsData.playerList[uuid] = nil removedCnt = removedCnt + 1 end end rankDirty = true BzcsData.activityStartTime = newStart BzcsData.activityEndTime = newEnd BzcsData.lastResetTime = newStart BzcsData.rewardIssued = false if not BzcsData.playerList or not next(BzcsData.playerList) then GenerateRobots() end rebuildServerList() dbUpdate._id = BzcsData._id LuaMongo.update(DB.db_bzcs, dbUpdate, BzcsData) BzcsLog.logAction("round_reset", string.format("start=%s end=%s robotCnt=%s removedReal=%s", newStart, newEnd, robotIdx, removedCnt)) end -- 周期发奖列表: 仅 firstJoinTime>0 且非机器人, 返回 {uuid, rank, serverId} function GetAllPlayersForReward() ensureRankIndex() local ret = {} for rank, entry in ipairs(rankCache or {}) do if rank > BaiZhanChengShenDefine.BZCS_REWARD_RANK_MAX then break end local pinfo = GetPlayer(entry.uuid) if pinfo and (pinfo.firstJoinTime or 0) > 0 and pinfo.isRobot ~= 1 then ret[#ret + 1] = {entry.uuid, rank, pinfo.serverId} end end return ret end