From cc207773cbedb51c20300a87c62529ace416b086 Mon Sep 17 00:00:00 2001
From: hxp <ale99527@vip.qq.com>
Date: 星期五, 19 九月 2025 19:23:35 +0800
Subject: [PATCH] 129 【战斗】战斗系统-服务端(无敌支持,免疫伤害、dot、控制;小怪技能;)

---
 ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Attack/TurnAttack.py | 1541 ++++++++++++++++++++++++++++++----------------------------
 1 files changed, 798 insertions(+), 743 deletions(-)

diff --git a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Attack/TurnAttack.py b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Attack/TurnAttack.py
index 1bc7528..111c080 100644
--- a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Attack/TurnAttack.py
+++ b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Attack/TurnAttack.py
@@ -20,31 +20,32 @@
 #-------------------------------------------------------------------------------
 
 import ChConfig
+import PlayerTask
+import PlayerViewCache
 import ChPyNetSendPack
 import NetPackCommon
 import PlayerControl
 import GameWorld
-import GameObj
-import FBCommon
 import IpyGameDataPY
 import PlayerOnline
 import NPCCommon
 import ShareDefine
 import PyGameData
-import ItemControler
 import SkillCommon
-import AttackCommon
 import BattleObj
+import TurnPassive
 import TurnSkill
 import TurnBuff
+import CommFunc
 import ObjPool
+import FBLogic
 
 import random
 import time
 import json
 
-FighterNPCID = 100 # 战斗NPCID,仅后端用,除怪物外,所有玩家武将均使用该NPCID
-PosNumMax = 10 # 最大站位编号
+TimelineSet = 10000 # 单回合最大时间轴
+PosNumMax = 7 # 最大站位编号
 ActionNumStart = -1 # 起始行动位置编号,一般是从1开始,如果有加主公、红颜等则扣除相应位置值,如从0或-1开始
 
 # 回合战斗流程状态
@@ -55,7 +56,8 @@
 FightState_FightEnd, # 3 战斗结束
 FightState_Award, # 4 结算奖励
 FightState_Over, # 5 结束状态,无特殊意义,仅代表所有处理结束了,与Start对应
-) = range(6)
+FightState_Rest, # 6 结束并休息,一般是前端主动退出并休息的
+) = range(7)
     
 class BatLineup():
     ## 战斗阵容
@@ -68,6 +70,8 @@
         self.shapeType = 0 # 阵型
         self.fightPower = 0 # 阵容总战力
         self.posObjIDDict = {} # 站位对应战斗实体 {站位编号:batObjID, ...}, 站位编号小于0为非主战单位,如主公、红颜等
+        self.lingshouObjIDDict = {} # 灵兽战斗单位 {位置编号:batObjID, ...}
+        self.beautyObjIDDict = {} # 红颜战斗单位 {位置编号:batObjID, ...}
         self.actionNum = ActionNumStart # 行动位置,从1开始
         return
     
@@ -85,24 +89,18 @@
         SummonLineupObjs(self, self.faction, self.num, lineupInfo, self.getPlayerID())
         return
     
-    def resetLineup(self, isReborn=True):
-        ## 重置阵容
-        batObjMgr = BattleObj.GetBatObjMgr()
-        for objID in self.posObjIDDict.values():
-            batObj = batObjMgr.getBatObj(objID)
-            if not batObj:
-                continue
-            batObj.ResetBatObj(isReborn)
-        return
-    
     def clearLineup(self):
         ## 清除阵容
-        if not self.posObjIDDict:
-            return
         batObjMgr = BattleObj.GetBatObjMgr()
         for objID in self.posObjIDDict.values():
             batObjMgr.delBatObj(objID)
+        for objID in self.lingshouObjIDDict.values():
+            batObjMgr.delBatObj(objID)
+        for objID in self.beautyObjIDDict.values():
+            batObjMgr.delBatObj(objID)
         self.posObjIDDict = {}
+        self.lingshouObjIDDict = {}
+        self.beautyObjIDDict = {}
         self.fightPower = 0
         return
     
@@ -124,12 +122,6 @@
             lineup = BatLineup(self.faction, num, self.turnFight)
             self.lineupDict[num] = lineup
         return lineup
-    
-    def resetLineups(self):
-        ## 重置所有战斗阵容
-        for lineup in self.lineupDict.values():
-            lineup.resetLineup()
-        return
     
     def clearLineups(self):
         ## 清除所有战斗阵容
@@ -154,62 +146,95 @@
         self.curPlayer = GameWorld.GetPlayerManager().FindPlayerByID(playerID) if playerID else None
         self.mapID = mapID
         self.funcLineID = funcLineID
+        self.state = -1 # -1 代表未战斗
         self.turnNum = 1 # 当前第x回合,默认第1回合开始
         self.turnMax = 15 # 最大回合数
         self.enterLogic = False # 是否已执行进场逻辑
-        self.turnStart = 0 # 已执行回合开始值,如第1回合开始已执行则为1,第2回合为2
-        self.turnEnd = 0 # 已执行回合结束值,如第1回合结束已执行则为1,第2回合为2
         self.winFaction = 0 # 本场战斗结束标记,获胜阵营,为0时代表未结束,所有小队打完或失败才有结果,0-未结束,>0-获胜的阵营
+        self.isWin = False
         self.batBuffer = "" # 战报buffer,战报暂时只保留最后一个小队的
         self.isNeedReport = isNeedReport # 是否需要战报
         self.msgDict = {} # 扩展信息字典,一般由MapID绑定的功能决定信息内容  {k:v, ...}
         
         self.factionDict = {} # 战斗阵营 {faction:BatFaction, ...},一般是只有两个阵营,faction为1或2,每个阵营支持多个阵容
         self.actionSortList = [] # 阵容行动顺序 [[faction, num], ...]
-        self.actionIndex = 0 # 行动顺序索引
-        
+        self.actionIndex = 0 # 行动顺序索引        
+        self.timeline = 0 # 时间轴节点  turnNum*1000+actionIndex*100++actionNum
         self.startTime = 0 # 开始时间戳,支持毫秒小数
         self.costTime = 0 # 单场战斗总耗时,支持毫秒小数
+        self._oneActionUseSkillCntDict = {} # 某对象行动开始后所有对象累计使用技能次数,用于单对象单次行动中限制每个对象的最高触发技能次数 {objID:useCnt, ...}
+        
+        # pve 多小队 - 一般只有PVE用到
+        self.lineupIndex = 0 # 当前小队索引
+        self.lineupIDList = [] # npc小队列表
+        self.strongerLV = 0 # npc成长等级
+        self.difficulty = 0 # npc难度
+        
+        # pvp 目标
+        self.tagPlayerID = 0
+        self.tagViewCache = None
         return
     
-    def setTurn(self, mapID, funcLineID, turnMax, isNeedReport=False, msgDict={}):
+    def setTurnFight(self, mapID, funcLineID, turnMax, isNeedReport=False, msgDict={}):
         ## 设置本场回合战斗设定
         self.mapID = mapID
         self.funcLineID = funcLineID
         self.turnMax = turnMax # 最大回合数
         self.isNeedReport = isNeedReport
+        self.setPVE()
+        self.setPVP()
         self.msgDict = {}
-        self.resetTurn(msgDict)
+        self.nextTurnFight(msgDict)
         return
     
-    def resetTurn(self, msgDict):
+    def setPVE(self, lineupIDList=[], strongerLV=0, difficulty=0):
+        self.lineupIndex = 0
+        self.lineupIDList = lineupIDList
+        self.strongerLV = strongerLV
+        self.difficulty = difficulty
+        return
+    
+    def setPVP(self, tagPlayerID=0, tagViewCache=None):
+        self.tagPlayerID = tagPlayerID
+        self.tagViewCache = tagViewCache
+        return
+    
+    #def setPVPTeam(self):
+    #    return
+    
+    def nextTurnFight(self, msgDict={}, resetByNextTeam=False):
         ## 一般用于玩家发起的战斗,在需要保留玩家阵容属性及状态的情况下,重置回合进入下一场战斗
         self.turnNum = 1
         self.enterLogic = False
-        self.turnStart = 0
-        self.turnEnd = 0
         self.winFaction = 0
-        self.batBuffer = "" # 战报buffer
+        self.isWin = False
         self.msgDict.update(msgDict)
+        self.timeline = 0
         self.startTime = time.time()
         self.costTime = 0
+        if resetByNextTeam:
+            ResetByNextTeam(self)
         return
     
-    def setFactionLineup(self, faction, lineupDict, onlyReset=False):
+    def haveNextLineup(self):
+        ## 是否可以打下一小队,获胜且还有下一小队时可打
+        return (self.winFaction == ChConfig.Def_FactionA and self.lineupIndex < len(self.lineupIDList) - 1)
+    def nextLineupID(self):
+        ## 下一小队ID
+        if not self.haveNextLineup():
+            return 0
+        self.lineupIndex += 1
+        return self.lineupIDList[self.lineupIndex]
+    
+    def setFactionLineup(self, faction, lineupDict):
         ## 设置阵营阵容
         # @param lineupDict: {阵容编号:阵容信息, ...} 每个阵营支持多个阵容,即支持多V多
-        # @param onlyReset: 是否仅重置,一般用于玩家的阵容,重复利用阵容实例,
         batFaction = self.getBatFaction(faction)
-        if onlyReset:
-            batFaction.resetLineups()
-        else:
-            batFaction.clearLineups()
+        batFaction.clearLineups()
         for num, lineupInfo in lineupDict.items():
-            batLineup = batFaction.getBatlineup(num)
-            if onlyReset and not batLineup.isEmpty():
-                continue
             if not lineupInfo:
                 continue
+            batLineup = batFaction.getBatlineup(num)
             batLineup.setLineup(lineupInfo)
         return
     
@@ -219,19 +244,35 @@
         for batFaction in self.factionDict.values():
             faction = batFaction.faction
             for num, batLineup in batFaction.lineupDict.items():
+                isPlayer = 1 if batLineup.ownerID else 0 # 玩家阵容优先攻击
                 fightPower = batLineup.fightPower
                 sortValue = -(faction * 10 + num)
-                sortList.append([fightPower, sortValue, faction, num])
+                sortList.append([isPlayer, fightPower, sortValue, faction, num])
         sortList.sort(reverse=True) # 战力高的先手
         
         self.actionIndex = 0
         self.actionSortList = []
-        for _, _, faction, num in sortList:
+        for _, _, _, faction, num in sortList:
             self.actionSortList.append([faction, num])
             
-        GameWorld.DebugLog("阵容战力排序[fp, sortV, f, n]: %s" % sortList)
+        GameWorld.DebugLog("阵容战力排序[isPlayer, fp, sortV, f, n]: %s" % sortList)
         GameWorld.DebugLog("阵容行动顺序[f, n]: %s" % self.actionSortList)
         return
+    
+    def getTurnNumStartTimelin(self, turnNum): return turnNum * TimelineSet + 0 # 每回合的时间节点起点
+    def getTimeline(self): return self.timeline
+    def setTimeline(self, timeline, isEmpty=False):
+        '''回合战斗的时间轴节点 ,即第几回合开始,每个回合支持9999个行动节点
+        @param turnNum: 第x回合
+        '''
+        self.timeline = timeline
+        GameWorld.DebugLog("[时间节点更新]: %s" % self.timeline)
+        if isEmpty:
+            # 空位置的节点可直接跳过
+            return timeline
+        
+        OnTimelineChange(self)
+        return timeline
     
     def getBatFaction(self, faction=ChConfig.Def_FactionA):
         ## 默认阵营1
@@ -263,27 +304,34 @@
                     batObj = batObjMgr.getBatObj(objID)
                     if not batObj:
                         continue
-                    if batObj.GetHP() > 0:
+                    if batObj.IsAlive():
                         allKilled = False
                         break
                     
             if allKilled:
-                self.winFaction = ChConfig.Def_FactionA if faction == ChConfig.Def_FactionB else ChConfig.Def_FactionA
+                self.winFaction = ChConfig.Def_FactionA if faction == ChConfig.Def_FactionB else ChConfig.Def_FactionB
                 DoTurnFightOver(self.guid)
                 return self.winFaction
             
         return 0
     
-    def clearBatFaction(self, faction):
-        batFaction = self.getBatFaction(faction)
-        batFaction.clearLineups()
-        return
-    
-    def clearFight(self):
-        self.syncState(FightState_Over)
+    def exitFight(self, rest=False):
+        ## 退出战斗
+        self.syncState(FightState_Over if not rest else FightState_Rest)
         for batFaction in self.factionDict.values():
             batFaction.clearLineups()
+        self.state = -1
         return
+    
+    def startFight(self):
+        ## 准备就绪,开始战斗
+        self.state = FightState_Start
+        self.turnNum = 1
+        self.timeline = self.getTurnNumStartTimelin(self.turnNum)
+        self.syncInit()
+        return
+    
+    def isInFight(self): return self.state != -1
     
     def syncInit(self):
         ## 初始化通知
@@ -336,6 +384,7 @@
         return
     
     def syncState(self, state, msgDict={}):
+        self.state = state
         msg = json.dumps(msgDict, ensure_ascii=False)
         msg = msg.replace(" ", "")
         clientPack = ChPyNetSendPack.tagMCTurnFightState()
@@ -364,15 +413,27 @@
             headStr = "%02x%02x" % (clientPack.Head.Cmd, clientPack.Head.SubCmd)
         else:
             headStr = "%02x%02x" % (clientPack.Cmd, clientPack.SubCmd)
