gitxsm 5 дней назад
Родитель
Сommit
c772229bb6

+ 2 - 1
script/common/InnerProto.lua

@@ -1440,6 +1440,7 @@ WL_ANOTHERWORLDBATTLE_UIONMORALE_DO = {
 LW_BZCS_MATCH = {
   {"sourceServerId",        "int"},
   {"playerUuid",            "string"},
+  {"refreshRanks",          "table"},  -- 非空时仅按名次刷新展示, 不重算匹配; 全量匹配传 {}
 }
 WL_BZCS_MATCH = {
   {"playerUuid",            "string"},
@@ -1468,7 +1469,7 @@ LW_BZCS_OPPONENT_INFO = {
 WL_BZCS_OPPONENT_INFO = {
   {"playerUuid",            "string"},
   {"res",                   "int"},    -- 0=成功 -1=目标不存在
-  {"targetInfo",            "table"},  -- 跨服玩家快照: name,head,headFrame,power,score 等
+  {"targetInfo",            "table"},  -- {name,head,headFrame,power,score}
 }
 
 -- 对手五族阵容展示

+ 2 - 0
script/core/ObjHuman.lua

@@ -107,6 +107,7 @@ local RoleStorageBox = require("roleSystem.RoleStorageBox")
 local BreakThroughTheme = require("battle.BreakThroughTheme")
 local BattleGift = require("battle.BattleGift")
 local BigRPlayerShow = require("broadcast.BigRPlayerShow")
+local BaiZhanChengShenNS = require("baiZhanChengShen.BaiZhanChengShenNS")
 
 
 local Json = require("common.Json")
@@ -921,6 +922,7 @@ function updateDaily(human, isGm)
     UnionDonateLogic.updateDaily(human)
     FirstChargeLogic.updateDaily(human)
     JjcLadderLogic.updateDaily(human)
+    BaiZhanChengShenNS.updateDaily(human)
     UnionRedBagLogic.updateDaily(human)
 	JjcGodWarLogic.updateDaily(human)
 	RacialTrialLogic.updateDaily(human)

+ 52 - 19
script/module/baiZhanChengShen/BaiZhanChengShenCS.lua

@@ -228,34 +228,58 @@ local function endRoundHandle()
     ActEnd()
 end
 
+-- 放弃上轮未完成的周期发奖(满21天开新轮时不再补发)
+local function abandonUnissuedRewards()
+    if BaiZhanChengShenDB.IsRewardIssued() then
+        return
+    end
+    BzcsLog.logAction("reward_abandon", "new_round_skip")
+    BaiZhanChengShenDB.SetRewardIssued(true)
+    BaiZhanChengShenDB.ClearAllPendingRewards()
+    ActEnd()
+end
+
+-- 是否应开启新轮(首次开轮 / 满21天开放日); 成功则已执行 newRoundHandle
+local function tryOpenNewRound(now, lastReset, startTime, endTime)
+    if not isInOpenWday() 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
+        abandonUnissuedRewards()
+    end
+    newRoundHandle(now)
+    return true
+end
+
 -- 周期阶段机 (oneMin/onHour 调用):
--- 1) 已过 endTime 且未发奖 -> endRoundHandle
--- 2) 从未开轮(lastReset==0) 且在开放日 -> newRoundHandle
--- 3) 距上次开轮>=21天 且(无活动/已结束/已发奖) -> newRoundHandle
--- 4) 活动记录中但 isRunning 为假(如跨天) -> 修正时间并 ActOpen
+-- 1) 满足新轮条件(含满21天且上轮未发奖则放弃发奖直接开轮) -> newRoundHandle
+-- 2) 已过 endTime 且未发奖且未满21天新轮 -> endRoundHandle
+-- 3) 活动记录中但 isRunning 为假(如跨天) -> 修正时间并 ActOpen
 local function timedStageHandle()
     local now = os.time()
     local lastReset = BaiZhanChengShenDB.GetLastResetTime()
     local startTime, endTime = BaiZhanChengShenDB.GetActivityTimes()
 
