BaiZhanChengShenDB.lua 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620
  1. -- 百战成神(DB) 跨服数据层
  2. --
  3. -- 运行环境: 仅跨服进程 _G.is_middle == true
  4. -- 存储: Mongo 集合 DB.db_bzcs
  5. --
  6. -- 全服积分榜 rankCache + uuid2rank(与 rankDirty 同步重建); 排序 score 降序 -> scoreTime 升序 -> uuid
  7. -- 客户端展示榜仅前 BZCS_RANK_MAX 名, 与匹配用的全服榜不是同一展示范围
  8. --[=[
  9. BzcsData = {
  10. activityStartTime, activityEndTime, -- 本轮活动起止时间戳
  11. lastResetTime, -- 上次开轮时间(用于 BZCS_CYCLE_DAYS 周期判定)
  12. rewardIssued, -- 本轮是否已发奖
  13. playerList = {
  14. [uuid] = {
  15. uuid, serverId,
  16. score, -- 积分, 默认 BZCS_INIT_SCORE
  17. scoreTime, -- 达到当前积分的时间戳(同分比较用, 越小名次越靠前)
  18. isRobot, -- 1=机器人
  19. firstJoinTime, -- 首次挑战时间, >0 才参与周期发奖
  20. showInfo, -- { name,head,headFrame,body, heroArr={[race]=真人阵容|机器人{monsterOutID,racePower}} }
  21. },
  22. },
  23. serverList = { [serverId] = { uuid, ... } }, -- 按服索引, 发奖 WL 路由用
  24. pendingRewards = { [serverId] = { {uuid, rank}, ... } }, -- 逻辑服断连时待发奖队列
  25. }
  26. ]=]
  27. local LuaMongo = _G.lua_mongo
  28. local DB = require("common.DB")
  29. local Util = require("common.Util")
  30. local CreateRole = require("role.CreateRole")
  31. local BaiZhanChengShenDefine = require("baiZhanChengShen.BaiZhanChengShenDefine")
  32. local BzcsLog = require("baiZhanChengShen.BaiZhanChengShenLog")
  33. local BzcsConfig = require("excel.baiZhanChengShen")
  34. BzcsData = BzcsData or {}
  35. local dbUpdate = {_id = nil}
  36. local dbUpdateField = {}
  37. local rankCache = nil
  38. local uuid2rank = nil
  39. local rankDirty = true
  40. -- 增量更新 Mongo 字段
  41. local function updateValue(key, value)
  42. if not key then return end
  43. if value then
  44. dbUpdateField["$set"] = {[key] = value}
  45. dbUpdateField["$unset"] = nil
  46. else
  47. dbUpdateField["$set"] = nil
  48. dbUpdateField["$unset"] = {[key] = 1}
  49. end
  50. dbUpdate._id = BzcsData._id
  51. LuaMongo.update(DB.db_bzcs, dbUpdate, dbUpdateField)
  52. end
  53. -- 从 db_bzcs 加载或初始化空档
  54. local function loadData()
  55. LuaMongo.find(DB.db_bzcs)
  56. local data = {}
  57. if LuaMongo.next(data) then
  58. BzcsData = data
  59. else
  60. BzcsData.activityStartTime = 0
  61. BzcsData.activityEndTime = 0
  62. BzcsData.lastResetTime = 0
  63. BzcsData.rewardIssued = true
  64. BzcsData.playerList = {}
  65. LuaMongo.insert(DB.db_bzcs, BzcsData)
  66. end
  67. rankDirty = true
  68. end
  69. -- 排行同分比较用时间(达到当前积分时刻)
  70. local function getRankScoreTime(pinfo)
  71. return pinfo.scoreTime or 0
  72. end
  73. -- 按积分降序重建排行缓存; 同分按 scoreTime 升序(先达到该积分者靠前), 仍同则 uuid
  74. local function sortRankCache()
  75. if not rankDirty then return end
  76. rankCache = {}
  77. uuid2rank = {}
  78. for uuid, pinfo in pairs(BzcsData.playerList or {}) do
  79. rankCache[#rankCache + 1] = {
  80. uuid = uuid,
  81. score = pinfo.score or BaiZhanChengShenDefine.BZCS_INIT_SCORE,
  82. scoreTime = getRankScoreTime(pinfo),
  83. }
  84. end
  85. table.sort(rankCache, function(a, b)
  86. if a.score ~= b.score then
  87. return a.score > b.score
  88. end
  89. if a.scoreTime ~= b.scoreTime then
  90. return a.scoreTime < b.scoreTime
  91. end
  92. return a.uuid < b.uuid
  93. end)
  94. for rank, entry in ipairs(rankCache) do
  95. uuid2rank[entry.uuid] = rank
  96. end
  97. rankDirty = false
  98. end
  99. -- 保证 rankCache / uuid2rank 已按当前 playerList 重建
  100. local function ensureRankIndex()
  101. sortRankCache()
  102. end
  103. -- 保证 showInfo 结构完整
  104. local function normalizePlayer(pinfo)
  105. if not pinfo then return end
  106. pinfo.showInfo = pinfo.showInfo or {}
  107. pinfo.showInfo.heroArr = pinfo.showInfo.heroArr or {}
  108. end
  109. -- 按 serverId 重建 serverList(仅真人, 机器人不参与发奖路由)
  110. local function rebuildServerList()
  111. BzcsData.serverList = BzcsData.serverList or {}
  112. Util.cleanTable(BzcsData.serverList)
  113. for uuid, pinfo in pairs(BzcsData.playerList or {}) do
  114. if pinfo.isRobot ~= 1 then
  115. local sid = pinfo.serverId
  116. if sid then
  117. BzcsData.serverList[sid] = BzcsData.serverList[sid] or {}
  118. table.insert(BzcsData.serverList[sid], uuid)
  119. end
  120. end
  121. end
  122. end
  123. -- 机器单族仅存 monsterOutID + racePower(由 monsterOut 算出); 展示由 ExpandBzcsRaceShow 用时展开
  124. local function genRobotRaceShow(robotListIdx, race)
  125. local cfg = BzcsConfig.robotList[robotListIdx]
  126. if not cfg or not cfg.monsterOutIDs then return {} end
  127. local monsterOutID = cfg.monsterOutIDs[race]
  128. if not monsterOutID then return {} end
  129. return {
  130. monsterOutID = monsterOutID,
  131. formation = 1,
  132. racePower = BaiZhanChengShenDefine.CalcMonsterOutPower(monsterOutID),
  133. }
  134. end
  135. -- 按当前 robotList 重算已存在机器人的五族 monsterOutID + racePower(保留 name/head 等展示字段)
  136. local function syncRobotHeroArr(robotListIdx, pinfo)
  137. if not robotListIdx or not pinfo then return end
  138. normalizePlayer(pinfo)
  139. local heroArr = {}
  140. for _, race in ipairs(BaiZhanChengShenDefine.BZCS_RACE_ORDER) do
  141. heroArr[race] = genRobotRaceShow(robotListIdx, race)
  142. end
  143. pinfo.showInfo.heroArr = heroArr
  144. end
  145. local function getRobotScore(robotListIdx)
  146. local cfg = BzcsConfig.robotList[robotListIdx]
  147. return (cfg and cfg.score) or BaiZhanChengShenDefine.BZCS_INIT_SCORE
  148. end
  149. -- 生成机器人池: 数量 = robotList 条数, bzcs_robot_i 对应 robotList[i]
  150. function GenerateRobots(cnt)
  151. cnt = cnt or BaiZhanChengShenDefine.GetRobotListCount()
  152. if cnt < 1 then return end
  153. BzcsData.playerList = BzcsData.playerList or {}
  154. local now = os.time()
  155. for i = 1, cnt do
  156. local uuid = "bzcs_robot_" .. i
  157. if not BzcsData.playerList[uuid] then
  158. local heroArr = {}
  159. for _, race in ipairs(BaiZhanChengShenDefine.BZCS_RACE_ORDER) do
  160. heroArr[race] = genRobotRaceShow(i, race)
  161. end
  162. BzcsData.playerList[uuid] = {
  163. uuid = uuid,
  164. isRobot = 1,
  165. score = getRobotScore(i),
  166. scoreTime = now + i,
  167. firstJoinTime = now,
  168. showInfo = {
  169. name = CreateRole.getRandomName(),
  170. head = CreateRole.getRandomHead(),
  171. headFrame = CreateRole.getRandomHeadFrame(),
  172. body = CreateRole.getRandomBody(),
  173. heroArr = heroArr,
  174. },
  175. }
  176. else
  177. syncRobotHeroArr(i, BzcsData.playerList[uuid])
  178. end
  179. end
  180. rankDirty = true
  181. rebuildServerList()
  182. updateValue("playerList", BzcsData.playerList)
  183. BzcsLog.logAction("robot_gen", string.format("cnt=%s robotList=%s", cnt, BaiZhanChengShenDefine.GetRobotListCount()))
  184. end
  185. -- 逻辑服断连期间缓存的周期奖励, 重连后补发
  186. function AddPendingReward(serverId, uuid, rank)
  187. if not serverId or not uuid then return end
  188. AddPendingRewards(serverId, {{uuid, rank}})
  189. end
  190. -- 逻辑服未连接时缓存整批待发奖
  191. function AddPendingRewards(serverId, rewardList)
  192. if not serverId or not rewardList or #rewardList == 0 then return end
  193. BzcsData.pendingRewards = BzcsData.pendingRewards or {}
  194. local list = BzcsData.pendingRewards[serverId]
  195. if not list then
  196. list = {}
  197. BzcsData.pendingRewards[serverId] = list
  198. end
  199. local addCnt, skipCnt = 0, 0
  200. for _, info in ipairs(rewardList) do
  201. local uuid, rank = info[1], info[2]
  202. if uuid and uuid ~= "" and rank and rank > 0 then
  203. list[#list + 1] = {uuid, rank}
  204. addCnt = addCnt + 1
  205. else
  206. skipCnt = skipCnt + 1
  207. BzcsLog.logAction("reward_pending_skip", string.format(
  208. "serverId=%s uuid=%s rank=%s", serverId, uuid or "", rank or 0
  209. ))
  210. end
  211. end
  212. if addCnt < 1 then
  213. return
  214. end
  215. updateValue("pendingRewards." .. serverId, list)
  216. BzcsLog.logAction("reward_pending", string.format(
  217. "serverId=%s add=%s skip=%s total=%s", serverId, addCnt, skipCnt, #list
  218. ))
  219. end
  220. -- 清空全部待发奖缓存(放弃上轮发奖开新轮时用)
  221. function ClearAllPendingRewards()
  222. if not BzcsData.pendingRewards or not next(BzcsData.pendingRewards) then
  223. return
  224. end
  225. BzcsData.pendingRewards = {}
  226. updateValue("pendingRewards", BzcsData.pendingRewards)
  227. BzcsLog.logAction("reward_pending_clear", "all")
  228. end
  229. -- 取出并清空某逻辑服待发奖列表, 返回 {{uuid, rank}, ...}
  230. function TakePendingRewards(serverId)
  231. BzcsData.pendingRewards = BzcsData.pendingRewards or {}
  232. local list = BzcsData.pendingRewards[serverId]
  233. if not list or #list == 0 then
  234. return {}
  235. end
  236. BzcsData.pendingRewards[serverId] = nil
  237. updateValue("pendingRewards." .. serverId, nil)
  238. return list
  239. end
  240. -- 跨服启动: 加载数据, 同步/补全机器人池, 并向已连接逻辑服同步活动状态
  241. function initAfterStart()
  242. if _G.is_middle ~= true then return end
  243. loadData()
  244. GenerateRobots()
  245. rebuildServerList()
  246. BzcsLog.logAction("db_init", string.format("playerCnt=%s", Util.getTableCount(BzcsData.playerList or {})))
  247. local BaiZhanChengShenCS = require("baiZhanChengShen.BaiZhanChengShenCS")
  248. BaiZhanChengShenCS.syncActStateToAllConnected()
  249. end
  250. --------------------------------------------------------------------------------
  251. -- 活动周期
  252. --------------------------------------------------------------------------------
  253. -- 获取本轮活动起止时间戳
  254. function GetActivityTimes()
  255. return BzcsData.activityStartTime or 0, BzcsData.activityEndTime or 0
  256. end
  257. -- 获取上次开新轮时间(BZCS_CYCLE_DAYS 周期判定)
  258. function GetLastResetTime()
  259. return BzcsData.lastResetTime or 0
  260. end
  261. -- 本轮周期奖励是否已发放
  262. function IsRewardIssued()
  263. return BzcsData.rewardIssued == true
  264. end
  265. -- 设置活动时间并清除发奖标记(开新轮)
  266. function SetActivityTimes(startTime, endTime)
  267. BzcsData.activityStartTime = startTime
  268. BzcsData.activityEndTime = endTime
  269. BzcsData.lastResetTime = startTime
  270. BzcsData.rewardIssued = false
  271. updateValue("activityStartTime", startTime)
  272. updateValue("activityEndTime", endTime)
  273. updateValue("lastResetTime", startTime)
  274. updateValue("rewardIssued", false)
  275. end
  276. -- 标记本轮是否已发奖
  277. function SetRewardIssued(flag)
  278. BzcsData.rewardIssued = flag
  279. updateValue("rewardIssued", flag)
  280. end
  281. -- 按 uuid 查询跨服玩家/机器人
  282. function GetPlayer(uuid)
  283. local pinfo = BzcsData.playerList and BzcsData.playerList[uuid]
  284. if pinfo then
  285. normalizePlayer(pinfo)
  286. end
  287. return pinfo
  288. end
  289. --------------------------------------------------------------------------------
  290. -- 玩家数据
  291. --------------------------------------------------------------------------------
  292. -- 新增或合并玩家跨服数据
  293. function UpsertPlayer(uuid, data)
  294. BzcsData.playerList = BzcsData.playerList or {}
  295. local old = BzcsData.playerList[uuid]
  296. if old then
  297. for k, v in pairs(data) do
  298. old[k] = v
  299. end
  300. data = old
  301. else
  302. BzcsData.playerList[uuid] = data
  303. end
  304. data.uuid = uuid
  305. normalizePlayer(data)
  306. rankDirty = true
  307. rebuildServerList()
  308. updateValue("playerList." .. uuid, data)
  309. return data
  310. end
  311. -- 增减积分并落库(积分变化时刷新 scoreTime 供同分排序)
  312. function UpdateScore(uuid, delta)
  313. local pinfo = GetPlayer(uuid)
  314. if not pinfo then
  315. BzcsLog.logAction("score_miss", string.format("uuid=%s delta=%s", uuid or "", delta or 0))
  316. return
  317. end
  318. pinfo.score = (pinfo.score or BaiZhanChengShenDefine.BZCS_INIT_SCORE) + delta
  319. pinfo.scoreTime = os.time()
  320. rankDirty = true
  321. updateValue("playerList." .. uuid .. ".score", pinfo.score)
  322. updateValue("playerList." .. uuid .. ".scoreTime", pinfo.scoreTime)
  323. BzcsLog.logAction("score", string.format("uuid=%s delta=%s score=%s isRobot=%s", uuid, delta, pinfo.score, pinfo.isRobot or 0))
  324. return pinfo.score
  325. end
  326. --------------------------------------------------------------------------------
  327. -- 排行与匹配
  328. --------------------------------------------------------------------------------
  329. -- 查询玩家当前全服名次, 0=未上榜
  330. function GetRankByUuid(uuid)
  331. if not uuid then return 0 end
  332. ensureRankIndex()
  333. return uuid2rank[uuid] or 0
  334. end
  335. -- 按全服名次取玩家(含机器人), rank 从 1 起
  336. function GetPlayerByRank(rank)
  337. if not rank or rank < 1 then return nil end
  338. ensureRankIndex()
  339. local entry = rankCache and rankCache[rank]
  340. if not entry then return nil end
  341. return GetPlayer(entry.uuid)
  342. end
  343. -- 单条排行榜展示结构(榜单与 myRankInfo 共用)
  344. function BuildRankInfoEntry(pinfo, rank)
  345. if not pinfo then return nil end
  346. local si = pinfo.showInfo or {}
  347. return {
  348. rank = rank or 0,
  349. uuid = pinfo.uuid,
  350. name = si.name or "",
  351. head = si.head or 0,
  352. headFrame = si.headFrame or 0,
  353. serverId = pinfo.serverId or 0,
  354. power = BaiZhanChengShenDefine.CalcPlayerPower(si, pinfo),
  355. score = pinfo.score or BaiZhanChengShenDefine.BZCS_INIT_SCORE,
  356. isRobot = pinfo.isRobot,
  357. }
  358. end
  359. -- 指定玩家排行展示(未注册跨服时仅 rank/score 等默认值)
  360. function BuildPlayerRankInfo(uuid)
  361. local rank = GetRankByUuid(uuid)
  362. local pinfo = GetPlayer(uuid)
  363. local info = BuildRankInfoEntry(pinfo, rank)
  364. if info then return info end
  365. return {
  366. rank = rank,
  367. uuid = uuid or "",
  368. name = "",
  369. head = 0,
  370. headFrame = 0,
  371. serverId = 0,
  372. power = 0,
  373. score = BaiZhanChengShenDefine.BZCS_INIT_SCORE,
  374. }
  375. end
  376. -- 对手详情 WL 回包(仅 GC_BZCS_OPPONENT_INFO 所需字段)
  377. function BuildOpponentInfoSnapshot(pinfo)
  378. if not pinfo then return nil end
  379. local si = pinfo.showInfo or {}
  380. return {
  381. name = si.name or "",
  382. head = si.head or 0,
  383. headFrame = si.headFrame or 0,
  384. power = BaiZhanChengShenDefine.CalcPlayerPower(si, pinfo),
  385. score = pinfo.score or BaiZhanChengShenDefine.BZCS_INIT_SCORE,
  386. }
  387. end
  388. -- 客户端展示榜前 limit 名(默认100), 含 rank/uuid/展示字段
  389. function GetRankList(limit)
  390. ensureRankIndex()
  391. limit = limit or BaiZhanChengShenDefine.BZCS_RANK_MAX
  392. local ret = {}
  393. for i = 1, math.min(limit, #(rankCache or {})) do
  394. local entry = rankCache[i]
  395. local pinfo = GetPlayer(entry.uuid)
  396. local info = BuildRankInfoEntry(pinfo, i)
  397. if info then
  398. ret[#ret + 1] = info
  399. end
  400. end
  401. return ret
  402. end
  403. -- 形象仅读跨服 showInfo.body(逻辑服 REGISTER/UPDATE_SHOW 同步), 不可查逻辑服 RoleDB
  404. local function getOpponentBody(pinfo)
  405. local si = pinfo.showInfo or {}
  406. return si.body or 0
  407. end
  408. local function makeMatchOpponentEntry(uuid, pinfo)
  409. local si = pinfo.showInfo or {}
  410. local score = pinfo.score or BaiZhanChengShenDefine.BZCS_INIT_SCORE
  411. return {
  412. rank = uuid2rank[uuid] or 0,
  413. uuid = uuid,
  414. serverId = pinfo.serverId or 0,
  415. name = si.name,
  416. body = getOpponentBody(pinfo),
  417. power = BaiZhanChengShenDefine.CalcPlayerPower(si, pinfo),
  418. score = score,
  419. isRobot = pinfo.isRobot,
  420. }
  421. end
  422. -- 收集当前积分窗口内可匹配候选(已选/排除名单不入列)
  423. local function collectWindowCandidates(minScore, maxScore, excludeTb)
  424. local candidates = {}
  425. for uuid, pinfo in pairs(BzcsData.playerList or {}) do
  426. if not excludeTb[uuid] then
  427. local score = pinfo.score or BaiZhanChengShenDefine.BZCS_INIT_SCORE
  428. if score >= minScore and score <= maxScore then
  429. local oppRank = uuid2rank[uuid] or 0
  430. if oppRank > 0 then
  431. candidates[#candidates + 1] = {uuid = uuid, pinfo = pinfo}
  432. end
  433. end
  434. end
  435. end
  436. return candidates
  437. end
  438. -- 从候选中随机抽取至多 need 名加入结果
  439. local function pickRandomFromCandidates(selected, excludeTb, candidates, need)
  440. if need <= 0 or #candidates < 1 then
  441. return
  442. end
  443. table.shuffle(candidates)
  444. for i = 1, #candidates do
  445. if need <= 0 then
  446. break
  447. end
  448. local c = candidates[i]
  449. excludeTb[c.uuid] = true
  450. selected[#selected + 1] = makeMatchOpponentEntry(c.uuid, c.pinfo)
  451. need = need - 1
  452. end
  453. end
  454. -- 步进匹配不足时: 按与己方积分差升序补满(真人与机器人一视同仁, 可超出步进窗口)
  455. local function fillMatchOpponentsFallback(selected, excludeTb, myScore)
  456. local need = BaiZhanChengShenDefine.BZCS_OPPONENT_CNT - #selected
  457. if need <= 0 then
  458. return
  459. end
  460. local candidates = {}
  461. for uuid, pinfo in pairs(BzcsData.playerList or {}) do
  462. if not excludeTb[uuid] then
  463. local oppRank = uuid2rank[uuid] or 0
  464. if oppRank > 0 then
  465. local score = pinfo.score or BaiZhanChengShenDefine.BZCS_INIT_SCORE
  466. candidates[#candidates + 1] = {
  467. uuid = uuid,
  468. pinfo = pinfo,
  469. scoreDist = math.abs(score - myScore),
  470. rank = oppRank,
  471. }
  472. end
  473. end
  474. end
  475. table.sort(candidates, function(a, b)
  476. if a.scoreDist ~= b.scoreDist then
  477. return a.scoreDist < b.scoreDist
  478. end
  479. return a.rank < b.rank
  480. end)
  481. for _, c in ipairs(candidates) do
  482. if need <= 0 then
  483. break
  484. end
  485. excludeTb[c.uuid] = true
  486. selected[#selected + 1] = makeMatchOpponentEntry(c.uuid, c.pinfo)
  487. need = need - 1
  488. end
  489. end
  490. -- 按积分±step*500 步进扩大(±500/±1000/±1500...); 每步窗口内随机抽选; 不足3人时按积分差兜底补满
  491. -- 返回 {rank,uuid,serverId,name,power,score,isRobot}[], rank 为全服积分榜名次(匹配标识)
  492. function GetMatchOpponents(myUuid, myScore, excludeTb)
  493. excludeTb = excludeTb or {}
  494. excludeTb[myUuid] = true
  495. ensureRankIndex()
  496. local selected = {}
  497. local need = BaiZhanChengShenDefine.BZCS_OPPONENT_CNT
  498. local step = 1
  499. while need > 0 and step <= BaiZhanChengShenDefine.BZCS_MATCH_MAX_STEP do
  500. local minScore = myScore - step * BaiZhanChengShenDefine.BZCS_MATCH_STEP
  501. local maxScore = myScore + step * BaiZhanChengShenDefine.BZCS_MATCH_STEP
  502. local candidates = collectWindowCandidates(minScore, maxScore, excludeTb)
  503. pickRandomFromCandidates(selected, excludeTb, candidates, need)
  504. need = BaiZhanChengShenDefine.BZCS_OPPONENT_CNT - #selected
  505. step = step + 1
  506. end
  507. fillMatchOpponentsFallback(selected, excludeTb, myScore)
  508. table.sort(selected, function(a, b)
  509. return (a.rank or 0) < (b.rank or 0)
  510. end)
  511. return selected
  512. end
  513. -- 按全服名次列表取对手摘要(仅刷新展示, 不重算匹配)
  514. function GetMatchOpponentsByRanks(ranks)
  515. if not ranks or #ranks == 0 then
  516. return {}
  517. end
  518. ensureRankIndex()
  519. local ret = {}
  520. for _, rank in ipairs(ranks) do
  521. local pinfo = GetPlayerByRank(rank)
  522. if pinfo then
  523. ret[#ret + 1] = makeMatchOpponentEntry(pinfo.uuid, pinfo)
  524. end
  525. end
  526. table.sort(ret, function(a, b)
  527. return (a.rank or 0) < (b.rank or 0)
  528. end)
  529. return ret
  530. end
  531. -- 新周期: 清除真人跨服数据(删号/合服后避免残留), 重置机器人池与发奖标记
  532. function ResetForNewRound(newStart, newEnd)
  533. newStart = newStart or os.time()
  534. local robotIdx = 0
  535. local removedCnt = 0
  536. for uuid, pinfo in pairs(BzcsData.playerList or {}) do
  537. if pinfo.isRobot == 1 then
  538. robotIdx = robotIdx + 1
  539. local listIdx = BaiZhanChengShenDefine.GetRobotListIndex(uuid)
  540. pinfo.score = getRobotScore(listIdx)
  541. pinfo.scoreTime = newStart + robotIdx
  542. pinfo.firstJoinTime = 0
  543. else
  544. BzcsData.playerList[uuid] = nil
  545. removedCnt = removedCnt + 1
  546. end
  547. end
  548. rankDirty = true
  549. BzcsData.activityStartTime = newStart
  550. BzcsData.activityEndTime = newEnd
  551. BzcsData.lastResetTime = newStart
  552. BzcsData.rewardIssued = false
  553. if not BzcsData.playerList or not next(BzcsData.playerList) then
  554. GenerateRobots()
  555. end
  556. rebuildServerList()
  557. dbUpdate._id = BzcsData._id
  558. LuaMongo.update(DB.db_bzcs, dbUpdate, BzcsData)
  559. BzcsLog.logAction("round_reset", string.format("start=%s end=%s robotCnt=%s removedReal=%s", newStart, newEnd, robotIdx, removedCnt))
  560. end
  561. -- 周期发奖列表: 仅 firstJoinTime>0 且非机器人, 返回 {uuid, rank, serverId}
  562. function GetAllPlayersForReward()
  563. ensureRankIndex()
  564. local ret = {}
  565. for rank, entry in ipairs(rankCache or {}) do
  566. if rank > BaiZhanChengShenDefine.BZCS_REWARD_RANK_MAX then
  567. break
  568. end
  569. local pinfo = GetPlayer(entry.uuid)
  570. if pinfo and (pinfo.firstJoinTime or 0) > 0 and pinfo.isRobot ~= 1 then
  571. ret[#ret + 1] = {entry.uuid, rank, pinfo.serverId}
  572. end
  573. end
  574. return ret
  575. end