gitxsm hace 2 días
padre
commit
540902f5ef

+ 133 - 54
script/module/baiZhanChengShen/BaiZhanChengShenCS.lua

@@ -20,6 +20,10 @@ local BzcsLog = require("baiZhanChengShen.BaiZhanChengShenLog")
 ------------------------------------ 活动周期调度 ------------------------------------
 
 local wDay
+-- 本轮发奖 Timer 已调度(进程内防重; rewardIssued 在调度前落库防 oneMin 重复触发)
+local rewardIssueInProgress = false
+-- 上一 tick 是否处于可挑战窗口(用于 0:10 到点补广播, 跨服重启后首次 tick 也会触发一次)
+local prevIsRunning = false
 
 -- 刷新缓存的当前星期
 local function updateWDay()
@@ -73,7 +77,20 @@ local function isInOpenWday()
     return wDay >= startW or wDay <= endW
 end
 
--- 活动是否在时间窗内且处于开放星期
+-- 本轮在 DB 中仍有效(未过 endTime 且处于开放周), 含 startTime 前待开启时段
+local function hasActiveRound(now)
+    now = now or os.time()
+    local startTime, endTime = BaiZhanChengShenDB.GetActivityTimes()
+    if startTime <= 0 or endTime <= 0 then
+        return false
+    end
+    if now >= endTime then
+        return false
+    end
+    return isInOpenWday()
+end
+
+-- 活动是否处于可挑战窗口(已过本轮 startTime)
 local function isRunning()
     local now = os.time()
     local startTime, endTime = BaiZhanChengShenDB.GetActivityTimes()
@@ -86,18 +103,69 @@ local function isRunning()
     return isInOpenWday()
 end
 