-    if startTime > 0 and now >= endTime and not BaiZhanChengShenDB.IsRewardIssued() then
-        endRoundHandle()
+    if tryOpenNewRound(now, lastReset, startTime, endTime) then
         return
     end
 
-    if lastReset == 0 then
-        if isInOpenWday() then
-            newRoundHandle(now)
-        end
+    if startTime > 0 and now >= endTime and not BaiZhanChengShenDB.IsRewardIssued() then
+        endRoundHandle()
         return
     end
 
-    local diffDays = Util.diffDay(lastReset)
-    if diffDays >= BaiZhanChengShenDefine.BZCS_CYCLE_DAYS and isInOpenWday() then
-        if startTime == 0 or now >= endTime or BaiZhanChengShenDB.IsRewardIssued() then
-            newRoundHandle(now)
-        end
-    elseif startTime > 0 and now >= startTime and now < endTime and not isRunning() then
+    if startTime > 0 and now >= startTime and now < endTime and not isRunning() then
         if isInOpenWday() then
             local ts = alignRoundStart(now)
             local te = calcRoundEnd(ts)
@@ -302,7 +326,7 @@ local function errTips(sourceServerId, playerUuid, errCode)
     sendWL(fd, msgData)
 end
 
--- LW_BZCS_MATCH -> WL_BZCS_MATCH (±100 步进匹配最多3人)
+-- LW_BZCS_MATCH -> WL_BZCS_MATCH (±100 步进匹配最多3人; refreshRanks 非空时仅刷新展示)
 function N2C_Match(msg)
     local fd = MiddleManager.getFDBySvrIndex(msg.sourceServerId)
     if not isRunning() then
@@ -310,7 +334,16 @@ function N2C_Match(msg)
     end
     local pinfo = BaiZhanChengShenDB.GetPlayer(msg.playerUuid)
     local myScore = pinfo and pinfo.score or BaiZhanChengShenDefine.BZCS_INIT_SCORE
-    local opponents = BaiZhanChengShenDB.GetMatchOpponents(msg.playerUuid, myScore, {})
+    local refreshRanks = msg.refreshRanks
+    local opponents
+    if refreshRanks and #refreshRanks > 0 then
+        opponents = BaiZhanChengShenDB.GetMatchOpponentsByRanks(refreshRanks)
+        if #opponents < #refreshRanks then
+            opponents = BaiZhanChengShenDB.GetMatchOpponents(msg.playerUuid, myScore, {})
+        end
+    else
+        opponents = BaiZhanChengShenDB.GetMatchOpponents(msg.playerUuid, myScore, {})
+    end
     local msgData = InnerMsg.wl.WL_BZCS_MATCH
     msgData.playerUuid = msg.playerUuid
     msgData.myScore = myScore
@@ -336,7 +369,7 @@ function N2C_OpponentInfo(msg)
     local msgData = InnerMsg.wl.WL_BZCS_OPPONENT_INFO
     msgData.playerUuid = msg.playerUuid
     msgData.res = target and 0 or -1
-    msgData.targetInfo = target or {}
+    msgData.targetInfo = BaiZhanChengShenDB.BuildOpponentInfoSnapshot(target) or {}
     sendWL(fd, msgData)
 end
 

+ 52 - 5
script/module/baiZhanChengShen/BaiZhanChengShenDB.lua

@@ -197,6 +197,16 @@ function AddPendingRewards(serverId, rewardList)
     BzcsLog.logAction("reward_pending", string.format("serverId=%s add=%s total=%s", serverId, #rewardList, #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 {}
@@ -365,6 +375,19 @@ function BuildPlayerRankInfo(uuid)
     }
 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)
     sortRankCache()
@@ -482,20 +505,44 @@ function GetMatchOpponents(myUuid, myScore, excludeTb)
     return selected
 end
 