-        GameWorld.DebugLog("回合战斗过程封包: %s, isNeedReport=%s,%s" % (headStr, self.isNeedReport, self.guid))
         if self.isNeedReport:
-            self.batBuffer += clientPack.GetBuffer()
-        # 有玩家的统一每个包单独发送,同样也支持战报统计
-        if self.curPlayer:
-            NetPackCommon.SendFakePack(self.curPlayer, clientPack)
-        else:
+            packBuff = clientPack.GetBuffer()
+            buffLen = len(packBuff)
+            GameWorld.DebugLog("回合战斗过程封包: %s, len:%s" % (headStr, buffLen))
+            self.batBuffer += CommFunc.WriteWORD("", buffLen)
+            self.batBuffer += packBuff
             ObjPool.GetPoolMgr().release(clientPack)
+        else:
+            GameWorld.DebugLog("回合战斗过程封包: %s" % (headStr))
+            # 有玩家的统一每个包单独发送,同样也支持战报统计
+            if self.curPlayer:
+                NetPackCommon.SendFakePack(self.curPlayer, clientPack)
+            else:
+                ObjPool.GetPoolMgr().release(clientPack)
         return
+    
+    def ResetOneActionUseSkillCnt(self): self._oneActionUseSkillCntDict = {}
+    def GetOneActionUseSkillCnt(self, objID): return self._oneActionUseSkillCntDict.get(objID, 0)
+    def SetOneActionUseSkillCnt(self, objID, useCnt):
+        self._oneActionUseSkillCntDict[objID] = useCnt
+        return useCnt
     
 class TurnFightMgr():
     ## 回合战斗管理器
@@ -381,8 +442,10 @@
         self.turnFightDict = {} # {guid:TurnFight, ...}
         return
     
-    def addTurnFight(self, mapID, funcLineID=0, playerID=0):
-        tf = TurnFight(mapID, funcLineID, playerID, False)
+    def addTurnFight(self, mapID, funcLineID=0, playerID=0, isNeedReport=False):
+        tf = ObjPool.GetPoolMgr().acquire(TurnFight, mapID, funcLineID, playerID, isNeedReport)
+        if not tf:
+            tf = TurnFight(mapID, funcLineID, playerID, isNeedReport) # 一般是不可能,为了点出代码
         self.turnFightDict[tf.guid] = tf
         return tf
     
@@ -390,8 +453,9 @@
         turnFight = self.getTurnFight(guid)
         if not turnFight:
             return
-        turnFight.clearFight()
+        turnFight.exitFight()
         self.turnFightDict.pop(guid, None)
+        ObjPool.GetPoolMgr().release(turnFight)
         return
     
     def getTurnFight(self, guid):
@@ -420,10 +484,6 @@
         self.levelNum = 0 # 关卡编号
         self.waveMax = 6 # 本关最大波数,每波有多个小队,每个小队即为一张战斗 TurnFight
         self.wave = 0 # 当前刷怪波,注意不是玩家当前进度波,比如被击杀会回退一波
-        self.teamNum = 1 # 当前小队
-        self.teamMax = 1 # 当前波最大小队,某场战斗可能包含多个小队,所有小队混流击杀完才算过了本波
-        self.nextTeam = False # 下次前端请求战斗是否是下一小队,否则都是重新刷新当前进度怪
-        self.waveLineupList = [] # 小队列表
         self.turnFight = GetTurnFightMgr().addTurnFight(ChConfig.Def_FBMapID_Main, 0, playerID)
         return
     
@@ -446,10 +506,30 @@
                           % (chapterID, levelNum, nowChapterID, fixNowValue), curPlayer.GetPlayerID())
     return
 
-def GetPlayerLineupInfoByCache(playerID, lineupID):
-    ## 获取玩家阵容信息 - 根据玩家查看缓存
-    lineupInfo = {}
+
+def GetCacheLineupFightPower(tagViewCache, lineupID):
+    lineupInfo = GetCacheLineupInfo(tagViewCache, lineupID)
+    return lineupInfo.get("FightPower", 0)
+def GetCacheLineupInfo(tagViewCache, lineupID):
+    ## 根据查看缓存获取阵容信息,一般是 GetPlayerLineupInfo 返回的结果
+    plusDict = tagViewCache.GetPlusDict()
+    lineupDict = plusDict.get("Lineup", {})
+    lineupInfo = lineupDict.get("%s" % lineupID, {})
+    if not lineupInfo:
+        lineupInfo = lineupDict.get("%s" % ShareDefine.Lineup_Main, {})
     return lineupInfo
+
+def GetPlayerLineupFightPower(curPlayer, lineupID):
+    ## 获取玩家阵容战力,一般用于直接获取阵容战力记录用
+    return GetPlayerLineup(curPlayer, lineupID).fightPower
+def GetPlayerLineup(curPlayer, lineupID):
+    ## 获取玩家阵容
+    olPlayer = PlayerOnline.GetOnlinePlayer(curPlayer)
+    lineup = olPlayer.GetLineup(lineupID)
+    if not lineup.lineupHeroDict:
+        # 为空时默认取主阵容
+        lineup = olPlayer.GetLineup(ShareDefine.Lineup_Main)
+    return lineup
 
 def GetPlayerLineupInfo(curPlayer, lineupID):
     ## 获取玩家阵容信息,可用于战斗或查看缓存,因为可能取玩家的缓存进行对战,所以统一使用json格式,前端通用
@@ -457,9 +537,10 @@
     # @return: 阵容全部信息json字典,前端通用格式
     
     playerID = curPlayer.GetPlayerID()
-    lineup = PlayerOnline.GetOnlinePlayer(curPlayer).GetLineup(lineupID)
+    lineup = GetPlayerLineup(curPlayer, lineupID)
+    if not lineup.lineupHeroDict:
+        return {}
     
-    lineupInfo = {"PlayerID":playerID, "FightPower":lineup.fightPower, "ShapeType":lineup.shapeType}
     heroDict = {}
     curPack = curPlayer.GetItemManager().GetPack(ShareDefine.rptHero)
     for posNum in lineup.lineupHeroDict.keys():
@@ -485,13 +566,18 @@
                                  "AttrDict":{str(k):v for k, v in hero.heroBatAttrDict.items() if v > 0},
                                  "SkillIDList":skillIDlist,
                                  }
-    lineupInfo.update({"Hero":heroDict})
+        
+    if not heroDict:
+        return {}
     
+    lineupInfo = {"PlayerID":playerID, "FightPower":lineup.fightPower, "ShapeType":lineup.shapeType, "Hero":heroDict}
     return lineupInfo
 
-def GetNPCLineupInfo(lineupID):
+def GetNPCLineupInfo(lineupID, strongerLV=0, difficulty=0):
     ## 获取NPC阵容信息
     # @param lineupID: 阵容ID
+    # @param npcLV: 成长NPC等级
+    # @param difficulty: 成长NPC难度系数
     # @return: 阵容全部信息json字典,前端通用格式
     ipyData = IpyGameDataPY.GetIpyGameData("NPCLineup", lineupID)
     if not ipyData:
@@ -504,9 +590,59 @@
         npcID = getattr(ipyData, "GetPosNPCID%s" % posNum)()
         if not npcID:
             continue
-        npcData = NPCCommon.GetNPCDataPy(npcID)
-        if not npcData:
+        battleDict = GetNPCBattleDict(ipyData, npcID, strongerLV, difficulty)
+        if not battleDict:
             continue
+        heroDict[str(posNum)] = battleDict
+        
+    lineupInfo = {"NPCLineupID":lineupID, "Hero":heroDict}
+    return lineupInfo
+
+def GetNPCBattleDict(lineupIpyData, npcID, strongerLV=0, difficulty=0):
+    ## 获取NPC战斗相关字典,支持成长NPC
+    # @param strongerLV: 成长等级
+    # @param difficulty: 难度系数
+    npcData = NPCCommon.GetNPCDataPy(npcID)
+    if not npcData:
+        return
+    heroID = npcData.GetRelatedHeroID()
+    npcLV = npcData.GetLV()
+    
+    lvIpyData = None
+    heroIpyData = IpyGameDataPY.GetIpyGameData("Hero", heroID) if heroID else None
+    npcStronger = IpyGameDataPY.GetIpyGameDataNotLog("NPCStronger", npcID)
+    if npcStronger and strongerLV:
+        lvIpyData = IpyGameDataPY.GetIpyGameData("PlayerLV", strongerLV)
+        if lvIpyData:
+            npcLV = strongerLV
+    if not lvIpyData:
+        lvIpyData = IpyGameDataPY.GetIpyGameData("PlayerLV", npcLV)
+        
+    if heroIpyData and lvIpyData:
+        skinIDList = heroIpyData.GetSkinIDList()
+        skinID = skinIDList[0] if skinIDList else 0
+        skillIDList = GetNPCHeroSkillIDList(heroID, heroIpyData, lvIpyData.GetReHeroBreakLV(), lvIpyData.GetReHeroAwakeLV())
+    else:
+        heroID = 0
+        skinID = 0
+        skillIDList = []# + npcData.GetSkillIDList()
+        
+    # boss额外随机技能
+    bossID = lineupIpyData.GetBossID()
+    if npcID == bossID:
+        skillIDExList = lineupIpyData.GetSkillIDExList()
+        if skillIDExList:
+            randSkillIDExList = [] + list(skillIDExList)
+            skillExCnt = lineupIpyData.GetSkillExCnt()
+            if skillExCnt > 0 and len(randSkillIDExList) > skillExCnt:
+                random.shuffle(randSkillIDExList)
+                randSkillIDExList = randSkillIDExList[:skillExCnt]
+            skillIDList += randSkillIDExList
+            GameWorld.DebugLog("阵容boss技能: %s, 随机附加技能: %s" % (skillIDList, randSkillIDExList))
+            
+    # 成长怪属性
+    batAttrDict = GetNPCStrongerAttrDict(npcID, lvIpyData, npcStronger, difficulty)
+    if not batAttrDict:
         batAttrDict = {ChConfig.AttrID_Atk:npcData.GetAtk(), ChConfig.AttrID_Def:npcData.GetDef(), ChConfig.AttrID_MaxHP:npcData.GetMaxHP(), 
                        ChConfig.AttrID_FinalDamPer:npcData.GetFinalDamPer(), ChConfig.AttrID_FinalDamPerDef:npcData.GetFinalDamPerDef(), 
                        ChConfig.AttrID_MissRate:npcData.GetMissRate(), ChConfig.AttrID_MissRateDef:npcData.GetMissRateDef(), 
@@ -516,15 +652,72 @@
                        ChConfig.AttrID_ParryRate:npcData.GetParryRate(), ChConfig.AttrID_ParryRateDef:npcData.GetParryRateDef(), 
                        ChConfig.AttrID_SuckHPPer:npcData.GetSuckHPPer(), ChConfig.AttrID_SuckHPPerDef:npcData.GetSuckHPPerDef(), 
                        }
-        batAttrDict.update(npcData.GetSpecAttrInfo())
-        skillIDList = [] + npcData.GetSkillIDList()
-        heroDict[str(posNum)] = {"NPCID":npcID, 
-                                 "AttrDict":{str(k):v for k, v in batAttrDict.items() if v > 0},
-                                 "SkillIDList":skillIDList
-                                 }
+    exAttrDict = npcData.GetSpecAttrInfo()
+    for attrIDStr, attrValue in exAttrDict.items():
+        attrID = int(attrIDStr)
+        batAttrDict[attrID] = batAttrDict.get(attrID, 0) + attrValue
         
