| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614 |
- -- 百战成神(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 RoleDBLogic = require("role.RoleDBLogic")
- 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
- 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,
- },
- }
- 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()
- if not BzcsData.playerList or not next(BzcsData.playerList) then
- GenerateRobots()
- end
- 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),
- 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),
- 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
- local function getOpponentBody(uuid, pinfo)
- local si = pinfo.showInfo or {}
- local body = si.body or 0
- if body ~= 0 or pinfo.isRobot == 1 then
- return body
- end
- local db = RoleDBLogic.getDb(uuid, "body")
- return db and db.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(uuid, pinfo),
- power = BaiZhanChengShenDefine.CalcPlayerPower(si),
- 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
|