--- 新周期: 全员积分重置、发奖标记清除, 保留机器人池
+-- 按全服名次列表取对手摘要(仅刷新展示, 不重算匹配)
+function GetMatchOpponentsByRanks(ranks)
+    if not ranks or #ranks == 0 then
+        return {}
+    end
+    sortRankCache()
+    local uuid2rank = {}
+    for r, entry in ipairs(rankCache or {}) do
+        uuid2rank[entry.uuid] = r
+    end
+    local ret = {}
+    for _, rank in ipairs(ranks) do
+        local pinfo = GetPlayerByRank(rank)
+        if pinfo then
+            ret[#ret + 1] = makeMatchOpponentEntry(pinfo.uuid, pinfo, uuid2rank)
+        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
-        pinfo.score = BaiZhanChengShenDefine.BZCS_INIT_SCORE
-        pinfo.firstJoinTime = 0
         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
-            pinfo.scoreTime = 0
+            BzcsData.playerList[uuid] = nil
+            removedCnt = removedCnt + 1
         end
     end
     rankDirty = true
@@ -509,7 +556,7 @@ function ResetForNewRound(newStart, newEnd)
     rebuildServerList()
     dbUpdate._id = BzcsData._id
     LuaMongo.update(DB.db_bzcs, dbUpdate, BzcsData)
-    BzcsLog.logAction("round_reset", string.format("start=%s end=%s robotCnt=%s", newStart, newEnd, robotIdx))
+    BzcsLog.logAction("round_reset", string.format("start=%s end=%s robotCnt=%s removedReal=%s", newStart, newEnd, robotIdx, removedCnt))
 end
 
 -- 周期发奖列表: 仅 firstJoinTime>0 且非机器人, 返回 {uuid, rank, serverId}

+ 1 - 0
script/module/baiZhanChengShen/BaiZhanChengShenDefine.lua

@@ -58,6 +58,7 @@ BZCS_TICKET_COST      = 5         -- 无免费时每次消耗数量
 -- 匹配与排行
 --------------------------------------------------------------------------------
 BZCS_OPPONENT_CNT     = 3         -- 单次匹配对手数量
+BZCS_MATCH_CACHE_TTL  = 30        -- 匹配列表内存缓存有效期(秒); 过期按 rank 轻量刷新展示
 BZCS_MATCH_STEP       = 100       -- 积分匹配步进(每步 ±100 分扩大)
 BZCS_MATCH_MAX_STEP   = 50        -- 最大扩大步数(最多 ±5000 分范围)
 

+ 2 - 7
script/module/baiZhanChengShen/BaiZhanChengShenLog.lua

@@ -1,20 +1,15 @@
 -- 百战成神专用日志(文件 log/oss_bzcs, 见 common.Log LOGID_OSS_BZCS)
 local Log = require("common.Log")
 
-local function write(logStr)
+function write(logStr)
     Log.write(Log.LOGID_OSS_BZCS, logStr or "")
 end
 
 -- action 为业务动作名, detail 为键值描述串
-local function logAction(action, detail)
+function logAction(action, detail)
     if detail and detail ~= "" then
         write(string.format("[%s] %s", action, detail))
     else
         write(string.format("[%s]", action))
     end
 end
-
-return {
-    write = write,
-    logAction = logAction,
-}

+ 129 - 80
script/module/baiZhanChengShen/BaiZhanChengShenNS.lua

@@ -5,7 +5,8 @@
 --
 -- 数据分工:
 --   跨服 DB: 积分、排行、匹配池、玩家展示(REGISTER/UPDATE_SHOW)
---   本地 human.db.baiZhanChengShen: 免费次数、战报、匹配缓存、crossRegistered
+--   本地 human.db.baiZhanChengShen: 免费次数、战报、crossRegistered
+--   human.bzcs_Match_Cache: 匹配列表/积分内存缓存(TTL 见 BZCS_MATCH_CACHE_TTL, 不持久化)
 --   CommonDB.KEY_BZCS_START_TIME: 本服活动轮次标记(与跨服 WL_BZCS_ACT_START 同步)
 --
 -- 跨服同步策略:
@@ -107,66 +108,110 @@ local function actStartTimeCheck()
     return IsRunning(startTime)
 end
 
--- 新活动轮次时清空本地战报与匹配缓存
+-- 新活动轮次时清空本地战报
 local function clearRoundLocalData(db)
     db.warReport = {}
-    db.matchList = {}
-    db.matchScore = nil
     db.crossRegistered = false
 end
 
--- 积分变化后清空匹配列表, 下次须重新 CG_BZCS_MATCH_LIST
-local function invalidateMatchCache(db)
-    db.matchList = {}
-    db.matchScore = nil
+-- 清除匹配内存缓存(积分变化/新轮/强制刷新)
+local function clearMatchCache(human)
+    if human then
+        human.bzcs_Match_Cache = nil
+    end
 end
 
 -- 活动开始时间变化(新轮开启/结束)时重置本地轮次数据
-local function resetRoundLocalIfNeeded(db)
+local function resetRoundLocalIfNeeded(human, db)
     local actStart = CommonDB.getValueByKey(CommonDB.KEY_BZCS_START_TIME) or 0
     if (db.actStartTime or 0) ~= actStart then
         db.actStartTime = actStart
         clearRoundLocalData(db)
+        clearMatchCache(human)
     end
 end
 
 -- 玩家本地百战成神数据 (不入跨服 Mongo)
---   freeTimes      今日剩余免费挑战次数
---   dailyReset     上次日重置时间戳(0点)
+--   freeTimes      今日剩余免费挑战次数(跨天由 updateDaily 重置)
 --   actStartTime   已同步的活动轮开始时间(与 KEY_BZCS_START_TIME 对比清轮次数据)
 --   crossRegistered 本轮是否已向跨服 REGISTER
---   lastScore      上次已知积分(匹配缓存/断连降级展示)
---   matchList      缓存的匹配对手(积分不变则复用)
---   matchScore     生成 matchList 时的积分
 --   warReport      本地战报 {warType,...}, 按时间尾插(最老在[1]), 下发时倒序为新在前
 local function getBzcsDb(human)
     human.db.baiZhanChengShen = human.db.baiZhanChengShen or {
         freeTimes = BaiZhanChengShenDefine.BZCS_FREE_TIMES,
-        dailyReset = 0,
         actStartTime = 0,
         crossRegistered = false,
-        lastScore = nil,
         warReport = {},
-        matchList = {},
-        matchScore = nil,
     }
-    resetRoundLocalIfNeeded(human.db.baiZhanChengShen)
-    local now = os.time()
-    local dayStart = Util.getDayStartTime(now)
-    if human.db.baiZhanChengShen.dailyReset ~= dayStart then
-        human.db.baiZhanChengShen.dailyReset = dayStart
-        human.db.baiZhanChengShen.freeTimes = BaiZhanChengShenDefine.BZCS_FREE_TIMES
-    end
+    resetRoundLocalIfNeeded(human, human.db.baiZhanChengShen)
     return human.db.baiZhanChengShen
 end
 
--- 校验 rank 是否属于当前 matchList(全服名次, 非列表下标 1~3)
+local function getMatchCache(human)
+    return human and human.bzcs_Match_Cache
+end
+
+local function setMatchCache(human, myScore, opponentList)
+    human.bzcs_Match_Cache = {
+        myScore = myScore,
+        matchScore = myScore,
+        opponentList = opponentList or {},
+        cacheTime = os.time(),
+    }
+end
+
+-- 展示刷新: 保留 matchScore(匹配基准), 更新对手展示并重置 TTL
+local function refreshMatchCacheDisplay(human, myScore, opponentList)
+    local cache = getMatchCache(human)
+    human.bzcs_Match_Cache = {
+        myScore = myScore,
+        matchScore = cache and cache.matchScore or myScore,
+        opponentList = opponentList or {},
+        cacheTime = os.time(),
+    }
+end
+
+-- 积分变动: 清空对手列表, 重置 matchScore/TTL(从变动时刻重新计时)
+local function resetMatchCacheOnScoreChange(human, newScore)
+    if not human then return end
+    newScore = newScore or BaiZhanChengShenDefine.BZCS_INIT_SCORE
+    human.bzcs_Match_Cache = {
+        myScore = newScore,
+        matchScore = newScore,
+        opponentList = {},
+        cacheTime = os.time(),
+    }
+end
+
+local function isMatchCacheFresh(human)
+    local cache = getMatchCache(human)
+    if not cache or not cache.opponentList or not next(cache.opponentList) then
+        return false
+    end
+    if (cache.myScore or 0) ~= (cache.matchScore or 0) then
+        return false
+    end
+    local ttl = BaiZhanChengShenDefine.BZCS_MATCH_CACHE_TTL or 30
+    return (os.time() - (cache.cacheTime or 0)) < ttl
+end
+
+local function collectRefreshRanks(opponentList)
+    local ranks = {}
+    for _, opp in ipairs(opponentList or {}) do
+        if opp.rank and opp.rank > 0 then
+            ranks[#ranks + 1] = opp.rank
+        end
+    end
+    return ranks
+end
+
+-- 校验 rank 是否属于当前匹配缓存(全服名次, 非列表下标 1~3)
 local function resolveMatchOpponentByRank(human, rank)
     if not rank or rank < 1 then
         return nil
     end
-    local db = getBzcsDb(human)
-    for _, opp in ipairs(db.matchList or {}) do
+    local cache = getMatchCache(human)
+    for _, opp in ipairs(cache and cache.opponentList or {}) do
         if opp.rank == rank and opp.uuid then
             return opp
         end
@@ -174,18 +219,6 @@ local function resolveMatchOpponentByRank(human, rank)
     return nil
 end
 
--- 积分未变且已有匹配列表时可直接下发, 无需请求跨服
-local function canUseCachedMatch(db)
-    if not db.matchList or not next(db.matchList) then
-        return false
-    end
-    if not db.matchScore then
-        return false
-    end
-    local curScore = db.lastScore or BaiZhanChengShenDefine.BZCS_INIT_SCORE
-    return db.matchScore == curScore
-end
-
 -- 组装并下发 GC_BZCS_MATCH_LIST
 local function sendMatchListGC(human, myScore, opponentList)
     local db = getBzcsDb(human)
@@ -563,35 +596,44 @@ end
 ------------------------------------ 客户端请求 (Handler.lua -> BZCS_*) ------------------------------------
 -- 由 Handler.lua 转发; 需跨服的走 sendLwToMiddle, 仅本地数据的直接 GC
 
--- 请求跨服匹配列表; forceRefresh=true 时忽略本地缓存
+-- 请求跨服匹配列表; forceRefresh=true 时全量重匹配
 local function requestMatchList(human, forceRefresh)
-    local db = getBzcsDb(human)
-    local fallbackScore = db.lastScore or BaiZhanChengShenDefine.BZCS_INIT_SCORE
-    local fallbackList = db.matchList or {}
+    if human.fd then
+        ObjHuman.updateDaily(human)
+    end
+    getBzcsDb(human)
     if not isServerEligible() then
-        return sendMatchListGC(human, BaiZhanChengShenDefine.BZCS_INIT_SCORE, {})
+        return Broadcast.sendErr(human, Lang.COMMOM_NOT_ENABLED)
     end
     if not actStartTimeCheck() then
         return Broadcast.sendErr(human, Lang.COMMOM_NOT_ENABLED)
     end
     if forceRefresh then
-        invalidateMatchCache(db)
-    elseif canUseCachedMatch(db) then
-        return sendMatchListGC(human, db.lastScore, db.matchList)
+        clearMatchCache(human)
+    end
+    if isMatchCacheFresh(human) then
+        local cache = getMatchCache(human)
+        return sendMatchListGC(human, cache.myScore, cache.opponentList)
     end
     if not isMiddleReady() then
-        return sendMatchListGC(human, fallbackScore, fallbackList)
+        return Broadcast.sendErr(human, Lang.MIDDLE_SVR_ERR_CONNECT)
     end
+    local cache = getMatchCache(human)
     local msgData = InnerMsg.lw.LW_BZCS_MATCH
     msgData.sourceServerId = Config.SVR_INDEX
     msgData.playerUuid = human.db._id
+    msgData.refreshRanks = {}
+    if cache and cache.opponentList and next(cache.opponentList)
+        and (cache.myScore or 0) == (cache.matchScore or 0) then
+        msgData.refreshRanks = collectRefreshRanks(cache.opponentList)
+    end
     if not sendLwToMiddle(human, msgData) then
-        sendMatchListGC(human, fallbackScore, fallbackList)
+        return
     end
 end
 
 -- 匹配主界面: 下发 myScore + 3 名对手(score/name/power)
--- 积分未变且 matchList 非空则本地缓存直返; 跨服断连时降级返回缓存/初始分
+-- 内存缓存未过期直返; 过期则跨服按 rank 刷新展示; 无缓存则全量匹配
 function BZCS_MatchList(human)
     requestMatchList(human, false)
 end
@@ -702,6 +744,7 @@ function C2N_Act_Start(msg)
                 if (human.db.baiZhanChengShen.actStartTime or 0) ~= actStart then
                     human.db.baiZhanChengShen.actStartTime = actStart
                     clearRoundLocalData(human.db.baiZhanChengShen)
+                    clearMatchCache(human)
                 end
             end
         end
@@ -715,6 +758,7 @@ function C2N_Act_End(msg)
         if human.db and human.db.baiZhanChengShen then
             clearRoundLocalData(human.db.baiZhanChengShen)
             human.db.baiZhanChengShen.actStartTime = 0
+            clearMatchCache(human)
         end
     end
 end
@@ -734,16 +778,21 @@ function C2N_ErrTips(msg)
     Broadcast.sendErr(human, Lang.DATA_ERR)
 end
 
--- WL_BZCS_MATCH -> GC_BZCS_MATCH_LIST, 写入 matchList/matchScore 缓存
+-- WL_BZCS_MATCH -> GC_BZCS_MATCH_LIST, 写入内存匹配缓存
 function C2N_Match(msg)
     local human = ObjHuman.onlineUuid[msg.playerUuid]
     if not human then return end
-    local db = getBzcsDb(human)
     local myScore = msg.myScore or BaiZhanChengShenDefine.BZCS_INIT_SCORE
-    db.matchList = msg.opponentList or {}
-    db.matchScore = myScore
-    db.lastScore = myScore
-    sendMatchListGC(human, myScore, db.matchList)
+    local opponentList = msg.opponentList or {}
+    local cache = getMatchCache(human)
+    local isDisplayRefresh = cache and cache.matchScore and cache.matchScore == myScore
+        and cache.opponentList and next(cache.opponentList)
+    if isDisplayRefresh then
+        refreshMatchCacheDisplay(human, myScore, opponentList)
+    else
+        setMatchCache(human, myScore, opponentList)
+    end
+    sendMatchListGC(human, myScore, opponentList)
 end
 
 local function fillBzcsRankNet(net, info)
@@ -779,12 +828,11 @@ function C2N_OpponentInfo(msg)
         return Broadcast.sendErr(human, Lang.DATA_ERR)
     end
     local t = msg.targetInfo or {}
-    local si = t.showInfo or {}
     local msgRet = Msg.gc.GC_BZCS_OPPONENT_INFO
-    msgRet.name = si.name or ""
-    msgRet.head = si.head or 0
-    msgRet.headFrame = si.headFrame or 0
-    msgRet.power = BaiZhanChengShenDefine.CalcPlayerPower(si)
+    msgRet.name = t.name or ""
+    msgRet.head = t.head or 0
+    msgRet.headFrame = t.headFrame or 0
+    msgRet.power = t.power or 0
     msgRet.score = t.score or BaiZhanChengShenDefine.BZCS_INIT_SCORE
     Msg.send(msgRet, human.fd)
 end
@@ -829,7 +877,7 @@ local function getBzcsRaceIdxByCombatType(combatType)
     end
 end
 
--- CG_COMBAT_BEGIN: 挑战一次依次调用 TYPE39~43; param=全服名次rank(须在本地 matchList 中)
+-- CG_COMBAT_BEGIN: 挑战一次依次调用 TYPE39~43; param=全服名次rank(须在内存匹配缓存中)
 function fight(human, args, combatType)
     local raceIdx = getBzcsRaceIdxByCombatType(combatType)
     if not raceIdx then
@@ -838,6 +886,7 @@ function fight(human, args, combatType)
 
     if raceIdx == 1 then
         -- 首场: 校验次数/道具、五族阵容, 请求跨服 CAN_FIGHT(扣次在 C2N_CanFight)
+        ObjHuman.updateDaily(human)
         local rank = tonumber(args[1])
         if not rank then
             return
@@ -988,32 +1037,25 @@ function C2N_CanFight(msg)
     startRaceCombat(human, human.bzcs_Battle_Cache)
 end
 
--- WL_BZCS_FIGHT_END: 同步攻方积分并失效匹配缓存
+-- WL_BZCS_FIGHT_END: 攻方积分变化, 重置匹配缓存与 TTL
 function C2N_FightEnd(msg)
     local human = ObjHuman.onlineUuid[msg.playerUuid]
     if not human then return end
     local db = getBzcsDb(human)
     db.crossRegistered = true
-    db.lastScore = msg.myScore
-    invalidateMatchCache(db)
+    resetMatchCacheOnScoreChange(human, msg.myScore)
     BzcsLog.logAction("fight_end_ns", string.format("uuid=%s atkWin=%s scoreChange=%s myScore=%s", msg.playerUuid or "", msg.atkWin or 0, msg.scoreChange or 0, msg.myScore or 0))
 end
 
 -- WL_BZCS_DEF_NOTIFY: 防守方战报(在线/离线)
 function C2N_DefNotify(msg)
     local human = ObjHuman.onlineUuid[msg.playerUuid]
+    local offlineSave = false
     if not human then
-        local db = RoleDBLogic.getDb(msg.playerUuid)
+        local db = RoleDBLogic.getDb(msg.playerUuid, {baiZhanChengShen = 1})
         if not db then return end
-        db.baiZhanChengShen = db.baiZhanChengShen or {
-            actStartTime = 0,
-            crossRegistered = false,
-            warReport = {},
-            matchList = {},
-            matchScore = nil,
-        }
-        resetRoundLocalIfNeeded(db.baiZhanChengShen)
         human = {db = db}
+        offlineSave = true
     end
     local defWin = msg.atkWin == 1
     addWarReport(human, {
@@ -1022,17 +1064,24 @@ function C2N_DefNotify(msg)
         oppName = msg.atkName or "",
         scoreChange = msg.scoreChange or 0,
     })
-    if human.db.baiZhanChengShen then
-        human.db.baiZhanChengShen.lastScore = msg.myScore
-    end
-    if not human.fd then
-        ObjHuman.save(human)
+    if human.fd then
+        resetMatchCacheOnScoreChange(human, msg.myScore)
+    elseif offlineSave then
+        RoleDBLogic.saveRoleSset(human.db)
     end
 end
 
 ------------------------------------ 对外接口 ------------------------------------
 -- 供红点/活动状态/阵容展示同步等其它模块调用
 
+-- 跨天重置免费挑战次数(由 ObjHuman.updateDaily 调用)
+function updateDaily(human)
+    if not human.db.baiZhanChengShen then
+        return
+    end
+    human.db.baiZhanChengShen.freeTimes = BaiZhanChengShenDefine.BZCS_FREE_TIMES
+end
+
 local ACT_STATE_NOOPEN = 0  -- 活动未开启
 local ACT_STATE_START = 2   -- 活动进行中(与 JjcActLogic.STATE_START 一致)