-    lineupInfo = {"NPCLineupID":lineupID, "Hero":heroDict}
-    return lineupInfo
+    battleDict = {"NPCID":npcID,
+                  "HeroID":heroID,
+                  "SkinID":skinID,
+                  "LV":npcLV,
+                  "AttrDict":{str(k):v for k, v in batAttrDict.items() if v > 0},
+                  "SkillIDList":skillIDList,
+                  }
+    
+    GameWorld.DebugLog("GetNPCBattleDict npcID=%s,strongerLV=%s,difficulty=%s,%s" % (npcID, strongerLV, difficulty, battleDict))
+    return battleDict
+
+def GetNPCHeroSkillIDList(heroID, heroIpyData, breakLV, awakeLV):
+    ## 获取NPC对应武将的技能ID列表
+    normalSkillID = heroIpyData.GetNormalSkillID()
+    angerSkillID = heroIpyData.GetAngerSkillID()
+    skillIDList = [normalSkillID, angerSkillID]
+    
+    breakIpyDataList = IpyGameDataPY.GetIpyGameDataListNotLog("HeroBreak", heroID)
+    if breakIpyDataList:
+        for breakIpyData in breakIpyDataList:
+            if breakIpyData.GetBreakLV() > breakLV:
+                break
+            skillID = breakIpyData.GetSkillID()
+            if skillID:
+                skillIDList.append(skillID)
+                
+    awakeIpyDataList = IpyGameDataPY.GetIpyGameDataListNotLog("HeroAwake", heroID)
+    if awakeIpyDataList:
+        for awakeIpyData in awakeIpyDataList:
+            if awakeIpyData.GetAwakeLV() > awakeLV:
+                break
+            skillID = awakeIpyData.GetSkillID()
+            if skillID:
+                skillIDList.append(skillID)
+                
+    return skillIDList
+
+def GetNPCStrongerAttrDict(npcID, lvIpyData, npcStronger, difficulty):
+    ## 获取NPC成长属性
+    # @param strongerLV: 成长等级
+    # @param difficulty: 难度系数
+    
+    batAttrDict = {}
+    if not lvIpyData or not npcStronger or not difficulty:
+        return batAttrDict
+    lv = lvIpyData.GetLV()
+    for attrID in ChConfig.CalcBattleAttrIDList:
+        attrIpyData = IpyGameDataPY.GetIpyGameData("PlayerAttr", attrID)
+        if not attrIpyData:
+            continue
+        attrName = attrIpyData.GetParameter()
+        if not hasattr(lvIpyData, "GetRe%s" % attrName):
+            continue
+        reValue = getattr(lvIpyData, "GetRe%s" % attrName)() # 基础参考值
+        ratio = getattr(npcStronger, "Get%sRatio" % attrName)() if hasattr(npcStronger, "Get%sRatio" % attrName) else 1 # 属性系数
+        attrValue = int(reValue * ratio * difficulty)
+        batAttrDict[attrID] = attrValue
+        #GameWorld.DebugLog("    attrID=%s,attrValue=%s,reValue=%s,ratio=%s,difficulty=%s" % (attrID, attrValue, reValue, ratio, difficulty))
+        
+    GameWorld.DebugLog("NPC成长属性: npcID=%s,lv=%s,difficulty=%s,%s" % (npcID, lv, difficulty, batAttrDict))
+    return batAttrDict
 
 def SummonLineupObjs(batLineup, faction, num, lineupInfo, playerID=0):
     '''召唤阵容战斗实例
@@ -545,76 +738,248 @@
     
     batObjMgr = BattleObj.GetBatObjMgr()
     initXP = IpyGameDataPY.GetFuncCfg("AngerXP", 1)
-    atkBackSkillIDList = IpyGameDataPY.GetFuncEvalCfg("ParryCfg", 3)
     for posNumKey, heroInfo in heroDict.items():
         posNum = int(posNumKey)
         
-        npcID, heroID, skinID = 0, 0, 0
-        atkBackSkillID = 0 # 反击技能ID
         fightPower = 0
         skillIDList = [] # 战斗对象可能改变属性或技能,重新创建,防止误修改来源值
         attrDict = {}
         skillIDList += heroInfo.get("SkillIDList", [])
         attrDict.update(heroInfo.get("AttrDict", {}))
-        objName = ""
+        npcID = heroInfo.get("NPCID", 0)
+        heroID = heroInfo.get("HeroID", 0)
+        skinID = heroInfo.get("SkinID", 0)
+        lv = heroInfo.get("LV", 1)
+        specialty, atkDistType, country, sex = 0, 1, 0, 1
+        heroIpyData = IpyGameDataPY.GetIpyGameData("Hero", heroID) if heroID else None
+        if heroIpyData:
+            objName = heroIpyData.GetName()
+            specialty = heroIpyData.GetSpecialty()
+            atkDistType = heroIpyData.GetAtkDistType()
+            country = heroIpyData.GetCountry()
+            sex = heroIpyData.GetSex()
+            
         if lineupPlayerID:
-            heroID = heroInfo.get("HeroID", 0)
-            skinID = heroInfo.get("SkinID", 0)
-            lv = heroInfo.get("LV", 1)
             fightPower = heroInfo.get("FightPower", 0)
-            heroIpyData = IpyGameDataPY.GetIpyGameData("Hero", heroID)
             if not heroIpyData:
                 continue
-            atkDistType = heroIpyData.GetAtkDistType()
-            objName = heroIpyData.GetName()
+            
         else:
-            npcID = heroInfo.get("NPCID", 0)
             npcDataEx = NPCCommon.GetNPCDataPy(npcID)
             if not npcDataEx:
                 continue
-            objName = npcDataEx.GetNPCName()
-            atkDistType = npcDataEx.GetAtkDistType()
-            lv = npcDataEx.GetLV()
-            
+            if not heroIpyData:
+                objName = npcDataEx.GetNPCName()
+                
         batObj = batObjMgr.addBatObj()
         if not batObj:
             break
         objID = batObj.GetID()
+        if npcID:
+            batObj.SetNPCID(npcID)
+        elif lineupPlayerID:
+            batObj.SetOwnerID(lineupPlayerID)
         batObj.SetTFGUID(tfGUID)
         batObj.SetName(objName)
         batObj.SetFaction(faction)
         batObj.SetLineupPos(posNum, num)
         batObj.SetFightPower(fightPower)
         batObj.SetLV(lv)
-        if npcID:
-            batObj.SetNPCID(npcID)
-        elif lineupPlayerID:
-            batObj.SetOwnerHero(lineupPlayerID, heroID, skinID)
-        batObj.InitBatAttr({int(k):v for k, v in attrDict.items()}, initXP)
+        batObj.SetAtkDistType(atkDistType)
+        batObj.SetSpecialty(specialty)
+        batObj.SetCountry(country)
+        batObj.SetSex(sex)
+        batObj.SetHero(heroID, skinID)
         
-        if atkDistType == ChConfig.AtkDistType_Short:
-            atkBackSkillID = atkBackSkillIDList[0] if len(atkBackSkillIDList) > 0 else 0
-        elif atkDistType == ChConfig.AtkDistType_Long:
-            atkBackSkillID = atkBackSkillIDList[1] if len(atkBackSkillIDList) > 1 else 0
-        if atkBackSkillID:
-            skillIDList.append(atkBackSkillID)
         skillManager = batObj.GetSkillManager()
         skillManager.SkillReset()
         for skillID in skillIDList:
             skillManager.LearnSkillByID(skillID)
             
         batLineup.posObjIDDict[posNum] = objID
-        GameWorld.DebugLog("AddBatObj ID:%s,faction:%s,num=%s,posNum=%s,skill=%s,atk=%s,def=%s,hp=%s" 
-                           % (objID, faction, num, posNum, skillIDList, batObj.GetAtk(), batObj.GetDef(), batObj.GetHP()))
+        GameWorld.DebugLog("AddBatObj %s,skill=%s" % (GetObjName(batObj), skillManager.GetSkillIDList()))
+        batObj.InitBatAttr({int(k):v for k, v in attrDict.items()}, initXP)
         
     return
+
+def ResetByNextTeam(turnFight):
+    ## 切换下一小队时重置相关,目前设置仅保留血量、怒气、已被击杀的不复活,其他重置
+    
+    batFaction = turnFight.getBatFaction(ChConfig.Def_FactionA)
+    if not batFaction:
+        return
+    GameWorld.DebugLog("切换小队重置玩家阵容武将...")
+    batLineup = batFaction.getBatlineup(1) # 只处理玩家阵容
+    batObjMgr = BattleObj.GetBatObjMgr()
+    for objID in batLineup.posObjIDDict.values():
+        batObj = batObjMgr.getBatObj(objID)
+        if not batObj:
+            continue
+        objName = GetObjName(batObj)
+        if not batObj.IsAlive():
+            GameWorld.DebugLog("    已被击杀不处理! %s" % (objName))
+            continue
+        GameWorld.DebugLog("    重置武将: %s, HP:%s/%s, XP:%s" % (objName, batObj.GetHP(), batObj.GetMaxHP(), batObj.GetXP()))
+        
+        batObj.TurnReset()
+        
+        # 清除buff
+        buffMgr = batObj.GetBuffManager()
+        buffMgr.ClearBuff()
+        
+        # 重置CD
+        
+        # 重刷属性、被动
+        TurnBuff.RefreshBuffAttr(batObj)
+        TurnPassive.RefreshPassive(batObj)
+        
+    return
+
+#// B4 10 回合制战斗 #tagCMTurnFight
+#
+#struct    tagCMTurnFight
+#{
+#    tagHead        Head;
+#    DWORD        MapID;        // 自定义地图ID,可用于绑定战斗地图场景功能(如主线boss、爬塔、竞技场等)
+#    DWORD        FuncLineID;    // MapID对应的扩展值,如具体某个关卡等
+#    BYTE        TagType;        // 目标类型,0-NPC阵容,1-玩家
+#    DWORD        TagID;        // 目标类型对应的ID,如玩家ID
+#    BYTE        ValueCount;
+#    DWORD        ValueList[ValueCount]; // 附加值列表,可选,具体含义由MapID决定
+#};
+def OnTurnFight(index, clientData, tick):
+    ''' 本战斗会一次性处理完所有小队战斗,以战报的形式下发,目前除了主线小怪分段战斗外,均使用该战斗
+    '''
+    curPlayer = GameWorld.GetPlayerManager().GetPlayerByIndex(index)
+    mapID = clientData.MapID
+    funcLineID = clientData.FuncLineID
+    tagType = clientData.TagType
+    tagID = clientData.TagID
+    valueList = clientData.ValueList
+    
+    GameWorld.DebugLog("回合制战斗请求: mapID=%s,funcLineID=%s,tagType=%s,tagID=%s,valueList=%s" 
+                       % (mapID, funcLineID, tagType, tagID, valueList), curPlayer.GetPlayerID())
+    
+    reqRet = FBLogic.OnTurnFightRequest(curPlayer, mapID, funcLineID, tagType, tagID, valueList)
+    if not reqRet:
+        return
+    
+    # 攻防方所使用的阵容ID
+    atkLineupID, defLineupID = FBLogic.GetFBPlayerLineupID(curPlayer, mapID, funcLineID)
+    if atkLineupID not in ShareDefine.LineupList or defLineupID not in ShareDefine.LineupList:
+        return
+    
+    # 玩家
+    if tagType == 1:
+        OnTurnFightVSPlayer(curPlayer, mapID, funcLineID, atkLineupID, defLineupID, tagID)
+        
+    # NPC
+    else:
+        ret = FBLogic.GetFBNPCLineupInfo(curPlayer, mapID, funcLineID)
+        if not ret:
+            return
+        npcLineupIDList, strongerLV, difficulty = ret
+        OnTurnFightVSNPC(curPlayer, mapID, funcLineID, atkLineupID, npcLineupIDList, strongerLV, difficulty)
+        
+    return
+
+def OnTurnFightVSNPC(curPlayer, mapID, funcLineID, atkLineupID, npcLineupIDList, strongerLV, difficulty):
+    playerID = curPlayer.GetPlayerID()
+    GameWorld.DebugLog("OnTurnFightVSNPC: mapID=%s,funcLineID=%s,atkLineupID=%s,npcLineupIDList=%s,strongerLV=%s,difficulty=%s" 
+                       % (mapID, funcLineID, atkLineupID, npcLineupIDList, strongerLV, difficulty), playerID)
+    if not npcLineupIDList:
+        return
+    
+    lineupMainInfo = GetPlayerLineupInfo(curPlayer, atkLineupID)
+    if not lineupMainInfo:
+        GameWorld.DebugLog("玩家没有该阵容数据! atkLineupID=%s" % atkLineupID, playerID)
+        return
+    
+    turnMax = GetTurnMax(mapID)
+    if mapID == ChConfig.Def_FBMapID_MainBoss:
+        # 停止主线小怪战斗、清空
+        mainFightMgr = GetMainFightMgr(curPlayer)
+        mainTF = mainFightMgr.turnFight
+        if mainTF.isInFight():
+            mainTF.exitFight()
+            
+    tfMgr = GetTurnFightMgr()
+    turnFight = tfMgr.addTurnFight(mapID, funcLineID, playerID)
+    guid = turnFight.guid
+    
+    turnFight.setTurnFight(mapID, funcLineID, turnMax, True)
+    turnFight.setPVE(npcLineupIDList, strongerLV, difficulty)
+    turnFight.setFactionLineup(ChConfig.Def_FactionA, {1:lineupMainInfo})
+    
+    for index, lineupID in enumerate(npcLineupIDList):
+        turnFight.lineupIndex = index
+        GameWorld.DebugLog("对战NPC阵容: index=%s, lineupID=%s" % (index, lineupID))
+        if index > 0:
+            turnFight.nextTurnFight(resetByNextTeam=True)
+        turnFight.setFactionLineup(ChConfig.Def_FactionB, {1:GetNPCLineupInfo(lineupID, strongerLV, difficulty)})
+        turnFight.sortActionQueue()
+        turnFight.startFight()
+        
+        __processTurnFight(turnFight.guid)
+        
+        if not turnFight.isWin:
+            break
+        
+    PlayerOnline.GetOnlinePlayer(curPlayer).SetLastBatBuffer(guid, turnFight.batBuffer)
+    SyncTurnFightReport(curPlayer, guid, turnFight.batBuffer)
+    tfMgr.delTurnFight(guid)
+    return
+
+def OnTurnFightVSPlayer(curPlayer, mapID, funcLineID, atkLineupID, defLineupID, tagPlayerID):
+    playerID = curPlayer.GetPlayerID()
+    GameWorld.DebugLog("OnTurnFightVSPlayer: mapID=%s,funcLineID=%s,atkLineupID=%s,defLineupID=%s,tagPlayerID=%s" 
+                       % (mapID, funcLineID, atkLineupID, defLineupID, tagPlayerID), playerID)
+    atkLineupInfo = GetPlayerLineupInfo(curPlayer, atkLineupID)
+    if not atkLineupInfo:
+        GameWorld.DebugLog("玩家没有该阵容数据! atkLineupID=%s" % atkLineupID, playerID)
+        return
+    
+    tagViewCache = PlayerViewCache.FindViewCache(tagPlayerID)
+    if not tagViewCache:
+        GameWorld.DebugLog("目标玩家没有缓存数据! tagPlayerID=%s" % tagPlayerID, playerID)
+        return {}
+    defLineupInfo = GetCacheLineupInfo(tagViewCache, defLineupID)
+    if not defLineupInfo:
+        GameWorld.DebugLog("目标玩家没有该阵容数据! tagPlayerID=%s,defLineupID=%s" % (tagPlayerID, defLineupID), playerID)
+        return
+    
+    turnMax = GetTurnMax(mapID)
+    
+    tfMgr = GetTurnFightMgr()
+    turnFight = tfMgr.addTurnFight(mapID, funcLineID, playerID)
+    guid = turnFight.guid
+    
+    turnFight.setTurnFight(mapID, funcLineID, turnMax, True)
+    turnFight.setPVP(tagPlayerID, tagViewCache)
+    turnFight.setFactionLineup(ChConfig.Def_FactionA, {1:atkLineupInfo})
+    turnFight.setFactionLineup(ChConfig.Def_FactionB, {1:defLineupInfo})
+    turnFight.sortActionQueue()
+    turnFight.startFight()
+    
+    __processTurnFight(turnFight.guid)
+    
+    PlayerOnline.GetOnlinePlayer(curPlayer).SetLastBatBuffer(guid, turnFight.batBuffer)
+    SyncTurnFightReport(curPlayer, guid, turnFight.batBuffer)
+    tfMgr.delTurnFight(guid)
+    return
+
+def GetTurnMax(mapID):
+    if mapID == ChConfig.Def_FBMapID_Main:
+        return IpyGameDataPY.GetFuncCfg("TurnMax", 1)
+    mapTurnMaxDict= IpyGameDataPY.GetFuncEvalCfg("TurnMax", 3, {})
+    return mapTurnMaxDict.get(mapID, IpyGameDataPY.GetFuncCfg("TurnMax", 2))
 
 #// B4 13 主线战斗请求 #tagCSMainFightReq
 #
 #struct    tagCSMainFightReq
 #{
 #    tagHead        Head;
-#    BYTE        ReqType;        // 0-停止战斗回城;1-设置消耗倍值;2-挑战关卡小怪;3-挑战关卡boss;4-继续战斗;
+#    BYTE        ReqType;        // 0-停止战斗回城;1-设置消耗倍值;2-挑战关卡小怪;4-继续战斗;
 #    DWORD        ReqValue;    // 请求值,ReqType为1时发送消耗倍值
 #};
 def OnMainFightReq(index, clientData, tick):
@@ -629,14 +994,13 @@
         __doSetFightPoint(curPlayer, reqValue)
         return
     
+    GameWorld.DebugLog("主线战斗请求: reqType=%s" % reqType, curPlayer.GetPlayerID())
     clientPack = ChPyNetSendPack.tagSCTurnFightReportSign()
     clientPack.Sign = 0
     NetPackCommon.SendFakePack(curPlayer, clientPack) # 标记开始
     
     if reqType == 2: # 前端主动请求开始关卡小怪的视为从休息中开始
         __doMainLevelWave(curPlayer, True)
-    elif reqType == 3:
-        __doMainBossStart(curPlayer, tick)
     elif reqType == 4:
         __doMainFight(curPlayer, tick)
     else:
@@ -652,7 +1016,7 @@
     mainFightMgr = GetMainFightMgr(curPlayer)
     turnFight = mainFightMgr.turnFight
     if turnFight:
-        turnFight.clearFight()
+        turnFight.exitFight(True)
     return
 
 def __doSetFightPoint(curPlayer, fightPoint):
@@ -676,9 +1040,6 @@
     # @param isRestStart: 是否从休息状态重新开始的
     playerID = curPlayer.GetPlayerID()
     chapterID, levelNum, wave = PlayerControl.GetMainLevelNowInfo(curPlayer)
-    if not chapterID and not levelNum:
-        PlayerControl.SetMainLevelNowInfo(curPlayer, 1, 1, 1)
-        chapterID, levelNum, wave = PlayerControl.GetMainLevelNowInfo(curPlayer)
     GameWorld.DebugLog("请求关卡波战斗: chapterID=%s,levelNum=%s,wave=%s,isRestStart=%s" % (chapterID, levelNum, wave, isRestStart), playerID)
     fightPoint = max(curPlayer.GetFightPoint(), 1)
     if not PlayerControl.HaveMoney(curPlayer, ShareDefine.TYPE_Price_Xiantao, fightPoint):
@@ -707,275 +1068,187 @@
     if wave > waveMax:
         wave = waveMax
         
-    waveLineupList = getattr(levelIpyData, "GetWaveLineupIDList%s" % wave)() # 小队1阵容ID|小队2阵容ID|...
-    if not waveLineupList:
+    lineupIDList = getattr(levelIpyData, "GetWaveLineupIDList%s" % wave)() # 小队1阵容ID|小队2阵容ID|...
+    if not lineupIDList:
         return
-    teamMax = len(waveLineupList)
-    teamNum = 1
-    lineupID = waveLineupList[teamNum - 1] # NPC阵容ID
+    lineupID = lineupIDList[0] # NPC阵容ID
     
     lineupMainInfo = GetPlayerLineupInfo(curPlayer, ShareDefine.Lineup_Main)
     if not lineupMainInfo:
         GameWorld.DebugLog("没有设置主阵容!", playerID)
         return
     
+    strongerLV = levelIpyData.GetNPCLV()
+    difficulty = levelIpyData.GetDifficulty()
     mainFightMgr = GetMainFightMgr(curPlayer)
-    mainFightMgr.nextTeam = False
     mainFightMgr.chapterID = chapterID
     mainFightMgr.levelNum = levelNum
     mainFightMgr.waveMax = waveMax
     mainFightMgr.wave = wave
-    mainFightMgr.waveLineupList = waveLineupList
-    mainFightMgr.teamNum = teamNum
-    mainFightMgr.teamMax = teamMax
-    turnMax = IpyGameDataPY.GetFuncCfg("Mainline", 2)
     mapID, funcLineID = ChConfig.Def_FBMapID_Main, PlayerControl.ComMainLevelValue(chapterID, levelNum, wave)
-    GameWorld.DebugLog("设置起始关卡波: 关卡%s-%s,波=%s/%s,teamMax=%s,mapID=%s,funcLineID=%s,lineupID=%s" 
-                       % (chapterID, levelNum, wave, waveMax, teamMax, mapID, funcLineID, lineupID), playerID)
+    turnMax = GetTurnMax(mapID)
+    GameWorld.DebugLog("设置起始关卡波: 关卡%s-%s,波=%s/%s,lineupIDList=%s,mapID=%s,funcLineID=%s,lineupID=%s,strongerLV=%s,difficulty=%s" 
+                       % (chapterID, levelNum, wave, waveMax, lineupIDList, mapID, funcLineID, lineupID, strongerLV, difficulty), playerID)
     
     turnFight = mainFightMgr.turnFight
-    turnFight.setTurn(mapID, funcLineID, turnMax, False, {"teamNum":teamNum, "teamMax":teamMax})
-    turnFight.setFactionLineup(ChConfig.Def_FactionA, {1:lineupMainInfo}, True)
-    turnFight.setFactionLineup(ChConfig.Def_FactionB, {1:GetNPCLineupInfo(lineupID)})
+    turnFight.setTurnFight(mapID, funcLineID, turnMax, False)
+    turnFight.setPVE(lineupIDList, strongerLV, difficulty)
+    turnFight.setFactionLineup(ChConfig.Def_FactionA, {1:lineupMainInfo})
+    turnFight.setFactionLineup(ChConfig.Def_FactionB, {1:GetNPCLineupInfo(lineupID, strongerLV, difficulty)})
     turnFight.sortActionQueue()
-    turnFight.syncInit()
+    turnFight.startFight()
+    
+    __doMainFight(curPlayer)
     return
 
-def __doMainBossStart(curPlayer, tick):
-    ## 开始挑战关卡boss
-    playerID = curPlayer.GetPlayerID()
-    chapterID, levelNum, wave = PlayerControl.GetMainLevelPassInfo(curPlayer)
-    if not chapterID:
-        chapterID = 1
-    if not levelNum:
-        levelNum = 1
-    GameWorld.DebugLog("请求挑战关卡Boss! passInfo: chapterID=%s,levelNum=%s,wave=%s" % (chapterID, levelNum, wave), playerID)
-    chapterIpyData = IpyGameDataPY.GetIpyGameData("MainChapter", chapterID)
-    if not chapterIpyData:
-        return
-    levelIpyData = IpyGameDataPY.GetIpyGameData("MainLevel", chapterID, levelNum)
-    if not levelIpyData:
-        return
+def __doMainFight(curPlayer, tick=0):
+    '''执行主线战斗,单场战斗断电式战斗,以前端玩家手动点击节点做为断点处
+    每场战斗的初始化、结束默认断点,由前端决定自动继续或者点击继续
+    '''
     
-    nowChapterID, nowLevelNum, _ = PlayerControl.GetMainLevelNowInfo(curPlayer)
-    if chapterID != nowChapterID or levelNum != nowLevelNum:
-        '''一般有两种情况会导致
-        1. 理论上玩家当前刷怪的章节关卡一定是与过关的章节关卡是一致的,除了全部章节已通关
-                                当通关时,已过关的记录会被设置为下一章节第1关(版本关卡表实际不存在该关卡)
-                                但是当前刷怪的还是版本的最后一关,固会出现不一致的情况
-                                在增加章节关卡新版本更新后玩家重登会自动进行修复当前刷怪进度为最新章节第1关
-        2. 真的出现问题了,可能是bug引起,这种情况只能进行bug修复及针对异常账号特殊处理,或者重登后也会自动进行修正
-        '''
-        GameWorld.ErrLog("当前刷怪章节关卡与挑战的boss章节关卡不一致,无法挑战! chapterID=%s,levelNum=%s,nowChapterID=%s,nowLevelNum=%s" 
-                         % (chapterID, levelNum, nowChapterID, nowLevelNum), playerID)
-        return
-    
-    # 本关卡最大波数
-    waveMax = 6
-    while waveMax >= 1 and (not hasattr(levelIpyData, "GetWaveLineupIDList%s" % waveMax) or not getattr(levelIpyData, "GetWaveLineupIDList%s" % waveMax)()):
-        waveMax -= 1
+    # 限制请求CD
+    if tick:
+        key = "MainFightReqTick"
+        lastTick = curPlayer.GetDictByKey(key)
+        if lastTick and tick - lastTick <= 1000:
+            GameWorld.DebugLog("主线战斗请求CD中")
+            return
+        curPlayer.SetDict(key, tick)
         
-    if wave < waveMax:
-        GameWorld.DebugLog("最后一波未通过,无法挑战本关boss! passWave=%s < %s" % (wave, waveMax))
-        return
-    
-    waveLineupList = levelIpyData.GetBossLineupIDList() # Boss波阵容ID列表,小队1阵容ID|小队2阵容ID|...
-    if not waveLineupList:
-        return
-    teamMax = len(waveLineupList)
-    teamNum = 1
-    lineupID = waveLineupList[teamNum - 1] # NPC阵容ID
-    
-    wave = waveMax = 1 # 关卡boss固定只有一波
-    
-    lineupMainInfo = GetPlayerLineupInfo(curPlayer, ShareDefine.Lineup_Main)
-    if not lineupMainInfo:
-        GameWorld.DebugLog("没有设置主阵容!", playerID)
-        return
-    
-    mainFightMgr = GetMainFightMgr(curPlayer)
-    mainFightMgr.nextTeam = False
-    mainFightMgr.chapterID = chapterID
-    mainFightMgr.levelNum = levelNum
-    mainFightMgr.waveMax = waveMax
-    mainFightMgr.wave = wave
-    mainFightMgr.waveLineupList = waveLineupList
-    mainFightMgr.teamNum = teamNum
-    mainFightMgr.teamMax = teamMax
-    turnMax = IpyGameDataPY.GetFuncCfg("Mainline", 3)
-    mapID, funcLineID = ChConfig.Def_FBMapID_MainBoss, PlayerControl.ComMainLevelValue(chapterID, levelNum, wave)
-    GameWorld.DebugLog("设置关卡boss: 关卡%s-%s,波=%s/%s,teamMax=%s,mapID=%s,funcLineID=%s,lineupID=%s" 
-                       % (chapterID, levelNum, wave, waveMax, teamMax, mapID, funcLineID, lineupID), playerID)
-    
-    turnFight = mainFightMgr.turnFight
-    turnFight.setTurn(mapID, funcLineID, turnMax, False, {"teamNum":teamNum, "teamMax":teamMax})
-    turnFight.setFactionLineup(ChConfig.Def_FactionA, {1:lineupMainInfo}, True)
-    turnFight.setFactionLineup(ChConfig.Def_FactionB, {1:GetNPCLineupInfo(lineupID)})
-    turnFight.sortActionQueue()
-    turnFight.syncInit()
-    
-    # 挑战boss无中间过程,每次执行直接挑战一队结果
-    __processTurnFight(turnFight.guid, tick)
-    return
-
-def __doMainFight(curPlayer, tick):
-    ## 主线执行战斗
     mainFightMgr = GetMainFightMgr(curPlayer)
     turnFight = mainFightMgr.turnFight
     
     isLevelBoss = mainFightMgr.isLevelBoss()
-    winFaction = turnFight.winFaction
-    if winFaction:
-        if mainFightMgr.nextTeam:
-            # 自动开始下一小队
-            teamNum = mainFightMgr.teamNum = mainFightMgr.teamNum + 1
-            GameWorld.DebugLog("开始进入下一小队: teamNum=%s" % teamNum)
-            if teamNum < 1 or teamNum > len(mainFightMgr.waveLineupList):
-                return
-            lineupID = mainFightMgr.waveLineupList[teamNum - 1] # NPC阵容ID
-            GameWorld.DebugLog("teamNum=%s,lineupID=%s" % (teamNum, lineupID))
-            
-            mainFightMgr.nextTeam = False
-            turnFight.resetTurn({"teamNum":teamNum})
-            # 切换小队时,玩家阵容不需要处理,保留状态
-            turnFight.setFactionLineup(ChConfig.Def_FactionB, {1:GetNPCLineupInfo(lineupID)})
-            turnFight.sortActionQueue()
-            turnFight.syncInit()
-            
-            if mainFightMgr.isLevelBoss():
-                # 每次处理一小队的完整战斗,相当于一次完整战报
-                __processTurnFight(turnFight.guid, tick)
-                return
-        else:
-            __doMainLevelWave(curPlayer, False)
-        return
-    
     if isLevelBoss:
         ## 关卡boss是一次性处理完的,一般不可能走到这里,这边做下防范
         return
     
-    # 以下均是处理关卡小怪分段实时战斗
+    if not turnFight.isInFight():
+        __doMainLevelWave(curPlayer, True)
+        return
     
-    EntryLogic(turnFight)
+    winFaction = turnFight.winFaction
+    if winFaction:
+        nextLineupID = turnFight.nextLineupID()
+        if nextLineupID:
+            GameWorld.DebugLog("---开始进入下一小队: lineupIndex=%s,nextLineupID=%s,%s" % (turnFight.lineupIndex, nextLineupID, turnFight.lineupIDList))
+            
+            turnFight.nextTurnFight(resetByNextTeam=True)
+            # 切换小队时,玩家阵容不需要处理,保留状态
+            turnFight.setFactionLineup(ChConfig.Def_FactionB, {1:GetNPCLineupInfo(nextLineupID, turnFight.strongerLV, turnFight.difficulty)})
+            turnFight.sortActionQueue()
+            turnFight.startFight()
+            
+            __doMainFight(curPlayer)
+        else:
+            __doMainLevelWave(curPlayer, False)
+        return
     
     # 小怪战斗,每次消耗1个战锤
     fightPoint = max(curPlayer.GetFightPoint(), 1) # 主线战斗消耗倍值,默认1
+    if not PlayerControl.HaveMoney(curPlayer, ShareDefine.TYPE_Price_Xiantao, fightPoint):
+        GameWorld.DebugLog("回合开始时战锤不足!")
+        return
     
-    # 按阵营阵容执行顺序,逐个遍历
-    doCnt = 0
-    doMax = (PosNumMax + 2) * len(turnFight.actionSortList) # 防止死循环,做最大循环次数限制 = (最大位置数 + 主公、红颜位置)*行动阵容数
-    overLineupList = [] # 本回合已经结束行动的阵容列表 [(faction, num), ...], 所有阵容全部结束代表本回合结束
+    # 以下均是处理关卡小怪分段实时战斗
+    isEntry = EntryLogic(turnFight)
+    
+    # 是否开始检查断点,预判可断的点,方便前端点击体验,点下去就是玩家放的某个行动
+    # 初始开始进场后,默认开始断点
+    checkBreakpoint = True if isEntry else False
     
     batObjMgr = BattleObj.GetBatObjMgr()
     turnNum = turnFight.turnNum
-    GameWorld.DebugLog("turnNum=%s,doMax=%s,actionIndex=%s,%s" % (turnNum, doMax, turnFight.actionIndex, turnFight.actionSortList))
-    while doCnt < doMax and len(overLineupList) < len(turnFight.actionSortList):
-        doCnt += 1
-        turnNum = turnFight.turnNum
-        # 执行回合开始逻辑
-        if turnFight.turnStart < turnNum:
-            GameWorld.DebugLog("执行行动: turnNum=%s, 回合开始" % (turnFight.turnNum))
-            turnFight.syncState(FightState_Fighting)
-            if not PlayerControl.HaveMoney(curPlayer, ShareDefine.TYPE_Price_Xiantao, fightPoint):
-                GameWorld.DebugLog("回合开始时战锤不足!")
-                return
-            turnFight.turnStart = turnNum
-            turnFight.actionIndex = 0
+    turnMax = turnFight.turnMax
+    for turnNum in range(turnNum, turnMax + 1):
+        turnTimeline = turnFight.getTurnNumStartTimelin(turnNum) # 本回合起始时间节点
+        curTimeline = turnFight.getTimeline()
+        
+        # 回合开始
+        turnTimeline += 1 # 每回合开始算一个时间节点
+        if curTimeline < turnTimeline:
+            curTimeline = turnFight.setTimeline(turnTimeline)
+            GameWorld.DebugLog("【----- 回合制战斗轮次: %s -----】 curTimeline=%s" % (turnNum, curTimeline))
+            turnFight.turnNum = turnNum
+            if curPlayer:
+                turnFight.syncState(FightState_Fighting)
+                
             for faction, num in turnFight.actionSortList:
+                GameWorld.DebugLog("大回合开始逻辑: turnNum=%s,faction=%s, num=%s" % (turnNum, faction, num))
                 batFaction = turnFight.getBatFaction(faction)
                 batLineup = batFaction.getBatlineup(num)
-                batLineup.actionNum = ActionNumStart
+                batLineup.actionNum = 1
                 for objID in batLineup.posObjIDDict.values():
                     batObj = batObjMgr.getBatObj(objID)
-                    TurnFightObjPerTurnStart(turnFight, batObj, turnNum)
+                    TurnFightPerTurnBigStart(turnFight, batObj, turnNum)
                     
-        if turnFight.actionIndex >= len(turnFight.actionSortList):
-            turnFight.actionIndex = 0
-            
-        playerHeroAtk = False # 玩家阵容行动标记
-        faction, num = turnFight.actionSortList[turnFight.actionIndex]
-        batFaction = turnFight.getBatFaction(faction)
-        batLineup = batFaction.getBatlineup(num)
-        if batLineup.actionNum > max(batLineup.posObjIDDict):
-            if (faction, num) not in overLineupList:
-                overLineupList.append((faction, num))
-                
-            turnFight.actionIndex += 1
-            continue
-        
-        if faction == ChConfig.Def_FactionA:
-            if not PlayerControl.HaveMoney(curPlayer, ShareDefine.TYPE_Price_Xiantao, fightPoint):
-                GameWorld.DebugLog("战锤不足!")
-                return
-        GameWorld.DebugLog("执行行动: turnNum=%s,faction=%s,num=%s,actionNum=%s" % (turnFight.turnNum, faction, num, batLineup.actionNum))
-        
-        # 主公
-        if batLineup.actionNum == -1:
-            pass
-        
         # 红颜
-        elif batLineup.actionNum == 0:
-            pass
+        # 灵兽
+        
+        if turnFight.checkOverByKilled():
+            break
         
         # 武将
-        elif batLineup.actionNum > 0:
+        doMax = PosNumMax * len(turnFight.actionSortList)
+        doCnt = 0
+        while doCnt < doMax and turnFight.actionIndex < len(turnFight.actionSortList):
+            doCnt += 1
+            faction, num = turnFight.actionSortList[turnFight.actionIndex]
+            batFaction = turnFight.getBatFaction(faction)
+            batLineup = batFaction.getBatlineup(num)
             for posNum in range(batLineup.actionNum, PosNumMax + 1):
-                batLineup.actionNum = posNum
+                # 每个武将位算一个时间节点
+                #GameWorld.DebugLog("武将节点: faction=%s,posNum=%s,curTimeline=%s" % (faction, posNum, curTimeline))
                 if posNum not in batLineup.posObjIDDict:
+                    batLineup.actionNum = posNum + 1
+                    curTimeline = turnFight.setTimeline(curTimeline + 1, True)
+                    #GameWorld.DebugLog("空位节点: faction=%s,posNum=%s,curTimeline=%s" % (faction, posNum, curTimeline))
                     continue
+                
                 objID = batLineup.posObjIDDict[posNum]
                 batObj = batObjMgr.getBatObj(objID)
+                
+                # 玩家自己阵营,预判可否行动
+                if checkBreakpoint and faction == ChConfig.Def_FactionA and batObj:
+                    if batObj.CanAction():
+                        GameWorld.DebugLog("玩家阵容下一个可行动的武将,断点: curTimeline=%s,nextPosNum=%s" % (curTimeline, posNum))
+                        return
+                    
+                batLineup.actionNum = posNum + 1
+                curTimeline = turnFight.setTimeline(curTimeline + 1)
+                TurnFightHeroTurnStart(turnFight, batObj, turnNum)
                 if not OnObjAction(turnFight, batObj):
                     continue
                 
-                if faction == ChConfig.Def_FactionA:
-                    playerHeroAtk = True
+                if not checkBreakpoint and faction == ChConfig.Def_FactionA:
+                    checkBreakpoint = True
                     
                 break
             
-        turnFight.actionIndex += 1
-        batLineup.actionNum += 1
-        if batLineup.actionNum > max(batLineup.posObjIDDict):
-            GameWorld.DebugLog("该阵容本回合已经全部行动完了: turnNum=%s,faction=%s,num=%s,actionNum=%s" % (turnFight.turnNum, faction, num, batLineup.actionNum))
-            if (faction, num) not in overLineupList:
-                overLineupList.append((faction, num))
+            if turnFight.actionIndex >= len(turnFight.actionSortList) - 1:
+                turnFight.actionIndex = 0
+            else:
+                turnFight.actionIndex += 1
+                
+        # 回合结束
+        curTimeline = turnFight.setTimeline(curTimeline + 1) # 每回合结束算一个时间节点
+        for faction, num in turnFight.actionSortList:
+            GameWorld.DebugLog("回合结束逻辑: turnNum=%s,faction=%s, num=%s" % (turnNum, faction, num))
+            batFaction = turnFight.getBatFaction(faction)
+            batLineup = batFaction.getBatlineup(num)
+            for objID in batLineup.posObjIDDict.values():
+                batObj = batObjMgr.getBatObj(objID)
+                TurnFightPerTurnBigEnd(turnFight, batObj, turnNum)
                 
         if turnFight.checkOverByKilled():
             break
         
-        if playerHeroAtk:
-            # 玩家武将有行动则停止,一段段执行
-            GameWorld.DebugLog("玩家武将有行动则停止!")
-            break
+    if not turnFight.winFaction:
+        OnTurnAllOver(turnFight.guid)
         
-    if turnFight.winFaction:
-        return
-    
-    GameWorld.DebugLog("turnNum=%s,doCnt=%s,overLineupList=%s" % (turnNum, doCnt, overLineupList))
-    if len(overLineupList) >= len(turnFight.actionSortList):
-        GameWorld.DebugLog("执行行动: turnNum=%s, 回合结束" % (turnFight.turnNum))
-        if turnFight.turnEnd < turnNum:
-            if not PlayerControl.HaveMoney(curPlayer, ShareDefine.TYPE_Price_Xiantao, fightPoint):
-                GameWorld.DebugLog("回合结束时战锤不足!")
-                return
-            turnFight.turnEnd = turnNum
-            for faction, num in turnFight.actionSortList:
-                batFaction = turnFight.getBatFaction(faction)
-                batLineup = batFaction.getBatlineup(num)
-                for objID in batLineup.posObjIDDict.values():
-                    pass
-                
-            if turnFight.checkOverByKilled():
-                return
-            
-        if turnNum < turnFight.turnMax:
-            turnFight.turnNum += 1
-        else:
-            OnTurnAllOver(turnFight.guid)
-            
     return
 
-def __processTurnFight(guid, tick):
+def __processTurnFight(guid):
     ## 一次性处理完一个小队的战斗
     turnFight = GetTurnFightMgr().getTurnFight(guid)
     curPlayer = turnFight.curPlayer
@@ -985,30 +1258,24 @@
     for turnNum in range(1, turnMax + 1):
         turnFight.turnNum = turnNum
         GameWorld.DebugLog("【----- 回合制战斗轮次: %s -----】" % turnNum)
+        curTimeline = turnFight.getTurnNumStartTimelin(turnNum) # 本回合起始时间节点
+        curTimeline = turnFight.setTimeline(curTimeline + 1)
         if curPlayer:
             turnFight.syncState(FightState_Fighting)
             
         # 回合开始
         for faction, num in turnFight.actionSortList:
-            GameWorld.DebugLog("回合开始逻辑: turnNum=%s,faction=%s, num=%s" % (turnNum, faction, num))
+            GameWorld.DebugLog("大回合开始逻辑: turnNum=%s,faction=%s, num=%s" % (turnNum, faction, num))
             batFaction = turnFight.getBatFaction(faction)
             batLineup = batFaction.getBatlineup(num)
             batLineup.actionNum = 1
             for objID in batLineup.posObjIDDict.values():
                 batObj = batObjMgr.getBatObj(objID)
-                TurnFightObjPerTurnStart(turnFight, batObj, turnNum)
+                TurnFightPerTurnBigStart(turnFight, batObj, turnNum)
                 
-        # 主公
-        for faction, num in turnFight.actionSortList:
-            GameWorld.DebugLog("主公逻辑: turnNum=%s,faction=%s, num=%s" % (turnNum, faction, num))
-            batFaction = turnFight.getBatFaction(faction)
-            batLineup = batFaction.getBatlineup(num)
-            
+                    
         # 红颜
-        for faction, num in turnFight.actionSortList:
-            GameWorld.DebugLog("红颜逻辑: turnNum=%s,faction=%s, num=%s" % (turnNum, faction, num))
-            batFaction = turnFight.getBatFaction(faction)
-            batLineup = batFaction.getBatlineup(num)
+        # 灵兽
             
         if turnFight.checkOverByKilled():
             break
@@ -1024,9 +1291,12 @@
             for posNum in range(batLineup.actionNum, PosNumMax + 1):
                 batLineup.actionNum = posNum + 1
                 if posNum not in batLineup.posObjIDDict:
+                    curTimeline = turnFight.setTimeline(curTimeline + 1, True)
                     continue
+                curTimeline = turnFight.setTimeline(curTimeline + 1) # 每个武将位算一个时间节点
                 objID = batLineup.posObjIDDict[posNum]
                 batObj = batObjMgr.getBatObj(objID)
+                TurnFightHeroTurnStart(turnFight, batObj, turnNum)
                 if not OnObjAction(turnFight, batObj):
                     continue
                 
@@ -1038,13 +1308,15 @@
                 turnFight.actionIndex += 1
                 
         # 回合结束
+        curTimeline = turnFight.setTimeline(curTimeline + 1) # 每回合结束算一个时间节点
         for faction, num in turnFight.actionSortList:
             GameWorld.DebugLog("回合结束逻辑: turnNum=%s,faction=%s, num=%s" % (turnNum, faction, num))
             batFaction = turnFight.getBatFaction(faction)
             batLineup = batFaction.getBatlineup(num)
             for objID in batLineup.posObjIDDict.values():
-                pass
-            
+                batObj = batObjMgr.getBatObj(objID)
+                TurnFightPerTurnBigEnd(turnFight, batObj, turnNum)
+                
         if turnFight.checkOverByKilled():
             break
         
@@ -1052,33 +1324,18 @@
         OnTurnAllOver(turnFight.guid)
     return
 
-#// B4 10 回合制战斗 #tagCMTurnFight
-#
-#struct    tagCMTurnFight
-#{
-#    tagHead        Head;
-#    DWORD        MapID;        // 自定义地图ID,可用于绑定战斗地图场景功能(如主线关卡、主线boss、爬塔、竞技场等)
-#    DWORD        FuncLineID;    // MapID对应的扩展值,如具体某个关卡等
-#    BYTE        TagType;    // 目标类型,0-NPC阵容,1-玩家
-#    DWORD        TagID;    // 目标类型对应的ID,如阵容ID或玩家ID
-#    BYTE        ValueCount;
-#    DWORD        ValueList[ValueCount]; // 附加值列表,可选,具体含义由MapID决定
-#};
-def OnTurnFight(index, clientData, tick):
-    #curPlayer = GameWorld.GetPlayerManager().GetPlayerByIndex(index)
-    #mapID = clientData.MapID
-    #funcLineID = clientData.FuncLineID
-    return
-
 def GetObjName(batObj):
     faction = batObj.faction
     num = batObj.lineupNum
     posNum = batObj.posNum
     heroID = batObj.heroID
-    if not heroID:
-        heroID = batObj.npcID
+    npcID = batObj.npcID
     objName = GameWorld.CodeToGbk(batObj.GetName())
-    return "%s%s-%s [%s-%s] %s" % ("A" if faction == ChConfig.Def_FactionA else "B", num, posNum, batObj.GetID(), heroID, objName)
+    if heroID:
+        objName += " Hero:%s" % heroID
+    if npcID:
+        objName += " NPC:%s" % npcID        
+    return "%s%s-P%s ID:%s %s" % ("A" if faction == ChConfig.Def_FactionA else "B", num, posNum, batObj.GetID(), objName)
 
 def EntryLogic(turnFight):
     ## 执行进场逻辑
@@ -1086,68 +1343,136 @@
         return
     GameWorld.DebugLog("执行进场逻辑...")
     
+    batObjMgr = BattleObj.GetBatObjMgr()
+    for faction, num in turnFight.actionSortList:
+        batFaction = turnFight.getBatFaction(faction)
+        batLineup = batFaction.getBatlineup(num)
+        batLineup.actionNum = ActionNumStart
+        for objID in batLineup.posObjIDDict.values():
+            batObj = batObjMgr.getBatObj(objID)
+            turnFight.ResetOneActionUseSkillCnt()
+            TurnPassive.OnTriggerPassiveEffect(turnFight, batObj, ChConfig.TriggerWay_FightStart)
+            
     turnFight.enterLogic = True
+    return True
+
+def OnTimelineChange(turnFight):
+    ## 每个时间节点变化时处理
+    
+    nowTimeline = turnFight.getTimeline()
+    
+    batObjMgr = BattleObj.GetBatObjMgr()
+    for batFaction in turnFight.factionDict.values():
+        for batLineup in batFaction.lineupDict.values():
+            for objID in batLineup.posObjIDDict.values():
+                batObj = batObjMgr.getBatObj(objID)
+                #GameWorld.DebugLog("OnTimelineChange! objID=%s" % (objID))
+                if not batObj or not batObj.IsAlive():
+                    continue
+                
+                curID = batObj.GetID()
+                skillManager = batObj.GetSkillManager()
+                for index in range(0, skillManager.GetSkillCount()):
+                    curSkill = skillManager.GetSkillByIndex(index)
+                    if not curSkill:
+                        continue
+                    remainTime = curSkill.GetRemainTime()
+                    if not remainTime:
+                        continue
+                    calcTimeline = curSkill.GetCalcTime()
+                    passTurn = __calcPassturn(calcTimeline, nowTimeline, True)
+                    if passTurn <= 0:
+                        continue
+                    skillID = curSkill.GetSkillID()
+                    updRemainTime = max(0, remainTime - passTurn)
+                    curSkill.SetRemainTime(updRemainTime)
+                    curSkill.SetCalcTime(nowTimeline)
+                    GameWorld.DebugLog("更新技能剩余回合数: curID=%s,skillID=%s,updRemainTime=%s,calcTimeline=%s,passTurn=%s" 
+                                       % (curID, skillID, updRemainTime, calcTimeline, passTurn))
+                    
+                buffMgr = batObj.GetBuffManager()
+                for index in range(buffMgr.GetBuffCount())[::-1]:
+                    buff = buffMgr.GetBuffByIndex(index)
+                    buffID = buff.GetBuffID()
+                    skillID = buff.GetSkillID()
+                    skillData = buff.GetSkillData()
+                    if skillData.GetSkillType() in ChConfig.Def_LstBuff_List:
+                        #GameWorld.DebugLog("    持续类buff由触发时机决定剩余时间! curID=%s,index=%s,skillID=%s,buffID=%s" % (curID, index, skillID, buffID))
+                        continue
+                    remainTime = buff.GetRemainTime()
+                    if not remainTime:
+                        # 永久buff不处理
+                        #GameWorld.DebugLog("    永久buff不处理! curID=%s,index=%s,skillID=%s" % (curID, index, skillID))
+                        continue
+                    calcTimeline = buff.GetCalcTime()
+                    passTurn = __calcPassturn(calcTimeline, nowTimeline, False)
+                    if passTurn <= 0:
+                        #GameWorld.DebugLog("    passTurn <= 0 passTurn=%s,calcTimeline=%s,nowTimeline=%s,skillID=%s" % (passTurn, calcTimeline, nowTimeline, skillID))
+                        continue
+                    
+                    updRemainTime = max(0, remainTime - passTurn)
+                    GameWorld.DebugLog("更新buff剩余回合数: curID=%s,buffID=%s,skillID=%s,updRemainTime=%s,calcTimeline=%s,passTurn=%s" 
+                                       % (curID, buffID, skillID, updRemainTime, calcTimeline, passTurn))
+                    if updRemainTime > 0:
+                        buff.SetRemainTime(updRemainTime)
+                        buff.SetCalcTime(nowTimeline)
+                        TurnBuff.SyncBuffRefresh(turnFight, batObj, buff)
+                    else:
+                        TurnBuff.DoBuffDel(turnFight, batObj, buff)
+                        
     return
 
-def TurnFightObjPerTurnStart(turnFight, batObj, turnNum):
-    ## 回合制战斗实例 - 每回合开始时处理
+def __calcPassturn(calcTimeline, nowTimeline, equalOK):
+    ## 计算已经过了的回合数
+    # @param equalOK: 时间节点相同时是否算1回合,一般技能可以算,buff不算
+    calcTurnNum = calcTimeline / TimelineSet
+    calcTimeNode = calcTimeline % TimelineSet
+    nowTurnNum = nowTimeline / TimelineSet
+    nowTimeNode = nowTimeline % TimelineSet
+    if equalOK:
+        if nowTimeNode >= calcTimeNode:
+            return max(0, nowTurnNum - calcTurnNum)
+        return max(0, nowTurnNum - calcTurnNum - 1)
+    else:
+        if nowTimeNode > calcTimeNode:
+            return max(0, nowTurnNum - calcTurnNum)
+        return max(0, nowTurnNum - calcTurnNum - 1)
+    return 0
+
+def TurnFightPerTurnBigStart(turnFight, batObj, turnNum):
+    ## 大回合开始时
     if not batObj:
         return
     
     if batObj.GetHP() <= 0:
         return
     
-    curID = batObj.GetID()
-    buffMgr = batObj.GetBuffManager()
-    GameWorld.DebugLog("更新buff: curID=%s,buffCount=%s" % (curID, buffMgr.GetBuffCount()))
-    for index in range(buffMgr.GetBuffCount()):
-        buff = buffMgr.GetBuffByIndex(index)
-        curRemainTime = buff.GetRemainTime()
-        if not curRemainTime:
-            # 永久buff不处理
-            continue
-        buffID = buff.GetBuffID()
-        updRemainTime = curRemainTime - 1
-        GameWorld.DebugLog("    更新buff剩余回合数: buffID=%s,updRemainTime=%s" % (buffID, updRemainTime))
-        if updRemainTime > 0:
-            buff.SetRemainTime(curRemainTime - 1)
-            TurnBuff.SyncBuffRefresh(turnFight, batObj, buff)
-        else:
-            TurnBuff.SyncBuffDel(turnFight, batObj, buffID)
-            
-#    SetTimeline(gameObj, turnNum, 0)
-#    # 重置连击、反击数
-#    gameObj.SetDict(ChConfig.Def_Obj_Dict_TurnComboNum, 0)
-#    gameObj.SetDict(ChConfig.Def_Obj_Dict_TurnAtkBackNum, 0)
-#    gameObj.SetDict(ChConfig.Def_Obj_Dict_TurnParryNum, 0)
-#    gameObj.SetDict(ChConfig.Def_Obj_Dict_TurnMissNum, 0)
-#    gameObj.SetDict(ChConfig.Def_Obj_Dict_TurnAtkAddXPCount, 0)
-#    gameObj.SetDict(ChConfig.Def_Obj_Dict_TurnXPUseState, 0)
-#    gameObj.SetDict(ChConfig.Def_PlayerKey_AttrFaintCD, 0) # 击晕CD
-#    
-#    objType = gameObj.GetGameObjType()
-#    objID = gameObj.GetID()
-#    objName = GetObjName(gameObj)
-#    GameWorld.DebugLog("ObjPerTurnStart: 回合%s, %s objType-ID-HP(%s-%s-%s)" % (turnNum, objName, objType, objID, GameObj.GetHP(gameObj)))
-#    
-#    # 每回合开始减技能CD
-#    skillManager = gameObj.GetSkillManager()
-#    for i in range(skillManager.GetSkillCount()):
-#        skill = skillManager.GetSkillByIndex(i)
-#        remainTime = skill.GetRemainTime()
-#        if not remainTime:
-#            continue
-#        skillID = skill.GetSkillID()
-#        updRemainTime = max(0, remainTime - ChConfig.Def_PerTurnTick)
-#        skill.SetRemainTime(updRemainTime)
-#        GameWorld.DebugLog("    skillID=%s,remainTime=%s,updRemainTime=%s" % (skillID, remainTime, updRemainTime))
-#        
-#    # 刷新定时处理的buff效果
-#    SkillShell.ProcessPersistBuff(gameObj, tick)
-#    
-#    PassiveBuffEffMng.OnPassiveSkillTrigger(gameObj, tagObj, None, ChConfig.TriggerType_TurnNum, tick)
-#    
-#    __logGameObjAttr(gameObj)
+    turnFight.ResetOneActionUseSkillCnt()
+    TurnPassive.OnTriggerPassiveEffect(turnFight, batObj, ChConfig.TriggerWay_BigTurnStart)
+    return
+
+def TurnFightPerTurnBigEnd(turnFight, batObj, turnNum):
+    ## 大回合结束时
+    if not batObj:
+        return
+    
+    if batObj.GetHP() <= 0:
+        return
+    
+    TurnPassive.OnTriggerPassiveEffect(turnFight, batObj, ChConfig.TriggerWay_BigTurnEnd)
+    return
+
+def TurnFightHeroTurnStart(turnFight, batObj, turnNum):
+    ## 武将回合开始时,不能行动也要触发
+    if not batObj:
+        return
+    
+    if batObj.GetHP() <= 0:
+        return
+    
+    GameWorld.DebugLog("---[武将回合开始时] : curID=%s,curHP=%s/%s" % (batObj.GetID(), batObj.GetHP(), batObj.GetMaxHP()))
+    turnFight.ResetOneActionUseSkillCnt()
+    TurnPassive.OnTriggerPassiveEffect(turnFight, batObj, ChConfig.TriggerWay_HeroTurnStart)
     return
 
 def AddTurnObjCureHP(curObj, srcObj, addValue, cureHP, skillID=0):
@@ -1166,19 +1491,20 @@
         
     return
 
-def AddTurnObjHurtValue(curBatObj, tagBatObj, hurtValue, lostHP, curSkill=None, isBounce=False):
+def AddTurnObjHurtValue(curBatObj, tagBatObj, hurtValue, lostHP, skillID=0, isBounce=False):
     ## 回合对象添加伤害值
     # @param isBounce: 是否反弹伤害
+    if hurtValue <= 0:
+        return
     curID = curBatObj.GetID()
     tagID = tagBatObj.GetID()
-    skillID = curSkill.GetSkillID() if curSkill else 0
     if curID != tagID:
         updStatValue = curBatObj.StatHurtValue(hurtValue)
         GameWorld.DebugLog("        统计伤血: curID=%s,tagID=%s,skillID=%s,hurtValue=%s,lostHP=%s,updStatValue=%s,tagHP=%s,isBounce=%s" 
                        % (curID, tagID, skillID, hurtValue, lostHP, updStatValue, tagBatObj.GetHP(), isBounce))
         
         if tagBatObj:
-            updStatValue = tagBatObj.StatDefValue(lostHP)
+            updStatValue = tagBatObj.StatDefValue(hurtValue)
             GameWorld.DebugLog("        统计承伤: curID=%s,tagID=%s,skillID=%s,hurtValue=%s,lostHP=%s,updStatValue=%s,curHP=%s,isBounce=%s" 
                            % (tagID, curID, skillID, hurtValue, lostHP, updStatValue, tagBatObj.GetHP(), isBounce))
             
@@ -1202,16 +1528,18 @@
     objName = GetObjName(curBatObj)
     
     # 是否可行动状态判断
-    canAction = True
-    
+    canAction = curBatObj.CanAction()
     if not canAction:
         GameWorld.DebugLog("★回合%s %s 当前状态不可行动!" % (turnNum, objName))
         return
     
-    GameWorld.DebugLog("★回合%s %s 行动 : curHP=%s" % (turnNum, objName, curHP))
+    atk = curBatObj.GetAtk()
+    curXP = curBatObj.GetXP()
+    GameWorld.DebugLog("★回合%s %s 行动 : atk=%s,curHP=%s/%s,curXP=%s" % (turnNum, objName, atk, curHP, curBatObj.GetMaxHP(), curXP))
     turnFight.syncObjAction(turnNum, objID)
     
-    curXP = curBatObj.GetXP()
+    TurnPassive.OnTriggerPassiveEffect(turnFight, curBatObj, ChConfig.TriggerWay_HeroActionStart)
+    
     xpMax = IpyGameDataPY.GetFuncCfg("AngerXP", 2) 
     skillManager = curBatObj.GetSkillManager()
     useSkillList = []
@@ -1220,8 +1548,8 @@
         useSkill = skillManager.GetSkillByIndex(index)
         if not useSkill:
             continue
-        if useSkill.GetFuncType() in [ChConfig.Def_SkillFuncType_AtkbackSkill]:
-            #基础普攻不能主动释放,目前仅用于反击
+        if useSkill.GetFuncType() not in [ChConfig.Def_SkillFuncType_TurnNormaSkill, ChConfig.Def_SkillFuncType_AngerSkill]:
+            #只能主动释放普攻或怒技
             continue
         #被动技能无法使用
         if SkillCommon.isPassiveSkill(useSkill):
@@ -1234,6 +1562,9 @@
         if SkillCommon.isAngerSkill(useSkill):
             if curXP < xpMax:
                 continue
+            if curBatObj.CheckInState(ChConfig.BatObjState_Sneer):
+                GameWorld.DebugLog("嘲讽状态下,无法主动释放怒技!") # 可被动释放怒技,如怒技追击
+                continue
             useCnt = -1 # xp技能优先释放
         else:
             useCnt = curBatObj.GetSkillUseCnt(skillID)
@@ -1245,215 +1576,28 @@
     for useInfo in useSkillList:
         useSkill = useInfo[-1]
         if TurnSkill.OnUseSkill(turnFight, curBatObj, useSkill):
-            return True
+            break
         
-    return
-
-def DoAttack(curBatObj, tagBatObj, tick, turnBattleType=ChConfig.TurnBattleType_Normal, useSkill=None):
-#    curID = curBatObj.GetID()
-#    npcID = curBatObj.GetNPCID()
-#    objName = GetObjName(curBatObj)
-#    GameWorld.DebugLog("    ● %s DoAttack: curID=%s,,turnBattleType=%s" % (objName, curID, turnBattleType))
-#    
-#    atkOK = False
-#    curBatObj.SetDict(ChConfig.Def_Obj_Dict_TurnBattleType, turnBattleType)
-#    
-#    if turnBattleType == ChConfig.TurnBattleType_AtkBack:
-#        if not tagBatObj:
-#            return
-#        skillManager = curBatObj.GetSkillManager()
-#        for index in range(0, skillManager.GetSkillCount()):
-#            skill = skillManager.GetSkillByIndex(index)
-#            if not skill:
-#                continue
-#            if skill.GetFuncType() == ChConfig.Def_SkillFuncType_AtkbackSkill:
-#                useSkill = skill
-#                break
-#        atkOK = SkillShell.DoLogic_UseSkill(curBatObj, tagBatObj, useSkill, tick)
-#    elif turnBattleType == ChConfig.TurnBattleType_Combo:
-#        if not tagBatObj:
-#            return
-#        atkOK = SkillShell.DoLogic_UseSkill(curBatObj, tagBatObj, useSkill, tick)
-#    else:
-#        curXP = GameObj.GetXP(curBatObj)
-#        xpMax = IpyGameDataPY.GetFuncCfg("AngerXP", 2) 
-#        skillManager = curBatObj.GetSkillManager()
-#        useSkillList = []
-#        #GameWorld.DebugLog('skillCount=%s' % skillManager.GetSkillCount(), npcID)
-#        for index in range(0, skillManager.GetSkillCount()):
-#            useSkill = skillManager.GetSkillByIndex(index)
-#            if not useSkill:
-#                continue
-#            if useSkill.GetFuncType() in [ChConfig.Def_SkillFuncType_AtkbackSkill]:
-#                #基础普攻不能主动释放,目前仅用于反击
-#                continue
-#            #被动技能无法使用
-#            if SkillCommon.isPassiveSkill(useSkill):
-#                continue
-#            #还在冷却时间内无法释放
-#            if useSkill.GetRemainTime():
-#                continue
-#            skillID = useSkill.GetSkillID()
-#            # 常规攻击优先xp
-#            if SkillCommon.isAngerSkill(useSkill) and turnBattleType == ChConfig.TurnBattleType_Normal:
-#                if curXP < xpMax:
-#                    continue
-#                useCnt = -1 # xp技能优先释放
-#            else:
-#                useCnt = curBatObj.GetDictByKey(ChConfig.Def_NPC_Dict_SkillUseCnt % skillID) # 该技能已使用次数
-#            useSkillList.append([useCnt, index, skillID, useSkill])
-#            
-#        useSkillList.sort() # 按使用次数优先升序排,使用次数低的优先判断使用
-#        #GameWorld.DebugLog('    技能使用顺序 = useSkillList%s' % str(useSkillList), npcID)
-#        
-#        for useInfo in useSkillList:
-#            useSkill = useInfo[-1]
-#            #skillID = useSkill.GetSkillID()
-#            atkOK, tagBatObj = OnUseSkill(curBatObj, useSkill, tick)
-#            if atkOK:
-#                break
-#            
-#    curBatObj.SetDict(ChConfig.Def_Obj_Dict_TurnBattleType, 0) # 无论攻击成功与否都重置战斗类型
-#    
-#    tagID = 0
-#    tagHP = 0
-#    if tagObj:
-#        tagID = tagObj.GetID()
-#        tagHP = GameObj.GetHP(tagObj)
-#    GameWorld.DebugLog('        atkOK=%s,tagID=%s,tagHP=%s' % (atkOK, tagID, tagHP), npcID)
-#    if not atkOK:
-#        return
-#    
-#    if not tagObj:
-#        return
-#    
-#    if GameObj.GetFaction(curNPC) == GameObj.GetFaction(tagObj):
-#        # 同阵营直接返回,不处理连击、反击
-#        return True
-#    
-#    curHP = GameObj.GetHP(curNPC)
-#    tagHP = GameObj.GetHP(tagObj)
-#    tagID = tagObj.GetID()
-#    GameWorld.DebugLog("            curID-HP=(%s-%s),tagID-HP=(%s-%s)" % (curID, curHP, tagID, tagHP))
-#    if tagHP <= 0 or curHP <= 0:
-#        return True
-#    
-#    # 反击,反击可打断连击,所以优先判断
-#    if CanAtkBack(curNPC, tagObj):
-#        DoAttack(tagObj, curNPC, tick, ChConfig.TurnBattleType_AtkBack)
-#        return True
-#    
-#    # 连击
-#    if CanCombo(curNPC, tagObj):
-#        DoAttack(curNPC, tagObj, tick, ChConfig.TurnBattleType_Combo, useSkill)
-#        
+    TurnPassive.OnTriggerPassiveEffect(turnFight, curBatObj, ChConfig.TriggerWay_HeroActionEnd)
     return True
-
-def CanAtkBack(atkObj, defObj):
-    ## 可否反击
-    posNum = atkObj.GetDictByKey(ChConfig.Def_Obj_Dict_TurnFightPosInfo) % 100
-    if posNum <= 0:
-        GameWorld.DebugLog("            被非主战单位攻击时无法触发反击: atkID=%s,posNum=%s" % (atkObj.GetID(), posNum))
-        return False
-    atkDistType = AttackCommon.GetAtkDistType(defObj)
-    if atkDistType == ChConfig.AtkDistType_Long:
-        if not IpyGameDataPY.GetFuncCfg("ParryCfg", 2):
-            GameWorld.DebugLog("            远程单位不可反击: defID=%s" % (defObj.GetID()))
-            return False
-    defAtkBackRate = GameObj.GetAtkBackRate(defObj) # 防方反击率
-    atkBackNum = defObj.GetDictByKey(ChConfig.Def_Obj_Dict_TurnAtkBackNum) # 已反击次数
-    if atkBackNum > 10:
-        # 内置最高反击数防范
-        return False
-    if atkBackNum > 0:
-        validPerList = IpyGameDataPY.GetFuncEvalCfg("TurnFight", 4)
-        vaildPer = validPerList[atkBackNum - 1] if len(validPerList) >= atkBackNum else 0
-        defAtkBackRate = int(defAtkBackRate * vaildPer / 100.0)
-        
-    atkAtkBackDefRate = GameObj.GetAtkBackDefRate(atkObj) # 攻方抵抗反击率
-    atkBackRate = max(0, defAtkBackRate - atkAtkBackDefRate)
-    if atkBackRate <= 0 or not GameWorld.CanHappen(atkBackRate):
-        GameWorld.DebugLog("            无法反击: defID=%s,atkBackNum=%s,atkBackRate=%s=(defAtkBackRate=%s - atkAtkBackDefRate=%s)" 
-                           % (defObj.GetID(), atkBackNum, atkBackRate, defAtkBackRate, atkAtkBackDefRate))
-        return False
-    GameWorld.DebugLog("            可以反击: defID=%s,atkBackNum=%s,atkBackRate=%s=(defAtkBackRate=%s - atkAtkBackDefRate=%s)" 
-                       % (defObj.GetID(), atkBackNum, atkBackRate, defAtkBackRate, atkAtkBackDefRate))
-    return True
-
-def CanCombo(atkObj, defObj):
-    ## 可否连击
-    
-    atkComboRate = GameObj.GetComboRate(atkObj) # 攻方连击率
-    comboNum = atkObj.GetDictByKey(ChConfig.Def_Obj_Dict_TurnComboNum) # 已连击次数
-    if comboNum > 10:
-        # 内置最高连击数防范
-        return False
-    if comboNum > 0:
-        validPerList = IpyGameDataPY.GetFuncEvalCfg("TurnFight", 3)
-        vaildPer = validPerList[comboNum - 1] if len(validPerList) >= comboNum else 0
-        atkComboRate = int(atkComboRate * vaildPer / 100.0)
-        
-    defComboReduce = GameObj.GetComboDefRate(defObj) # 防方抵抗连击率
-    comboRate = max(0, atkComboRate - defComboReduce)
-    if comboRate <= 0 or not GameWorld.CanHappen(comboRate):
-        GameWorld.DebugLog("            无法连击: atkID=%s,comboNum=%s,comboRate=%s=(atkComboRate=%s - defComboReduce=%s)" 
-                           % (atkObj.GetID(), comboNum, comboRate, atkComboRate, defComboReduce))
-        return False
-    GameWorld.DebugLog("            可以连击: atkID=%s,comboNum=%s,comboRate=%s=(atkComboRate=%s - defComboReduce=%s)" 
-                       % (atkObj.GetID(), comboNum, comboRate, atkComboRate, defComboReduce))
-    return True
-
-#def GetEnemyObj(curNPC, ruleType=0):
-#    ## 获取一个敌对单位,针对所有敌对阵容主战单位,优先选择对位阵容单位,仅限活着的单位
-#    # @param ruleType: 选择规则,默认0任意,1-最低血量;2-最高血量
-#    objID = curNPC.GetID()
-#    faction = GameObj.GetFaction(curNPC)
-#    posInfo = curNPC.GetDictByKey(ChConfig.Def_Obj_Dict_TurnFightPosInfo)
-#    num = posInfo / 100 # 阵容编号
-#    posNum = posInfo % 100 # 所在阵容站位
-#    turnFight = GetTurnFightMgr().getTurnFight(curNPC.GetTFGUID())
-#    if not turnFight:
-#        return
-#    
-#    tagBatFaction = turnFight.getBatFaction(Def_FactionB if faction == Def_FactionA else Def_FactionA)
-#    
-#    tagLineupNumList = [num] # 目标阵容选择顺序,优先对位阵容,再其他阵容
-#    if num in tagBatFaction.lineupDict:
-#        tagLineupNumList = [num]
-#    for tagNum in tagBatFaction.lineupDict.keys():
-#        if tagNum not in tagLineupNumList:
-#            tagLineupNumList.append(tagNum)
-#            
-#    batObjMgr = BattleObj.GetBatObjMgr()
-#    for tagNum in tagLineupNumList:
-#        tagLineup = tagBatFaction.getBatlineup(tagNum)
-#        tagPosNumList = [posNum] # 优先对位位置,再其他位置按顺序遍历
-#        pNumList = tagLineup.posObjIDDict.keys()
-#        pNumList.sort()
-#        for pNum in pNumList:
-#            if pNum > 0 and pNum not in tagPosNumList:
-#                tagPosNumList.append(pNum)
-#        for pNum in tagPosNumList:
-#            batObj = batObjMgr.getBatObj(objID)
-#            if not batObj:
-#                continue
-#            if batObj.GetHP( )<= 0:
-#                continue
-#            return batObj
-#    return
 
 def SetObjKilled(turnFight, gameObj, killer=None, useSkill=None):
     objID = gameObj.GetID()
-    GameWorld.DebugLog("        %s 回合战斗主体被击杀: curID=%s" % (GetObjName(gameObj), objID))
-    gameObj.SetHP(0)
     killerObjID = killer.GetID() if killer else 0
     skillID = useSkill.GetSkillID() if useSkill else 0
+    GameWorld.DebugLog("        %s 回合战斗主体被击杀: curID=%s,killerObjID=%s,skillID=%s" % (GetObjName(gameObj), objID, killerObjID, skillID))
+    gameObj.SetDead()
     
     clientPack = ObjPool.GetPoolMgr().acquire(ChPyNetSendPack.tagMCTurnFightObjDead)
     clientPack.ObjID = objID
     clientPack.KillerObjID = killerObjID
     clientPack.SkillID = skillID
-    turnFight.addBatPack(clientPack)        
+    turnFight.addBatPack(clientPack)
+    
+    curPlayer = turnFight.curPlayer
+    # 暂时只算主线小怪
+    if curPlayer and turnFight.mapID == ChConfig.Def_FBMapID_Main and gameObj.GetFaction() != ChConfig.Def_FactionA:
+        PlayerTask.AddTaskValue(curPlayer, ChConfig.TaskType_KillNPC, 1)    
     return True
 
 def OnTurnAllOver(guid):
@@ -1485,7 +1629,8 @@
     
     turnFight.costTime = time.time() - turnFight.startTime
     winFaction = turnFight.winFaction
-    GameWorld.DebugLog("--- 战斗结束处理 --- %s, winFaction=%s, costTime=%ss" % (guid, winFaction, turnFight.costTime))
+    turnFight.isWin = (winFaction == ChConfig.Def_FactionA)
+    GameWorld.DebugLog("--- 战斗结束处理 ---, winFaction=%s, costTime=%ss, %s" % (winFaction, turnFight.costTime, guid))
     
     # 统计明细
     batObjMgr = BattleObj.GetBatObjMgr()
@@ -1511,132 +1656,19 @@
                 atkHurt = batObj.hurtStat
                 defHurt = batObj.defStat
                 cureHP = batObj.cureStat
-                GameWorld.DebugLog("    Pos:%s ID=%s-%s-%s,,HP=%s/%s, 输出=%s,承伤=%s,治疗=%s" 
+                GameWorld.DebugLog("    Pos:%s ID=%s,npcID=%s,heroID=%s,HP=%s/%s, 输出=%s,承伤=%s,治疗=%s" 
                                    % (posNum, objID, npcID, heroID, batObj.GetHP(), batObj.GetMaxHP(), atkHurt, defHurt, cureHP))
                 lineupStatInfo[str(posNum)] = {"ObjID":objID, "HeroID":heroID, "NPCID":npcID, "AtkHurt":atkHurt, "DefHurt":defHurt, "CureHP":cureHP}
                 
-    awardItemList = []
-    playerID = turnFight.playerID
-    if playerID:
-        curPlayer = turnFight.curPlayer
-        isWin = (winFaction == ChConfig.Def_FactionA)
-        # 主线小怪
-        if turnFight.mapID == ChConfig.Def_FBMapID_Main:
-            OnOver_MainLevel(curPlayer, isWin, awardItemList)
-        # 主线boss
-        elif turnFight.mapID == ChConfig.Def_FBMapID_MainBoss:
-            OnOver_MainLevelBoss(curPlayer, isWin, awardItemList)    
-        else:
-            pass
-        
-    overMsg = {"winFaction":winFaction, "statInfo":statInfo, FBCommon.Over_itemInfo:FBCommon.GetJsonItemList(awardItemList)}
+    overMsg = {"winFaction":winFaction, "statInfo":statInfo}
+    curPlayer = turnFight.curPlayer
+    mapID = turnFight.mapID
+    funcLineID = turnFight.funcLineID
+    
+    FBLogic.OnTurnFightOver(curPlayer, turnFight, mapID, funcLineID, overMsg)
+    
     turnFight.syncState(FightState_Award, overMsg)
-    
-    # 除了主线外,其余战斗结束后均直接删除
-    if not turnFight.playerID:
-        tfMgr.delTurnFight(guid)
     return
-
-def OnOver_MainLevel(curPlayer, isWin, awardItemList):
-    ## 战斗结束额外处理 - 主线关卡
-    if not curPlayer:
-        return
-    playerID = curPlayer.GetPlayerID()
-    mainFightMgr = GetMainFightMgr(curPlayer)
-    mainFightMgr.nextTeam = False
-    chapterID, levelNum, wave = PlayerControl.GetMainLevelNowInfo(curPlayer)
-    
-    if not isWin:
-        nextWave = max(1, wave - 1)
-        nowValue = PlayerControl.SetMainLevelNowInfo(curPlayer, chapterID, levelNum, nextWave)
-        GameWorld.DebugLog("主线小怪战斗失败,降一波! chapterID=%s,levelNum=%s,wave=%s,nextWave=%s,nowValue=%s" 
-                           % (chapterID, levelNum, wave, nextWave, nowValue), playerID)
-        return
-    
-    if mainFightMgr.teamNum < mainFightMgr.teamMax:
-        mainFightMgr.nextTeam = True
-        GameWorld.DebugLog("主线小怪战斗胜利,下一小队! chapterID=%s,levelNum=%s,wave=%s,teamNum=%s/%s" 
-                           % (chapterID, levelNum, wave, mainFightMgr.teamNum, mainFightMgr.teamMax), playerID)
-        return
-    
-    # 获胜过波
-    if wave < mainFightMgr.waveMax:
-        nextWave = min(mainFightMgr.waveMax, wave + 1)
-        nowValue = PlayerControl.SetMainLevelNowInfo(curPlayer, chapterID, levelNum, nextWave)
-        GameWorld.DebugLog("主线小怪波战斗胜利,下一波! chapterID=%s,levelNum=%s,wave=%s,nextWave=%s,nowValue=%s" 
-                           % (chapterID, levelNum, wave, nextWave, nowValue), playerID)
-    else:
-        GameWorld.DebugLog("主线小怪波战斗胜利,最后一波循环刷! chapterID=%s,levelNum=%s,wave=%s" % (chapterID, levelNum, wave), playerID)
-        
-    # 小怪可能会退波,所以只在有超过已过关卡进度时才更新值
-    hisPassValue = PlayerControl.GetMainLevelPassValue(curPlayer)
-    curPassValue = PlayerControl.ComMainLevelValue(chapterID, levelNum, wave)
-    if curPassValue > hisPassValue:
-        GameWorld.DebugLog("更新当前过关进度! curPassValue=%s,hisPassValue=%s" % (curPassValue, hisPassValue), playerID)
-        PlayerControl.SetMainLevelPassValue(curPlayer, curPassValue)
-    else:
-        GameWorld.DebugLog("未超过当前过关进度,不更新! curPassValue=%s <= hisPassValue=%s" % (curPassValue, hisPassValue), playerID)
-        
-    return
-
-def OnOver_MainLevelBoss(curPlayer, isWin, awardItemList):
-    ## 战斗结束额外处理 - 主线关卡boss
-    if not curPlayer:
-        return
-    playerID = curPlayer.GetPlayerID()
-    mainFightMgr = GetMainFightMgr(curPlayer)
-    mainFightMgr.nextTeam = False
-    chapterID, levelNum, _ = PlayerControl.GetMainLevelPassInfo(curPlayer)
-    if not isWin:
-        nowValue = PlayerControl.GetMainLevelNowValue(curPlayer)
-        GameWorld.DebugLog("主线boss战斗失败!保持当前刷怪波进度不变! nowValue=%s" % nowValue, playerID)
-        return
-    
-    if mainFightMgr.teamNum < mainFightMgr.teamMax:
-        mainFightMgr.nextTeam = True
-        GameWorld.DebugLog("主线boss小队战斗胜利,下一小队! chapterID=%s,levelNum=%s,teamNum=%s/%s" 
-                           % (chapterID, levelNum, mainFightMgr.teamNum, mainFightMgr.teamMax), playerID)
-        return
-    
-    isAllPass = False # 是否通关
-    if IpyGameDataPY.GetIpyGameDataNotLog("MainLevel", chapterID, levelNum + 1):
-        nextChapterID, nextLevelNum = chapterID, levelNum + 1
-        GameWorld.DebugLog("主线boss波战斗胜利!下一关! chapterID=%s,levelNum=%s,nextLevelNum=%s" 
-                           % (chapterID, levelNum, nextLevelNum), playerID)
-    elif IpyGameDataPY.GetIpyGameDataNotLog("MainLevel", chapterID + 1, 1):
-        nextChapterID, nextLevelNum = chapterID + 1, 1
-        GameWorld.DebugLog("主线boss波战斗胜利!下一章! chapterID=%s,levelNum=%s,nextChapterID=%s,nextLevelNum=%s" 
-                           % (chapterID, levelNum, nextChapterID, nextLevelNum), playerID)
-    else:
-        # 已通关的暂时先保持不变
-        # 注意防范最后一关奖励重复获得
-        nextChapterID, nextLevelNum = chapterID + 1, 1
-        GameWorld.DebugLog("主线boss波战斗胜利!已通关! chapterID=%s,levelNum=%s,nextChapterID=%s,nextLevelNum=%s" 
-                            % (chapterID, levelNum, nextChapterID, nextLevelNum), playerID)
-        isAllPass = True
-        
-    updPassValue = PlayerControl.SetMainLevelPassInfo(curPlayer, nextChapterID, nextLevelNum, 0)
-    if isAllPass:
-        # 已通关的刷怪进度保持不变
-        updNowValue = PlayerControl.GetMainLevelNowValue(curPlayer)
-        GameWorld.DebugLog("已通关的刷怪进度保持不变: updNowValue=%s" % updNowValue, playerID)
-    else:
-        updNowValue = PlayerControl.SetMainLevelNowInfo(curPlayer, nextChapterID, nextLevelNum, 1)
-        GameWorld.DebugLog("为通关的刷怪进度设置为下一关的第1波: updNowValue=%s" % updNowValue, playerID)
-    GameWorld.DebugLog("updPassValue=%s,updNowValue=%s" % (updPassValue, updNowValue), playerID)
-    
-    # 发放过关奖励
-    levelIpyData = IpyGameDataPY.GetIpyGameData("MainLevel", chapterID, levelNum)
-    if not levelIpyData:
-        return
-    itemList = levelIpyData.GetAwardItemList()
-    GameWorld.DebugLog("过关奖励: chapterID=%s,levelNum=%s,itemList=%s" % (chapterID, levelNum, itemList), playerID)
-    
-    ItemControler.GivePlayerItemOrMail(curPlayer, itemList, event=["MainLevelBoss", False, {}], isNotifyAward=False)
-    
-    awardItemList += itemList
-    return
-
 
 #// B4 14 查看战报 #tagCSTurnFightReportView
 #
@@ -1646,4 +1678,27 @@
 #    char        GUID[40];    //战报guid
 #};
 def OnTurnFightReportView(index, clientData, tick):
+    curPlayer = GameWorld.GetPlayerManager().GetPlayerByIndex(index)
+    guid = clientData.GUID
+    
+    lastBatBufferInfo = PlayerOnline.GetOnlinePlayer(curPlayer).GetLastBatBuffer()
+    if lastBatBufferInfo and len(lastBatBufferInfo) == 2 and guid == lastBatBufferInfo[0]:
+        guid, reprot = lastBatBufferInfo
+        SyncTurnFightReport(curPlayer, guid, reprot)
+        return
+    
+    # 其他战报,一般是入库存储的,待扩展
+    
+    # 战报已过期
+    PlayerControl.NotifyCode(curPlayer, "FightReportExpired")
     return
+
+def SyncTurnFightReport(curPlayer, guid, reprot):
+    ## 通知完整战报
+    clientPack = ChPyNetSendPack.tagSCTurnFightReport()
+    clientPack.GUID = guid
+    clientPack.Report = reprot
+    clientPack.Len = len(clientPack.Report)
+    NetPackCommon.SendFakePack(curPlayer, clientPack)
+    return
+    

--
Gitblit v1.8.0