+-- 严格判断当前是否可开启新一轮: 周末 + 距上轮足够 + 已过本轮周六 0:10 + 上轮已结束
+local function canOpenNewRoundNow(now, lastReset, startTime, endTime)
+    now = now or os.time()
+    if not isInOpenWday() then
+        return false
+    end
+    local roundStart = alignRoundStart(now)
+    if now < roundStart then
+        return false
+    end
+    if lastReset == 0 then
+        return true
+    end
+    if Util.diffDay(lastReset) < BaiZhanChengShenDefine.BZCS_CYCLE_DAYS then
+        return false
+    end
+    if startTime > 0 and now < endTime then
+        return false
+    end
+    return true
+end
+
+-- 汇总本次 WL 广播已通知的逻辑服 serverId(MiddleManager_GetAllFD 返回 SVRINDEX_2_FD, key 即 serverId)
+local function formatNotifyServerIds(svrIndex2Fd)
+    local seen, serverIds = {}, {}
+    for serverId, _ in pairs(svrIndex2Fd or {}) do
+        if serverId and not seen[serverId] then
+            seen[serverId] = true
+            serverIds[#serverIds + 1] = serverId
+        end
+    end
+    -- 合服扩展连接(仅 MERGE 表有登记时补充)
+    for serverId, _ in pairs(SVRINDEX_2_FD_MERGE or {}) do
+        if serverId and not seen[serverId] then
+            seen[serverId] = true
+            serverIds[#serverIds + 1] = serverId
+        end
+    end
+    table.sort(serverIds)
+    return table.concat(serverIds, ",")
+end
+
 -- 向单个逻辑服 fd 推送当前活动状态(重连/跨服重启后补偿, 避免错过广播)
 local function sendActStateToFd(fd)
     if not fd then return end
+    local serverId = MiddleManager.FD_2_SVRINDEX[fd] or 0
     local startTime, endTime = BaiZhanChengShenDB.GetActivityTimes()
-    if startTime > 0 and endTime > 0 and isRunning() then
+    if hasActiveRound() then
         local msgData = InnerMsg.wl.WL_BZCS_ACT_START
         msgData.startTime = startTime
         msgData.endTime = endTime
         InnerMsg.sendMsg(fd, msgData)
+        BzcsLog.logAction("act_open", string.format(
+            "tag=sync_fd serverId=%s fd=%s start=%s end=%s running=%s",
+            serverId, fd, startTime, endTime, isRunning() and 1 or 0
+        ))
     else
         local msgData = InnerMsg.wl.WL_BZCS_ACT_END
         InnerMsg.sendMsg(fd, msgData)
+        BzcsLog.logAction("act_close", string.format(
+            "tag=sync_fd serverId=%s fd=%s start=%s end=%s running=0",
+            serverId, fd, startTime, endTime
+        ))
     end
 end
 
@@ -105,7 +173,7 @@ end
 function onLogicServerConnect(fd)
     if _G.is_middle ~= true or not fd then return end
     sendActStateToFd(fd)
-    local serverId = FD_2_SVRINDEX and FD_2_SVRINDEX[fd]
+    local serverId = MiddleManager.FD_2_SVRINDEX[fd]
     if not serverId then return end
     local pending = BaiZhanChengShenDB.TakePendingRewards(serverId)
     if #pending > 0 then
@@ -125,25 +193,42 @@ function syncActStateToAllConnected()
     end
 end
 
--- 广播活动开启, 普通服写入 KEY_BZCS_START_TIME
-function ActOpen(startTime)
+-- 广播活动开启, 普通服写入 KEY_BZCS_START_TIME; tag 用于区分开轮/时间修正等场景
+function ActOpen(startTime, tag)
+    tag = tag or "broadcast"
     local msgData = InnerMsg.wl.WL_BZCS_ACT_START
     msgData.startTime = startTime or os.time()
     local _, endTime = BaiZhanChengShenDB.GetActivityTimes()
     msgData.endTime = endTime
-    local fdList = MiddleManager.MiddleManager_GetAllFD()
-    for _, fd in pairs(fdList) do
+    local svrIndex2Fd = MiddleManager.MiddleManager_GetAllFD()
+    local fdCnt = 0
+    for serverId, fd in pairs(svrIndex2Fd) do
         InnerMsg.sendMsg(fd, msgData)
+        fdCnt = fdCnt + 1
     end
+    BzcsLog.logAction("act_open", string.format(
+        "tag=%s start=%s end=%s fdCnt=%s running=%s servers=%s",
+        tag, msgData.startTime, msgData.endTime or 0, fdCnt, isRunning() and 1 or 0,
+        formatNotifyServerIds(svrIndex2Fd)
+    ))
 end
 
--- 广播活动结束
-function ActEnd()
+-- 广播活动结束; tag 用于区分正常结算/放弃上轮等场景
+function ActEnd(tag)
+    tag = tag or "broadcast"
+    local startTime, endTime = BaiZhanChengShenDB.GetActivityTimes()
     local msgData = InnerMsg.wl.WL_BZCS_ACT_END
-    local fdList = MiddleManager.MiddleManager_GetAllFD()
-    for _, fd in pairs(fdList) do
+    local svrIndex2Fd = MiddleManager.MiddleManager_GetAllFD()
+    local fdCnt = 0
+    for serverId, fd in pairs(svrIndex2Fd) do
         InnerMsg.sendMsg(fd, msgData)
+        fdCnt = fdCnt + 1
     end
+    BzcsLog.logAction("act_close", string.format(
+        "tag=%s start=%s end=%s fdCnt=%s rewardIssued=%s servers=%s",
+        tag, startTime or 0, endTime or 0, fdCnt, BaiZhanChengShenDB.IsRewardIssued() and 1 or 0,
+        formatNotifyServerIds(svrIndex2Fd)
+    ))
 end
 
 -- 按 serverId 汇总待发奖 {{uuid,rank},...}
@@ -178,18 +263,9 @@ local function issueRewardBatch(serverId, rewardList)
     end
 end
 
--- 末批发奖完成后标记 rewardIssued(1=最后一服)
-local function issueRewardBatchFinish(serverId, rewardList, markIssued)
-    issueRewardBatch(serverId, rewardList)
-    if markIssued == 1 then
-        BaiZhanChengShenDB.SetRewardIssued(true)
-        BzcsLog.logAction("reward_issue_done", string.format("serverId=%s lastBatchCnt=%s", serverId, #rewardList))
-    end
-end
-
 -- 活动结束发奖: 按逻辑服批量 WL_BZCS_ISSUE_REWARD(服与服之间 2s 节流)
 function IssueRewardManager()
-    if BaiZhanChengShenDB.IsRewardIssued() then
+    if BaiZhanChengShenDB.IsRewardIssued() or rewardIssueInProgress then
         return
     end
     local rewardPlayers = BaiZhanChengShenDB.GetAllPlayersForReward()
@@ -202,13 +278,28 @@ function IssueRewardManager()
         BaiZhanChengShenDB.SetRewardIssued(true)
         return
     end
+    rewardIssueInProgress = true
+    -- 先落库标记, 避免异步 Timer 完成前 timedStageHandle/oneMin 重复调度
+    BaiZhanChengShenDB.SetRewardIssued(true)
     BzcsLog.logAction("reward_issue", string.format("playerCnt=%s serverCnt=%s", #rewardPlayers, #serverList))
     local delay = 0
-    for i, entry in ipairs(serverList) do
-        delay = delay + 2
-        local markIssued = (i == #serverList) and 1 or 0
-        Timer.addLater(delay, issueRewardBatchFinish, entry[1], entry[2], markIssued)
+    local onlineCnt, pendingCnt = 0, 0
+    for _, entry in ipairs(serverList) do
+        local serverId, rewardList = entry[1], entry[2]
+        local fd = MiddleManager.getFDBySvrIndex(serverId)
+        if not fd then
+            BaiZhanChengShenDB.AddPendingRewards(serverId, rewardList)
+            pendingCnt = pendingCnt + 1
+        else
+            delay = delay + 2
+            onlineCnt = onlineCnt + 1
+            Timer.addLater(delay, issueRewardBatch, serverId, rewardList)
+        end
     end
+    rewardIssueInProgress = false
+    BzcsLog.logAction("reward_issue_scheduled", string.format(
+        "onlineCnt=%s pendingCnt=%s delaySec=%s", onlineCnt, pendingCnt, delay
+    ))
 end
 
 -- 开启新周期: 重置 DB 并 ActOpen
@@ -217,18 +308,17 @@ local function newRoundHandle(now)
     local startTime = alignRoundStart(now)
     local endTime = calcRoundEnd(startTime)
     BaiZhanChengShenDB.ResetForNewRound(startTime, endTime)
-    ActOpen(startTime)
-    BzcsLog.logAction("act_open", string.format("start=%s end=%s", startTime, endTime))
+    ActOpen(startTime, "new_round")
 end
 
 -- 结束当前周期: 发奖 + ActEnd
 local function endRoundHandle()
-    if BaiZhanChengShenDB.IsRewardIssued() then
+    if BaiZhanChengShenDB.IsRewardIssued() or rewardIssueInProgress then
         return
     end
-    BzcsLog.logAction("act_end", "begin_issue_reward")
+    BzcsLog.logAction("act_settle", "begin_issue_reward")
     IssueRewardManager()
-    ActEnd()
+    ActEnd("end_round")
 end
 
 -- 放弃上轮未完成的周期发奖(满21天开新轮时不再补发)
@@ -239,25 +329,15 @@ local function abandonUnissuedRewards()
     BzcsLog.logAction("reward_abandon", "new_round_skip")
     BaiZhanChengShenDB.SetRewardIssued(true)
     BaiZhanChengShenDB.ClearAllPendingRewards()
-    ActEnd()
+    ActEnd("abandon_new_round")
 end
 
--- 是否应开启新轮(首次开轮 / 满21天开放日); 成功则已执行 newRoundHandle
+-- 是否应开启新轮; 条件满足则执行 newRoundHandle 并广播
 local function tryOpenNewRound(now, lastReset, startTime, endTime)
-    if not isInOpenWday() then
+    if not canOpenNewRoundNow(now, lastReset, startTime, endTime) then
         return false
     end
-    if lastReset == 0 then
-        newRoundHandle(now)
-        return true
-    end
-    if Util.diffDay(lastReset) < BaiZhanChengShenDefine.BZCS_CYCLE_DAYS then
-        return false
-    end
-    if startTime > 0 and now < endTime then
-        return false
-    end
-    if not BaiZhanChengShenDB.IsRewardIssued() then
+    if lastReset > 0 and not BaiZhanChengShenDB.IsRewardIssued() then
         abandonUnissuedRewards()
     end
     newRoundHandle(now)
@@ -265,31 +345,30 @@ local function tryOpenNewRound(now, lastReset, startTime, endTime)
 end
 
 -- 周期阶段机 (oneMin/onHour 调用):
--- 1) 满足新轮条件(含满21天且上轮未发奖则放弃发奖直接开轮) -> newRoundHandle
--- 2) 已过 endTime 且未发奖且未满21天新轮 -> endRoundHandle
--- 3) 活动记录中但 isRunning 为假(如跨天) -> 修正时间并 ActOpen
+-- 1) 严格满足开新轮条件(周末/间隔/已过周六0:10/上轮已结束) -> newRoundHandle + ActOpen
+-- 2) 已过 endTime 且未发奖 -> endRoundHandle
+-- 3) 刚进入可挑战窗口(如周六0:10) -> ActOpen 补偿逻辑服
 local function timedStageHandle()
     local now = os.time()
     local lastReset = BaiZhanChengShenDB.GetLastResetTime()
     local startTime, endTime = BaiZhanChengShenDB.GetActivityTimes()
+    local running = isRunning()
 
     if tryOpenNewRound(now, lastReset, startTime, endTime) then
+        prevIsRunning = isRunning()
         return
     end
 
     if startTime > 0 and now >= endTime and not BaiZhanChengShenDB.IsRewardIssued() then
         endRoundHandle()
+        prevIsRunning = false
         return
     end
 
-    if startTime > 0 and now >= startTime and now < endTime and not isRunning() then
-        if isInOpenWday() then
-            local ts = alignRoundStart(now)
-            local te = calcRoundEnd(ts)
-            BaiZhanChengShenDB.SetActivityTimes(ts, te)
-            ActOpen(ts)
-        end
+    if running and not prevIsRunning then
+        ActOpen(startTime, "window_open")
     end
+    prevIsRunning = running
 end
 
 -- Timer 每分钟(跳过整点)检查阶段

+ 21 - 6
script/module/baiZhanChengShen/BaiZhanChengShenDB.lua

@@ -8,7 +8,7 @@
 --[=[
 BzcsData = {
     activityStartTime, activityEndTime,  -- 本轮活动起止时间戳
-    lastResetTime,                       -- 上次开轮时间(用于21天周期判定)
+    lastResetTime,                       -- 上次开轮时间(用于 BZCS_CYCLE_DAYS 周期判定)
     rewardIssued,                        -- 本轮是否已发奖
     playerList = {
         [uuid] = {
@@ -200,11 +200,26 @@ function AddPendingRewards(serverId, rewardList)
         list = {}
         BzcsData.pendingRewards[serverId] = list
     end
+    local addCnt, skipCnt = 0, 0
     for _, info in ipairs(rewardList) do
-        list[#list + 1] = {info[1], info[2]}
+        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 total=%s", serverId, #rewardList, #list))
+    BzcsLog.logAction("reward_pending", string.format(
+        "serverId=%s add=%s skip=%s total=%s", serverId, addCnt, skipCnt, #list
+    ))
 end
 
 -- 清空全部待发奖缓存(放弃上轮发奖开新轮时用)
@@ -251,7 +266,7 @@ function GetActivityTimes()
     return BzcsData.activityStartTime or 0, BzcsData.activityEndTime or 0
 end
 
--- 获取上次开新轮时间(21 天周期判定)
+-- 获取上次开新轮时间(BZCS_CYCLE_DAYS 周期判定)
 function GetLastResetTime()
     return BzcsData.lastResetTime or 0
 end
@@ -303,8 +318,8 @@ function UpsertPlayer(uuid, data)
         data = old
     else
         BzcsData.playerList[uuid] = data
-        data.uuid = uuid
     end
+    data.uuid = uuid
     normalizePlayer(data)
     rankDirty = true
     rebuildServerList()
@@ -593,7 +608,7 @@ function GetAllPlayersForReward()
         end
         local pinfo = GetPlayer(entry.uuid)
         if pinfo and (pinfo.firstJoinTime or 0) > 0 and pinfo.isRobot ~= 1 then
-            ret[#ret + 1] = {pinfo.uuid, rank, pinfo.serverId}
+            ret[#ret + 1] = {entry.uuid, rank, pinfo.serverId}
         end
     end
     return ret

+ 3 - 2
script/module/baiZhanChengShen/BaiZhanChengShenDefine.lua

@@ -2,7 +2,7 @@
 --
 -- 玩法概要:
 --   跨服 5v5 积分 PvP, 5 种族独立阵容(COMBAT_TYPE39~43), 5 局 3 胜制。
---   开服>=45 天可参与; 周六 0:10 ~ 周日 23:00 可挑战; 每 21 天(3 周) 开新轮。
+--   开服>=45 天可参与; 周六 0:10 ~ 周日 23:00 可挑战; 每轮开放后隔 3 个开放周,第 5 周再开新轮。
 --   一次点击挑战仅扣 1 次(每日 5 次免费, 不足扣道具 115 x5)。
 --   攻胜+100/攻负-50, 守胜+50/守负-50; 初始积分 3000。
 --
@@ -15,7 +15,8 @@
 BZCS_OPEN_WDAY_AREA   = {7, 1}    -- 可挑战星期: 周六~周日 (getWeekDay: 7=周六, 1=周日)
 BZCS_START_SEC        = 600       -- 每日开始时刻: 0:10 (距 0 点秒数)
 BZCS_END_SEC          = 82800     -- 每日结束时刻: 23:00
-BZCS_CYCLE_DAYS       = 21        -- 两轮活动最小间隔(天), 即 3 周
+BZCS_SKIP_OPEN_WEEKS  = 3         -- 本轮开放后跳过的开放周数(第2~4周不开新轮)
+BZCS_CYCLE_DAYS       = (1 + BZCS_SKIP_OPEN_WEEKS) * 7  -- 距上次开轮最少天数(28), 第5个开放周可开
 BZCS_OPEN_DAYS        = 2         -- 单轮活动持续天数(周六+周日, 用于 IsRunning 校验)
 
 -- 开放结束日相对开始日的天数偏移(周六->周日为 1)

+ 25 - 1
script/module/baiZhanChengShen/BaiZhanChengShenNS.lua

@@ -676,6 +676,7 @@ local function challengeFinish(human, cache)
         oppServerId = cache.defServerId or 0,
         oppName = cache.defName or "",
         scoreChange = scoreChange,
+        isRobot = cache.isRobot or 0,
     })
 
     human.bzcs_Battle_Cache = nil
@@ -796,7 +797,15 @@ function BZCS_WarReport(human)
         local r = list[total - j + 1]
         local net = msgRet.reportList[j]
         net.warType = r.warType or 0
-        net.oppServerId = BaiZhanChengShenDefine.ToClientServerId(r.oppServerId, 0)
+        local isRobot = r.isRobot or 0
+        if isRobot ~= 1 and (r.oppServerId or 0) == 0 then
+            local warType = r.warType or 0
+            if warType == BaiZhanChengShenDefine.BZCS_WAR_TYPE_ATK_WIN
+                or warType == BaiZhanChengShenDefine.BZCS_WAR_TYPE_ATK_LOSE then
+                isRobot = 1
+            end
+        end
+        net.oppServerId = BaiZhanChengShenDefine.ToClientServerId(r.oppServerId, isRobot)
         net.oppName = r.oppName or ""
         net.scoreChange = r.scoreChange or 0
     end
@@ -824,6 +833,8 @@ end
 
 -- WL_BZCS_ACT_START: 记录本轮开始时间(重连补偿广播也会走到这里)
 function C2N_Act_Start(msg)
+    local oldStart = CommonDB.getValueByKey(CommonDB.KEY_BZCS_START_TIME) or 0
+    local resetCnt = 0
     if msg.startTime and msg.startTime > 0 then
         CommonDB.updateValue(CommonDB.KEY_BZCS_START_TIME, msg.startTime)
         for uuid, human in pairs(ObjHuman.onlineUuid) do
@@ -833,22 +844,35 @@ function C2N_Act_Start(msg)
                     human.db.baiZhanChengShen.actStartTime = actStart
                     clearRoundLocalData(human.db.baiZhanChengShen)
                     clearMatchCache(human)
+                    resetCnt = resetCnt + 1
                 end
             end
         end
     end
+    BzcsLog.logAction("act_open", string.format(
+        "svr=%s start=%s end=%s oldStart=%s online=%s resetCnt=%s",
+        Config.SVR_INDEX, msg.startTime or 0, msg.endTime or 0, oldStart,
+        Util.getTableCount(ObjHuman.onlineUuid), resetCnt
+    ))
 end
 
 -- WL_BZCS_ACT_END: 清除活动开始时间, 并清空在线玩家本轮本地战报/匹配缓存
 function C2N_Act_End(msg)
+    local oldStart = CommonDB.getValueByKey(CommonDB.KEY_BZCS_START_TIME) or 0
     CommonDB.updateValue(CommonDB.KEY_BZCS_START_TIME, nil)
+    local resetCnt = 0
     for uuid, human in pairs(ObjHuman.onlineUuid) do
         if human.db and human.db.baiZhanChengShen then
             clearRoundLocalData(human.db.baiZhanChengShen)
             human.db.baiZhanChengShen.actStartTime = 0
             clearMatchCache(human)
+            resetCnt = resetCnt + 1
         end
     end
+    BzcsLog.logAction("act_close", string.format(
+        "svr=%s oldStart=%s online=%s resetCnt=%s",
+        Config.SVR_INDEX, oldStart, Util.getTableCount(ObjHuman.onlineUuid), resetCnt
+    ))
 end
 
 -- WL_BZCS_TIPS: 跨服业务错误(活动未开/对手无效等)

+ 0 - 299
script/module/baiZhanChengShen/百战成神.md

@@ -1,299 +0,0 @@
-# 百战成神
-
-跨服五族积分 PvP:玩家为妖、人、兽、仙、魔五个种族各配置独立阵容,向积分榜上的对手发起 **5 局 3 胜** 的系列战,按整场胜负结算积分;周期结束按全服排名发奖。
-
----
-
-## 1. 玩法概要
-
-| 项目 | 规则 |
-|------|------|
-| 战斗形式 | 五族独立阵容,对应战斗类型 `COMBAT_TYPE39~43` |
-| 系列赛制 | 最多打满 5 族;先赢 **3** 族者赢得整场 |
-| 积分 | 初始 **3000**;进攻胜 +20 / 负 -10;防守胜 +10 / 负 -20 |
-| 对手 | 全服积分榜(含机器人),单次展示 **3** 名匹配对手 |
-| 次数 | 每日 **5** 次免费;用尽后每次消耗道具 **115 × 5** |
-| 架构 | 普通服 **NS** + 跨服 **CS** + Mongo **`db_bzcs`** |
-
----
-
-## 2. 活动时间
-
-### 2.1 开放周期
-
-- **可挑战星期**:周六、周日(`getWeekDay`:7=周六,1=周日)
-- **每日时段**:0:10 ~ 23:00(`BZCS_START_SEC=600`,`BZCS_END_SEC=82800`)
-- **单轮跨度**:从周六 0:10 对齐开局,至周日 23:00 结束(持续约 2 天)
-- **轮次间隔**:距上次开轮满 **21 天**(3 周)且处于开放日,可开启新轮
-
-跨服 `timedStageHandle`(每分钟/每小时)负责:
-
-1. 满足新轮条件(首次开轮,或满 21 天且在开放日)→ 开新轮;若上轮仍未发奖则**放弃发奖**(清 `pendingRewards`、标记已发奖、`ActEnd`)后直接开轮  
-2. 已过 `endTime` 且未发奖且未满 21 天新轮 → 结算发奖并广播活动结束  
-3. 记录中活动应进行中但 `isRunning` 为假 → 修正时间并重新 `ActOpen`
-
-### 2.2 参与条件
-
-- 本服开服天数 ≥ **45**(`BZCS_OPEN_SVR_DAY`)
-- 跨服已广播本轮 `startTime`(`CommonDB.KEY_BZCS_START_TIME`)
-- 当前星期、时刻在开放窗口内(`actStartTimeCheck` / `IsRunning`)
-
-### 2.3 活动状态(客户端)
-
-- `getActState`:`0` 未开启,`2` 进行中,并返回 `leftSec`
-- `isActRed`:可参与且仍有免费次数
-
----
-
-## 3. 积分与排行
-
-### 3.1 积分变化(整场结束后结算)
-
-| 角色 | 整场结果 | 积分变化 |
-|------|----------|----------|
-| 进攻方 | 胜 | +20 |
-| 进攻方 | 负 | -10 |
-| 防守方 | 胜(攻方负) | +10 |
-| 防守方 | 负(攻方胜) | -20 |
-
-- 新周期开始时:真人重置为 **3000**,`firstJoinTime` 清零;机器人按策划表 `robotList[].score` 恢复  
-- 同分排序:**积分高优先** → **先达到该积分者优先**(`scoreTime` 更小)→ `uuid` 字典序
-
-### 3.2 排行榜
-
-- 客户端展示前 **100** 名(`BZCS_RANK_MAX`)
-- 匹配与名次标识使用 **全服积分榜**(含机器人,规模大于展示榜)
-- 周期发奖排名上限 **9999**(`BZCS_REWARD_RANK_MAX`)
-
----
-
-## 4. 匹配规则
-
-1. 排除自己,在积分榜中按积分 **±100 × step** 步进扩大(`step` 1~50,最大约 ±5000)  
-2. 每步在分数窗口内随机选取对手,直至满 **3** 人  
-3. 仍不足时,按与己方 **积分差升序** 兜底补满(可超出 ±5000 窗口)  
-4. 结果按对手 **全服名次** 升序排序  
-
-**本地缓存**(普通服):
-
-- 积分未变且已有 `matchList` 时,`CG_BZCS_MATCH_LIST` 可直接返回缓存  
-- 积分变化、刷新对手、战斗结束后会清空缓存,需重新拉取  
-
-**说明**:打开匹配界面与刷新对手均不写 `match` 业务日志。
-
----
-
-## 5. 战斗流程
-
-### 5.1 阵容要求
-
-- 五个种族各对应一种 `COMBAT_TYPE`(39~43),上阵英雄 **camp** 必须与该族一致  
-- 挑战前校验五族均已至少上阵 1 名英雄  
-
-### 5.2 发起挑战
-
-```mermaid
-sequenceDiagram
-    participant C as 客户端
-    participant NS as 普通服 NS
-    participant CS as 跨服 CS
-
-    C->>NS: CG_COMBAT_BEGIN(TYPE39, param=rank|expectUuid)
-    NS->>NS: 校验次数/阵容/活动
-    NS->>CS: LW_BZCS_CAN_FIGHT
-    CS->>NS: WL_BZCS_CAN_FIGHT
-    NS->>NS: 扣次,创建 bzcs_Battle_Cache
-    NS->>NS: 开打第1族
-    loop 第2~5族
-        C->>NS: CG_COMBAT_BEGIN(TYPE40~43)
-        NS->>NS: 沿用 Cache,不扣次
-    end
-    NS->>CS: LW_BZCS_FIGHT_END
-    CS->>CS: 双方 UpdateScore
-    CS->>NS: WL_BZCS_FIGHT_END / WL_BZCS_DEF_NOTIFY
-```
-
-- **仅首场**(`COMBAT_TYPE39`)请求跨服校验并 **扣除 1 次** 挑战次数  
-- **第 2~5 场** 仅本地 `bzcs_Battle_Cache` 链式开战,不再扣次  
-- **机器人**:防守方在普通服本地 `combatBegin`,`param={defUuid, raceIdx}`  
-- **真人**:防守方走 `MiddleCommonLogic_CombatBegin_LW` 到对方逻辑服取阵容  
-
-### 5.3 胜负判定
-
-- 单族:战斗胜利计攻方 1 胜,否则守方 1 胜  
-- 整场:`atkW` 或 `defW` ≥ **3** 时结束;或打满 5 族后结束  
-- 整场结束后:`LW_BZCS_FIGHT_END` 改分;攻方 **首次完成挑战** 时 `LW_BZCS_REGISTER`(每轮一次)
-
----
-
-## 6. 机器人
-
-### 6.1 配置表
-
-策划表:`excel/ssecy/baiZhanChengShen.lua` → **`robotList`**
-
-| 字段 | 说明 |
-|------|------|
-| `score` | 该机器人在积分榜上的积分 |
-| `monsterOutIDs[1..5]` | 五族 `monsterOutID`,顺序:**妖、人、兽、仙、魔** |
-
-- 机器人数量 = `robotList` 条数(当前 **101** 条)  
-- 跨服 uuid:`bzcs_robot_N` 与 `robotList[N]` **一一对应**  
-- 展示区服:固定为 `810538`(`BZCS_ROBOT_DISPLAY_SERVER_ID`,即「1 服」展示用)
-
-### 6.2 队伍与战力
-
-- 每个 `monsterOutID` 对应 `monster.lua` 中一条 `monsterOut`,战斗时由 `CombatLogic.getMonsterObjList()` 展开为上阵怪物列表  
-- 跨服仅存 `monsterOutID` + `racePower`(由 `monsterOut` 算出);客户端展示经 `ExpandBzcsRaceShow` 展开英雄模型  
-- 生成时随机 `name` / `head` / `headFrame` / `body`
-
-### 6.3 战斗
-
-- `getCombatMonsterOutID`:按 `defUuid` + `raceIdx` 取 `robotList[N].monsterOutIDs[raceIdx]`
-
----
-
-## 7. 玩家展示数据(跨服 showInfo)
-
-注册(`REGISTER`)时全量写入:
-
-- `name`、`head`、`headFrame`、`body`、`heroArr[1..5]`(五族阵容展示)
-
-增量同步(`LW_BZCS_UPDATE_SHOW`,须已 `crossRegistered`):
-
-| updateType | 含义 |
-|------------|------|
-| 1 | 昵称 |
-| 3 | 头像 |
-| 4 | 头像框 |
-| 5 | 单族阵容(需 `race` 1~5) |
-| 6 | 形象 body |
-
-触发示例:改头像/框/形象/上阵 → `RoleHeadLogic` 或 `onCombatPosUpdate` 调用 `UpdateShowInfo`。
-
----
-
-## 8. 周期结算奖励
-
-### 8.1 策划表 `rankReward`
-
-| 名次区间 | 示例奖励(道具 id, 数量) |
-|----------|---------------------------|
-| 1 | 7056×1, 186×10000, 102×2000, 1026×10 |
-| 2 | 7057×1, 186×9000, … |
-| 3 | 7058×1, … |
-| 4~5 | 186×7000, … |
-| 6~10 | … |
-| 11~20 | … |
-| 21~50 | … |
-| 51~100 | … |
-| 101~9999 | 186×2000, 102×500, 118×10 |
-
-### 8.2 发奖条件
-
-- 仅 **`firstJoinTime > 0`** 的真人参与发奖(本轮至少完成过一次挑战注册)  
-- 机器人不参与发奖、不参与 `serverList` 路由  
-- 邮件 id:**7038**(`BZCS_AWARD_MAIL_ID`),正文带名次  
-- 跨服按逻辑服批量 `WL_BZCS_ISSUE_REWARD`;断连时写入 `pendingRewards`,重连补发  
-
----
-
-## 9. 客户端协议(节选)
-
-| 协议 | 说明 |
-|------|------|
-| `CG_BZCS_MATCH_LIST` / `GC_BZCS_MATCH_LIST` | 匹配主界面:己方积分、免费次数、门票消耗、3 名对手(含 `body`) |
-| `CG_BZCS_MATCH_REFRESH` | 强制刷新对手(回包同 MATCH_LIST) |
-| `CG_BZCS_RANK_LIST` / `GC_BZCS_RANK_LIST` | 排行榜前 100 |
-| `CG_BZCS_OPPONENT_INFO` | 按全服 `rank` 查对手头像/战力/积分 |
-| `CG_BZCS_OPPONENT_LINEUP` | 按 `rank` 查五族阵容展示 |
-| `CG_BZCS_MY_LINEUP` | 己方五族阵容(本地) |
-| `CG_BZCS_WAR_REPORT` | 本地战报最多 20 条 |
-| `CG_BZCS_RANK_REWARD` | 排名奖励预览 |
-| `CG_COMBAT_BEGIN` | 挑战入口,`combatType=39~43`,首场 `param=rank\|expectUuid` |
-
-**区服显示**:`serverId - 810537` = 第几服;机器人为固定展示服。
-
----
-
-## 10. 数据存储
-
-### 10.1 跨服 Mongo `db_bzcs`
-
-```
-BzcsData = {
-    activityStartTime, activityEndTime, lastResetTime, rewardIssued,
-    playerList[uuid] = {
-        uuid, serverId, score, scoreTime, isRobot, firstJoinTime,
-        showInfo = { name, head, headFrame, body, heroArr[race] }
-    },
-    serverList[serverId] = { uuid, ... },   -- 仅真人,发奖路由
-    pendingRewards[serverId] = { {uuid, rank}, ... }
-}
-```
-
-### 10.2 普通服 `human.db.baiZhanChengShen`
-
-| 字段 | 说明 |
-|------|------|
-| `freeTimes` | 当日剩余免费次数(0 点重置为 5) |
-| `actStartTime` | 本轮开始时间(与跨服同步) |
-| `crossRegistered` | 本轮是否已向跨服注册 |
-| `matchList` / `matchScore` | 匹配缓存 |
-| `lastScore` | 上次已知积分 |
-| `warReport` | 本地战报,最多 20 条 |
-
----
-
-## 11. 代码结构
-
-| 文件 | 职责 |
-|------|------|
-| `BaiZhanChengShenDefine.lua` | 常量、排行/机器人工具、`MergeShowInfo`、`ExpandBzcsRaceShow` |
-| `BaiZhanChengShenNS.lua` | 普通服逻辑、协议、战斗钩子、`UpdateShowInfo` |
-| `BaiZhanChengShenCS.lua` | 跨服活动周期、LW 处理、发奖调度 |
-| `BaiZhanChengShenDB.lua` | 跨服数据、匹配、积分、机器人池 |
-| `BaiZhanChengShenLog.lua` | 业务日志封装 |
-| `Proto.lua` | 客户端协议结构 |
-| `Handler.lua` | CG 入口 |
-| `excel/ssecy/baiZhanChengShen.lua` | `rankReward`、`robotList` |
-
----
-
-## 12. 业务日志
-
-- 日志文件:**`log/oss_bzcs`**(`Log.LOGID_OSS_BZCS`)  
-- 格式:`[action] key=value ...`  
-
-| action | 场景 |
-|--------|------|
-| `act_open` / `act_end` | 开轮 / 结束发奖 |
-| `round_reset` | 新周期重置 DB |
-| `robot_gen` / `db_init` | 生成机器人 / 跨服加载 |
-| `register` / `register_send` | 跨服注册 |
-| `update_show` / `update_show_send` | 展示增量 |
-| `score` | 积分变更 |
-| `fight_end` / `fight_end_send` / `fight_end_ns` | 整场结算 |
-| `can_fight` | 开战扣次 |
-| `reward_issue` / `reward_pending` / `reward_reissue` / `reward_mail_fail` | 发奖 |
-| `err_tips` | 跨服业务错误 |
-| `fight_end_middle_down` / `fight_end_wl_fail` | 跨服断连或 WL 失败 |
-
-**不写日志**:匹配列表拉取与刷新对手。
-
----
-
-## 13. 跨服错误码(WL_BZCS_TIPS)
-
-| errCode | 含义 |
-|---------|------|
-| 1 | 活动未开启 |
-| 2 | 不满足参与条件 |
-| 3 | 挑战次数不足 |
-| 4 | 对手无效 |
-| 5 | 数据异常 |
-| 6 | 战斗中 |
-
----
-
-*文档依据当前服务端实现整理;策划数值以 `excel/ssecy/baiZhanChengShen.lua` 为准。*