ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Attack/TurnAttack.py
@@ -21,30 +21,32 @@
import ChConfig
import PlayerTask
import PlayerActivity
import PlayerViewCache
import ChPyNetSendPack
import NetPackCommon
import PlayerControl
import GameWorld
import FBCommon
import IpyGameDataPY
import PlayerOnline
import NPCCommon
import ShareDefine
import PyGameData
import ItemControler
import SkillCommon
import BattleObj
import TurnPassive
import TurnSkill
import TurnBuff
import FBCommon
import CommFunc
import ObjPool
import FBLogic
import random
import time
import json
TimelineSet = 10000 # 单回合最大时间轴
PosNumMax = 10 # 最大站位编号
PosNumMax = 7 # 最大站位编号
ActionNumStart = -1 # 起始行动位置编号,一般是从1开始,如果有加主公、红颜等则扣除相应位置值,如从0或-1开始
# 回合战斗流程状态
@@ -55,7 +57,8 @@
FightState_FightEnd, # 3 战斗结束
FightState_Award, # 4 结算奖励
FightState_Over, # 5 结束状态,无特殊意义,仅代表所有处理结束了,与Start对应
) = range(6)
FightState_Rest, # 6 结束并休息,一般是前端主动退出并休息的
) = range(7)
    
class BatLineup():
    ## 战斗阵容
@@ -71,6 +74,11 @@
        self.lingshouObjIDDict = {} # 灵兽战斗单位 {位置编号:batObjID, ...}
        self.beautyObjIDDict = {} # 红颜战斗单位 {位置编号:batObjID, ...}
        self.actionNum = ActionNumStart # 行动位置,从1开始
        self.totalHurt = 0 # 阵容总输出
        #特殊
        self.bossID = 0
        self.bossPosView = 0
        return
    
    def getPlayerID(self): return self.turnFight.playerID # 发起的玩家ID
@@ -84,6 +92,8 @@
        self.ownerID = lineupInfo.get("PlayerID", 0) # 阵容所属的玩家ID
        self.shapeType = lineupInfo.get("ShapeType", 0)
        self.fightPower = lineupInfo.get("FightPower", 0)
        self.bossID = lineupInfo.get("BossID", 0)
        self.bossPosView = lineupInfo.get("BossPosView", 0)
        SummonLineupObjs(self, self.faction, self.num, lineupInfo, self.getPlayerID())
        return
    
@@ -100,7 +110,21 @@
        self.lingshouObjIDDict = {}
        self.beautyObjIDDict = {}
        self.fightPower = 0
        self.totalHurt = 0
        return
    def getDeadObjCnt(self):
        ## 获取本阵容目前死亡队员数
        deadCnt = 0
        batObjMgr = BattleObj.GetBatObjMgr()
        for objID in self.posObjIDDict.values():
            batObj = batObjMgr.getBatObj(objID)
            if not batObj:
                continue
            if batObj.IsAlive():
                continue
            deadCnt += 1
        return deadCnt
    
class BatFaction():
    ## 战斗阵营
@@ -109,6 +133,7 @@
        self.turnFight = turnFight # TurnFight
        self.faction = faction
        self.lineupDict = {} # 该阵营所有阵容信息 {编号:BatLineup, ...}
        self.totalHurt = 0 # 阵营总输出
        return
    
    def getBatlineup(self, num=1):
@@ -120,6 +145,8 @@
            lineup = BatLineup(self.faction, num, self.turnFight)
            self.lineupDict[num] = lineup
        return lineup
    def getTotalHurt(self): return self.totalHurt # 阵营总输出
    
    def clearLineups(self):
        ## 清除所有战斗阵容
@@ -147,41 +174,90 @@
        self.state = -1 # -1 代表未战斗
        self.turnNum = 1 # 当前第x回合,默认第1回合开始
        self.turnMax = 15 # 最大回合数
        self.turnNumStart = 0 # 已处理第x回合开始
        self.enterLogic = False # 是否已执行进场逻辑
        self.winFaction = 0 # 本场战斗结束标记,获胜阵营,为0时代表未结束,所有小队打完或失败才有结果,0-未结束,>0-获胜的阵营
        self.isWin = False
        self.batBuffer = "" # 战报buffer,战报暂时只保留最后一个小队的
        self.isNeedReport = isNeedReport # 是否需要战报
        self.msgDict = {} # 扩展信息字典,一般由MapID绑定的功能决定信息内容  {k:v, ...}
        self._kvDict = {} # 自定义信息字典,不会被重置  {k:v, ...}
        
        self.factionDict = {} # 战斗阵营 {faction:BatFaction, ...},一般是只有两个阵营,faction为1或2,每个阵营支持多个阵容
        self.actionSortList = [] # 阵容行动顺序 [[faction, num], ...]
        self.actionIndex = 0 # 行动顺序索引
        self.timeline = 0 # 时间轴节点  turnNum*1000+actionIndex*100++actionNum
        self.actionIndex = 0 # 行动顺序索引
        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._kvDict = {}
        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 isFBMap(self):
        ## 是否副本地图中,非主线的均视为副本
        return self.mapID not in [ChConfig.Def_FBMapID_Main, ChConfig.Def_FBMapID_MainBoss]
    def GetDictByKey(self, key): return self._kvDict.get(key, 0)
    def SetDict(self, key, value): self._kvDict[key] = value
    #def setPVPTeam(self):
    #    return
    def nextTurnFight(self, msgDict={}):
        ## 一般用于玩家发起的战斗,在需要保留玩家阵容属性及状态的情况下,重置回合进入下一场战斗
        self.turnNum = 1
        self.turnNumStart = 0
        self.enterLogic = False
        self.winFaction = 0
        self.batBuffer = "" # 战报buffer
        self.isWin = False
        self.msgDict.update(msgDict)
        self.timeline = 0
        self.startTime = time.time()
        self.costTime = 0
        ResetByNextTeam(self)
        return
    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):
        ## 设置阵营阵容
@@ -215,21 +291,6 @@
        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
@@ -272,9 +333,9 @@
            
        return 0
    
    def exitFight(self):
    def exitFight(self, rest=False):
        ## 退出战斗
        self.syncState(FightState_Over)
        self.syncState(FightState_Over if not rest else FightState_Rest)
        for batFaction in self.factionDict.values():
            batFaction.clearLineups()
        self.state = -1
@@ -284,7 +345,7 @@
        ## 准备就绪,开始战斗
        self.state = FightState_Start
        self.turnNum = 1
        self.timeline = self.getTurnNumStartTimelin(self.turnNum)
        self.turnNumStart = 0
        self.syncInit()
        return
    
@@ -329,7 +390,10 @@
                    tfObj.MaxHP = batObj.GetMaxHP() % ChConfig.Def_PerPointValue
                    tfObj.MaxHPEx = batObj.GetMaxHP() / ChConfig.Def_PerPointValue
                    tfObj.LV = batObj.GetLV()
                    tfObj.PosNum = posNum
                    if batLineup.bossPosView and batLineup.bossID == batObj.GetNPCID():
                        tfObj.PosNum = batLineup.bossPosView
                    else:
                        tfObj.PosNum = posNum
                    tfObj.AngreXP = batObj.GetXP()
                    tfLineup.ObjList.append(tfObj)
                tfLineup.ObjCnt = len(tfLineup.ObjList)
@@ -338,6 +402,11 @@
            clientPack.FactionList.append(tfFaction)
        clientPack.FactionCnt = len(clientPack.FactionList)
        self.addBatPack(clientPack)
        return
    def syncHelp(self, msgDict):
        ## 通知帮助信息,一般是副本用
        self.syncState(self.state, msgDict)
        return
    
    def syncState(self, state, msgDict={}):
@@ -370,25 +439,40 @@
            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():
    ## 回合战斗管理器
    
    def __init__(self):
        self.lastRequestTick = 0
        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
    
@@ -398,6 +482,7 @@
            return
        turnFight.exitFight()
        self.turnFightDict.pop(guid, None)
        ObjPool.GetPoolMgr().release(turnFight)
        return
    
    def getTurnFight(self, guid):
@@ -426,12 +511,6 @@
        self.levelNum = 0 # 关卡编号
        self.waveMax = 6 # 本关最大波数,每波有多个小队,每个小队即为一张战斗 TurnFight
        self.wave = 0 # 当前刷怪波,注意不是玩家当前进度波,比如被击杀会回退一波
        self.teamNum = 1 # 当前小队
        self.teamMax = 1 # 当前波最大小队,某场战斗可能包含多个小队,所有小队混流击杀完才算过了本波
        self.nextTeam = False # 下次前端请求战斗是否是下一小队,否则都是重新刷新当前进度怪
        self.waveLineupList = [] # 小队列表
        self.strongerLV = 0
        self.difficulty = 0
        self.turnFight = GetTurnFightMgr().addTurnFight(ChConfig.Def_FBMapID_Main, 0, playerID)
        return
    
@@ -454,10 +533,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 lineup.IsEmpty():
        GameWorld.DebugLog("玩家没有目标阵容默认取主阵容! lineupID=%s" % lineupID)
        lineup = olPlayer.GetLineup(ShareDefine.Lineup_Main)
    return lineup
def GetPlayerLineupInfo(curPlayer, lineupID):
    ## 获取玩家阵容信息,可用于战斗或查看缓存,因为可能取玩家的缓存进行对战,所以统一使用json格式,前端通用
@@ -465,12 +564,13 @@
    # @return: 阵容全部信息json字典,前端通用格式
    
    playerID = curPlayer.GetPlayerID()
    lineup = PlayerOnline.GetOnlinePlayer(curPlayer).GetLineup(lineupID)
    lineup = GetPlayerLineup(curPlayer, lineupID)
    if lineup.IsEmpty():
        return {}
    
    lineupInfo = {"PlayerID":playerID, "FightPower":lineup.fightPower, "ShapeType":lineup.shapeType}
    heroDict = {}
    curPack = curPlayer.GetItemManager().GetPack(ShareDefine.rptHero)
    for posNum in lineup.lineupHeroDict.keys():
    for posNum in lineup.GetPosNumList():
        hero = lineup.GetLineupHero(posNum)
        heroID = hero.heroID
        itemIndex = hero.itemIndex
@@ -493,8 +593,11 @@
                                 "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, strongerLV=0, difficulty=0):
@@ -506,23 +609,25 @@
    ipyData = IpyGameDataPY.GetIpyGameData("NPCLineup", lineupID)
    if not ipyData:
        return {}
    bossID = ipyData.GetBossID()
    bossPosView = ipyData.GetBossPosView()
    
    heroDict = {}
    for posNum in range(1, 1 + 10):
    for posNum in range(1, 1 + 6):
        if not hasattr(ipyData, "GetPosNPCID%s" % posNum):
            break
        npcID = getattr(ipyData, "GetPosNPCID%s" % posNum)()
        if not npcID:
            continue
        battleDict = GetNPCBattleDict(npcID, strongerLV, difficulty)
        battleDict = GetNPCBattleDict(ipyData, npcID, strongerLV, difficulty)
        if not battleDict:
            continue
        heroDict[str(posNum)] = battleDict
        
    lineupInfo = {"NPCLineupID":lineupID, "Hero":heroDict}
    lineupInfo = {"NPCLineupID":lineupID, "Hero":heroDict, "BossID":bossID, "BossPosView":bossPosView}
    return lineupInfo
def GetNPCBattleDict(npcID, strongerLV=0, difficulty=0):
def GetNPCBattleDict(lineupIpyData, npcID, strongerLV=0, difficulty=0):
    ## 获取NPC战斗相关字典,支持成长NPC
    # @param strongerLV: 成长等级
    # @param difficulty: 难度系数
@@ -549,8 +654,21 @@
    else:
        heroID = 0
        skinID = 0
        skillIDList = [] + npcData.GetSkillIDList()
        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:
@@ -563,7 +681,10 @@
                       ChConfig.AttrID_ParryRate:npcData.GetParryRate(), ChConfig.AttrID_ParryRateDef:npcData.GetParryRateDef(), 
                       ChConfig.AttrID_SuckHPPer:npcData.GetSuckHPPer(), ChConfig.AttrID_SuckHPPerDef:npcData.GetSuckHPPerDef(), 
                       }
        batAttrDict.update(npcData.GetSpecAttrInfo())
    exAttrDict = npcData.GetSpecAttrInfo()
    for attrIDStr, attrValue in exAttrDict.items():
        attrID = int(attrIDStr)
        batAttrDict[attrID] = batAttrDict.get(attrID, 0) + attrValue
        
    battleDict = {"NPCID":npcID,
                  "HeroID":heroID,
@@ -582,7 +703,7 @@
    angerSkillID = heroIpyData.GetAngerSkillID()
    skillIDList = [normalSkillID, angerSkillID]
    
    breakIpyDataList = IpyGameDataPY.GetIpyGameDataList("HeroBreak", heroID)
    breakIpyDataList = IpyGameDataPY.GetIpyGameDataListNotLog("HeroBreak", heroID)
    if breakIpyDataList:
        for breakIpyData in breakIpyDataList:
            if breakIpyData.GetBreakLV() > breakLV:
@@ -591,7 +712,7 @@
            if skillID:
                skillIDList.append(skillID)
                
    awakeIpyDataList = IpyGameDataPY.GetIpyGameDataList("HeroAwake", heroID)
    awakeIpyDataList = IpyGameDataPY.GetIpyGameDataListNotLog("HeroAwake", heroID)
    if awakeIpyDataList:
        for awakeIpyData in awakeIpyDataList:
            if awakeIpyData.GetAwakeLV() > awakeLV:
@@ -640,17 +761,16 @@
        if not curPlayer:
            return
        
    tfGUID = batLineup.turnFight.guid
    turnFight = batLineup.turnFight
    tfGUID = turnFight.guid
    lineupPlayerID = lineupInfo.get("PlayerID", 0) # 阵容所属玩家ID
    heroDict = lineupInfo.get("Hero", {})
    
    batObjMgr = BattleObj.GetBatObjMgr()
    initXP = IpyGameDataPY.GetFuncCfg("AngerXP", 1)
    atkBackSkillIDList = IpyGameDataPY.GetFuncEvalCfg("ParryCfg", 2)
    for posNumKey, heroInfo in heroDict.items():
        posNum = int(posNumKey)
        
        atkBackSkillID = 0 # 反击技能ID
        fightPower = 0
        skillIDList = [] # 战斗对象可能改变属性或技能,重新创建,防止误修改来源值
        attrDict = {}
@@ -660,12 +780,15 @@
        heroID = heroInfo.get("HeroID", 0)
        skinID = heroInfo.get("SkinID", 0)
        lv = heroInfo.get("LV", 1)
        specialty, atkDistType, country, sex, job = 0, 1, 0, 1, 0
        heroIpyData = IpyGameDataPY.GetIpyGameData("Hero", heroID) if heroID else None
        if heroIpyData:
            atkDistType = heroIpyData.GetAtkDistType()
            objName = heroIpyData.GetName()
            specialty = heroIpyData.GetSpecialty()
            atkDistType = heroIpyData.GetAtkDistType()
            country = heroIpyData.GetCountry()
            sex = heroIpyData.GetSex()
            job = heroIpyData.GetJob()
            
        if lineupPlayerID:
            fightPower = heroInfo.get("FightPower", 0)
@@ -677,10 +800,7 @@
            if not npcDataEx:
                continue
            if not heroIpyData:
                atkDistType = npcDataEx.GetAtkDistType()
                objName = npcDataEx.GetNPCName()
                country = npcDataEx.GetCountry()
                sex = npcDataEx.GetSex()
                
        batObj = batObjMgr.addBatObj()
        if not batObj:
@@ -697,33 +817,254 @@
        batObj.SetFightPower(fightPower)
        batObj.SetLV(lv)
        batObj.SetAtkDistType(atkDistType)
        batObj.SetSpecialty(specialty)
        batObj.SetCountry(country)
        batObj.SetSex(sex)
        batObj.SetJob(job)
        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 %s,skill=%s" % (GetObjName(batObj), skillIDList))
        GameWorld.DebugLog("AddBatObj %s,skill=%s" % (GetObjName(batObj), skillManager.GetSkillIDList()))
        ResetObjSkill(batObj)
        if npcID:
            #副本指定NPC属性
            fbNPCInitAttrDict = FBLogic.GetFBNPCInitAttr(curPlayer, turnFight, batObj)
            if fbNPCInitAttrDict:
                GameWorld.DebugLog("副本指定NPC初始化属性: npcID=%s, %s" % (npcID, fbNPCInitAttrDict))
                attrDict = {str(k):v for k, v in fbNPCInitAttrDict.items()} # 统一格式
        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()))
        # 清除buff
        buffMgr = batObj.GetBuffManager()
        buffMgr.ClearBuff()
        # 重置技能
        ResetObjSkill(batObj)
        # 重刷属性、被动
        TurnBuff.RefreshBuffAttr(batObj)
        TurnPassive.RefreshPassive(batObj)
    return
def CheckFightCD(curPlayer, tick, selfKey):
    ## 是否战斗请求CD中
    # 所有玩家公共CD,待扩展
    tfMgr = GetTurnFightMgr()
    pubCD = IpyGameDataPY.GetFuncCfg("TurnFightCD", 1)
    if pubCD:
        if tfMgr.lastRequestTick and tick - tfMgr.lastRequestTick <= pubCD:
            GameWorld.DebugLog("回合制战斗请求服务器公共CD中!")
            PlayerControl.NotifyCode(curPlayer, "BattleCoolDown")
            return True
    # 个人CD
    selfCD = IpyGameDataPY.GetFuncCfg("TurnFightCD", 2)
    lastTick = curPlayer.GetDictByKey(selfKey)
    if selfCD and lastTick and tick - lastTick <= selfCD:
        GameWorld.DebugLog("回合制战斗请求CD中: %s" % selfKey)
        PlayerControl.NotifyCode(curPlayer, "BattleCoolDown")
        return True
    tfMgr.lastRequestTick = tick
    curPlayer.SetDict(selfKey, tick)
    return False
#// 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
    funcLineID = reqRet[1] if len(reqRet) > 1 else funcLineID
    fbIpyData = FBCommon.GetFBIpyData(mapID)
    fbLineIpyData = FBCommon.GetFBLineIpyData(mapID, funcLineID, False)
    if fbIpyData:
        if not fbLineIpyData:
            GameWorld.DebugLog("不存在该副本功能线路! mapID=%s,funcLineID=%s" % (mapID, funcLineID))
            return
        if FBCommon.CheckCanEnterFBComm(curPlayer, mapID, funcLineID, fbIpyData, fbLineIpyData) != ShareDefine.EntFBAskRet_OK:
            return
    # 攻防方所使用的阵容ID
    atkLineupID, defLineupID = FBLogic.GetFBPlayerLineupID(curPlayer, mapID, funcLineID)
    if atkLineupID not in ShareDefine.LineupList or defLineupID not in ShareDefine.LineupList:
        return
    if CheckFightCD(curPlayer, tick, "TurnFightReqTick"):
        return
    # 玩家
    if tagType == 1:
        if not OnTurnFightVSPlayer(curPlayer, mapID, funcLineID, atkLineupID, defLineupID, tagID):
            return
    # NPC
    else:
        npcLineupIDList, strongerLV, difficulty = [], 0, 0
        if fbLineIpyData:
            npcLineupIDList = fbLineIpyData.GetLineupIDList()
            strongerLV = fbLineIpyData.GetNPCLV()
            difficulty = fbLineIpyData.GetDifficulty()
        if not npcLineupIDList:
            ret = FBLogic.GetFBNPCLineupInfo(curPlayer, mapID, funcLineID)
            if not ret:
                return
            npcLineupIDList, strongerLV, difficulty = ret
        if not OnTurnFightVSNPC(curPlayer, mapID, funcLineID, atkLineupID, npcLineupIDList, strongerLV, difficulty):
            return
    return True
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()
        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 True
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 True
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):
@@ -738,15 +1079,13 @@
        __doSetFightPoint(curPlayer, reqValue)
        return
    
    GameWorld.DebugLog("主线战斗请求: reqType=%s" % reqType, curPlayer.GetPlayerID())
    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)
    elif reqType == 4:
        __doMainFight(curPlayer, tick)
    else:
@@ -762,7 +1101,7 @@
    mainFightMgr = GetMainFightMgr(curPlayer)
    turnFight = mainFightMgr.turnFight
    if turnFight:
        turnFight.exitFight()
        turnFight.exitFight(True)
    return
def __doSetFightPoint(curPlayer, fightPoint):
@@ -786,9 +1125,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):
@@ -817,12 +1153,10 @@
    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:
@@ -832,110 +1166,24 @@
    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
    mainFightMgr.strongerLV = strongerLV
    mainFightMgr.difficulty = difficulty
    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,strongerLV=%s,difficulty=%s"
                       % (chapterID, levelNum, wave, waveMax, teamMax, mapID, funcLineID, lineupID, strongerLV, difficulty), 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.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.startFight()
    
    __doMainFight(curPlayer)
    return
def __doMainBossStart(curPlayer):
    ## 开始挑战关卡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
    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
    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
    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
    mainFightMgr.strongerLV = strongerLV
    mainFightMgr.difficulty = difficulty
    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,strongerLV=%s,difficulty=%s"
                       % (chapterID, levelNum, wave, waveMax, teamMax, 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})
    turnFight.setFactionLineup(ChConfig.Def_FactionB, {1:GetNPCLineupInfo(lineupID, strongerLV, difficulty)})
    turnFight.sortActionQueue()
    turnFight.startFight()
    # 挑战boss无中间过程,每次执行直接挑战一队结果
    __processTurnFight(turnFight.guid)
    return
def __doMainFight(curPlayer, tick=0):
@@ -945,47 +1193,36 @@
    
    # 限制请求CD
    if tick:
        key = "MainFightReqTick"
        lastTick = curPlayer.GetDictByKey(key)
        if lastTick and tick - lastTick <= 1000:
            GameWorld.DebugLog("主线战斗请求CD中")
        if CheckFightCD(curPlayer, tick, "MainFightReqTick"):
            return
        curPlayer.SetDict(key, tick)
        
    mainFightMgr = GetMainFightMgr(curPlayer)
    turnFight = mainFightMgr.turnFight
    
    isLevelBoss = mainFightMgr.isLevelBoss()
    if isLevelBoss:
        ## 关卡boss是一次性处理完的,一般不可能走到这里,这边做下防范
        return
    if not turnFight.isInFight():
        __doMainLevelWave(curPlayer, True)
        return
    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))
        nextLineupID = turnFight.nextLineupID()
        if nextLineupID:
            GameWorld.DebugLog("---开始进入下一小队: lineupIndex=%s,nextLineupID=%s,%s" % (turnFight.lineupIndex, nextLineupID, turnFight.lineupIDList))
            
            mainFightMgr.nextTeam = False
            turnFight.resetTurn({"teamNum":teamNum})
            turnFight.nextTurnFight()
            # 切换小队时,玩家阵容不需要处理,保留状态
            turnFight.setFactionLineup(ChConfig.Def_FactionB, {1:GetNPCLineupInfo(lineupID, mainFightMgr.strongerLV, mainFightMgr.difficulty)})
            turnFight.setFactionLineup(ChConfig.Def_FactionB, {1:GetNPCLineupInfo(nextLineupID, turnFight.strongerLV, turnFight.difficulty)})
            turnFight.sortActionQueue()
            turnFight.startFight()
            
            if mainFightMgr.isLevelBoss():
                # 每次处理一小队的完整战斗,相当于一次完整战报
                __processTurnFight(turnFight.guid)
                return
            else:
                __doMainFight(curPlayer)
            __doMainFight(curPlayer)
        else:
            __doMainLevelWave(curPlayer, False)
        return
    if isLevelBoss:
        ## 关卡boss是一次性处理完的,一般不可能走到这里,这边做下防范
        return
    
    # 小怪战斗,每次消耗1个战锤
@@ -1004,52 +1241,39 @@
    batObjMgr = BattleObj.GetBatObjMgr()
    turnNum = turnFight.turnNum
    turnMax = turnFight.turnMax
    turnNumStart = turnFight.turnNumStart
    for turnNum in range(turnNum, turnMax + 1):
        turnTimeline = turnFight.getTurnNumStartTimelin(turnNum) # 本回合起始时间节点
        curTimeline = turnFight.getTimeline()
        if turnFight.winFaction:
            break
        
        # 回合开始
        turnTimeline += 1 # 每回合开始算一个时间节点
        if curTimeline < turnTimeline:
            curTimeline = turnFight.setTimeline(turnTimeline)
            GameWorld.DebugLog("【----- 回合制战斗轮次: %s -----】 curTimeline=%s" % (turnNum, curTimeline))
        if turnNumStart < turnNum:
            GameWorld.DebugLog("【----- 回合制战斗轮次: %s -----】 " % (turnNum))
            turnFight.turnNum = turnNum
            turnFight.turnNumStart = 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 = 1
                for objID in batLineup.posObjIDDict.values():
                    batObj = batObjMgr.getBatObj(objID)
                    TurnFightPerTurnBigStart(turnFight, batObj, turnNum)
            TurnFightPerTurnBigStart(turnFight, turnNum)
        # 红颜
        # 灵兽
        
        if turnFight.checkOverByKilled():
        if turnFight.winFaction:
            break
        
        # 武将
        doMax = PosNumMax * len(turnFight.actionSortList)
        doCnt = 0
        while doCnt < doMax and turnFight.actionIndex < len(turnFight.actionSortList):
        while doCnt < doMax and turnFight.actionIndex < len(turnFight.actionSortList) and not turnFight.winFaction:
            doCnt += 1
            faction, num = turnFight.actionSortList[turnFight.actionIndex]
            batFaction = turnFight.getBatFaction(faction)
            batLineup = batFaction.getBatlineup(num)
            for posNum in range(batLineup.actionNum, PosNumMax + 1):
                turnTimeline += 1 # 每个武将位算一个时间节点
                if turnTimeline <= curTimeline:
                    # 该时间节点已经处理过了
                    GameWorld.DebugLog("该时间节点已经处理过了! turnTimeline=%s <= %s" % (turnTimeline, curTimeline))
                    continue
                #GameWorld.DebugLog("武将位置: faction=%s,posNum=%s" % (faction, posNum))
                if posNum not in batLineup.posObjIDDict:
                    batLineup.actionNum = posNum + 1
                    curTimeline = turnFight.setTimeline(turnTimeline, True)
                    #GameWorld.DebugLog("没有武将: faction=%s,posNum=%s" % (faction, posNum))
                    continue
                
                objID = batLineup.posObjIDDict[posNum]
@@ -1058,37 +1282,33 @@
                # 玩家自己阵营,预判可否行动
                if checkBreakpoint and faction == ChConfig.Def_FactionA and batObj:
                    if batObj.CanAction():
                        GameWorld.DebugLog("玩家武将已经行动过了,且有下一个可行动的武将,断点: curTimeline=%s,nextPosNum=%s" % (curTimeline, posNum))
                        GameWorld.DebugLog("玩家阵容下一个可行动的武将,断点: nextPosNum=%s" % (posNum))
                        return
                    
                batLineup.actionNum = posNum + 1
                curTimeline = turnFight.setTimeline(turnTimeline)
                TurnFightHeroTurnStart(turnFight, batObj, turnNum)
                if not OnObjAction(turnFight, batObj):
                isAction = OnObjAction(turnFight, batObj)
                TurnFightHeroTurnEnd(turnFight, batObj, turnNum)
                if not isAction:
                    continue
                
                if not checkBreakpoint and faction == ChConfig.Def_FactionA:
                    checkBreakpoint = True
                    
                break
            if turnFight.actionIndex >= len(turnFight.actionSortList) - 1:
                turnFight.actionIndex = 0
            else:
                turnFight.actionIndex += 1
                
        # 回合结束
        turnTimeline += 1 # 每回合结束算一个时间节点
        if curTimeline < turnTimeline:
            curTimeline = turnFight.setTimeline(turnTimeline)
            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():
        if turnFight.winFaction:
            break
        # 回合结束
        TurnFightPerTurnBigEnd(turnFight, turnNum)
        
    if not turnFight.winFaction:
        OnTurnAllOver(turnFight.guid)
@@ -1103,34 +1323,26 @@
    EntryLogic(turnFight)
    batObjMgr = BattleObj.GetBatObjMgr()
    for turnNum in range(1, turnMax + 1):
        if turnFight.winFaction:
            break
        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))
            batFaction = turnFight.getBatFaction(faction)
            batLineup = batFaction.getBatlineup(num)
            batLineup.actionNum = 1
            for objID in batLineup.posObjIDDict.values():
                batObj = batObjMgr.getBatObj(objID)
                TurnFightPerTurnBigStart(turnFight, batObj, turnNum)
        TurnFightPerTurnBigStart(turnFight, turnNum)
        # 红颜
        # 灵兽
            
        if turnFight.checkOverByKilled():
        if turnFight.winFaction:
            break
        
        # 武将
        doMax = PosNumMax * len(turnFight.actionSortList)
        doCnt = 0
        while doCnt < doMax and turnFight.actionIndex < len(turnFight.actionSortList):
        while doCnt < doMax and turnFight.actionIndex < len(turnFight.actionSortList) and not turnFight.winFaction:
            doCnt += 1
            faction, num = turnFight.actionSortList[turnFight.actionIndex]
            batFaction = turnFight.getBatFaction(faction)
@@ -1138,15 +1350,14 @@
            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):
                isAction = OnObjAction(turnFight, batObj)
                TurnFightHeroTurnEnd(turnFight, batObj, turnNum)
                if not isAction:
                    continue
                break
            
            if turnFight.actionIndex >= len(turnFight.actionSortList) - 1:
@@ -1154,18 +1365,11 @@
            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():
        if turnFight.winFaction:
            break
        # 回合结束
        TurnFightPerTurnBigEnd(turnFight, turnNum)
        
    if not turnFight.winFaction:
        OnTurnAllOver(turnFight.guid)
@@ -1197,118 +1401,55 @@
        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()
def TurnFightPerTurnBigStart(turnFight, turnNum):
    ## 大回合开始时
    
    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
    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 = 1
        for objID in batLineup.posObjIDDict.values():
            batObj = batObjMgr.getBatObj(objID)
            if not batObj:
                continue
            if not batObj.IsAlive():
                continue
            turnFight.ResetOneActionUseSkillCnt()
            batObj.SetTiming(ChConfig.TurnTiming_Before) # 重置时机到回合前
            if turnNum > 1: # 第1回合不用刷新技能
                RefreshObjSkillByTurn(batObj)
                
                batObj.SetDict(ChConfig.Def_Obj_Dict_TurnComboNum, 0)
                batObj.SetDict(ChConfig.Def_Obj_Dict_TurnMissNum, 0)
                batObj.SetDict(ChConfig.Def_Obj_Dict_TurnParryNum, 0)
                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)
            TurnPassive.OnTriggerPassiveEffect(turnFight, batObj, ChConfig.TriggerWay_BigTurnStart)
    return
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
    TurnPassive.OnTriggerPassiveEffect(turnFight, batObj, ChConfig.TriggerWay_BigTurnStart)
    return
def TurnFightPerTurnBigEnd(turnFight, batObj, turnNum):
def TurnFightPerTurnBigEnd(turnFight, turnNum):
    ## 大回合结束时
    if not batObj:
        return
    
    if batObj.GetHP() <= 0:
        return
    TurnPassive.OnTriggerPassiveEffect(turnFight, batObj, ChConfig.TriggerWay_BigTurnEnd)
    batObjMgr = BattleObj.GetBatObjMgr()
    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)
            if not batObj:
                continue
            if not batObj.IsAlive():
                continue
            turnFight.ResetOneActionUseSkillCnt()
            TurnPassive.OnTriggerPassiveEffect(turnFight, batObj, ChConfig.TriggerWay_BigTurnEnd)
    return
def TurnFightHeroTurnStart(turnFight, batObj, turnNum):
@@ -1316,12 +1457,110 @@
    if not batObj:
        return
    
    if batObj.GetHP() <= 0:
    if not batObj.IsAlive():
        return
    
    GameWorld.DebugLog("---[武将回合开始] : curID=%s,curHP=%s/%s" % (batObj.GetID(), batObj.GetHP(), batObj.GetMaxHP()))
    turnFight.ResetOneActionUseSkillCnt()
    RefreshObjBuffTime(turnFight, batObj)
    TurnPassive.OnTriggerPassiveEffect(turnFight, batObj, ChConfig.TriggerWay_HeroTurnStart)
    # 最后设置时机为回合后,说明:回合开启前的逻辑处理完后,其他的均默认为回合后
    batObj.SetTiming(ChConfig.TurnTiming_After)
    return
def TurnFightHeroTurnEnd(turnFight, batObj, turnNum):
    ## 武将回合结束时,不能行动也要触发
    if not batObj:
        return
    if not batObj.IsAlive():
        return
    GameWorld.DebugLog("---[武将回合结束] : curID=%s,curHP=%s/%s" % (batObj.GetID(), batObj.GetHP(), batObj.GetMaxHP()))
    turnFight.ResetOneActionUseSkillCnt()
    RefreshObjBuffTime(turnFight, batObj)
    return
def ResetObjSkill(batObj):
    ## 重置所有技能,一般是每场战斗开始的重置
    curID = batObj.GetID()
    batObj.ResetSkillUseCnt() # 使用次数
    skillManager = batObj.GetSkillManager()
    for index in range(0, skillManager.GetSkillCount()):
        curSkill = skillManager.GetSkillByIndex(index)
        if not curSkill:
            continue
        skillID = curSkill.GetSkillID()
        initCD = curSkill.GetCoolDownInit()
        if initCD:
            curSkill.SetRemainTime(initCD)
            GameWorld.DebugLog("技能初始CD: curID=%s,skillID=%s,initCD=%s" % (curID, skillID, initCD))
        elif curSkill.GetRemainTime():
            curSkill.SetRemainTime(0)
    return
def RefreshObjSkillByTurn(batObj):
    '''按回合刷新技能:默认以大回合统一减1回合
    '''
    curID = batObj.GetID()
    skillManager = batObj.GetSkillManager()
    for index in range(0, skillManager.GetSkillCount()):
        curSkill = skillManager.GetSkillByIndex(index)
        if not curSkill:
            continue
        skillID = curSkill.GetSkillID()
        preTurnUseCnt = batObj.GetSkillTurnUseCnt(skillID)
        remainTime = curSkill.GetRemainTime()
        if remainTime <= 0:
            continue
        if preTurnUseCnt > 0:
            GameWorld.DebugLog("    上回合有使用技能本回合不刷新CD: curID=%s,skillID=%s,remainTime=%s,preTurnUseCnt=%s" % (curID, skillID, remainTime, preTurnUseCnt))
            continue
        remainTime -= 1
        curSkill.SetRemainTime(remainTime)
        GameWorld.DebugLog("    更新技能CD: curID=%s,skillID=%s,remainTime=%s" % (curID, skillID, remainTime))
    batObj.ResetSkillTurnUseCnt() # 重置回合使用次数,放刷新CD后重置
    return
def RefreshObjBuffTime(turnFight, batObj):
    '''刷新buff持续时间:以武将自身回合前、回合后处理buff持续时间
    回合前添加的buff在回合开始减1,回合后添加的buff在回合结束减1
    '''
    curID = batObj.GetID()
    curTiming = batObj.GetTiming() # 时间节点 0-回合前;1-回合后
    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 remainTime <= 0:
            # 永久buff不处理
            #GameWorld.DebugLog("    永久buff不处理! curID=%s,index=%s,skillID=%s" % (curID, index, skillID))
            continue
        addTiming = buff.GetAddTiming()
        if not buff.GetRefreshState():
            GameWorld.DebugLog("    未刷新过的buff更新为刷新过! curID=%s,index=%s,skillID=%s,buffID=%s,addTiming=%s" % (curID, index, skillID, buffID, addTiming))
            buff.SetRefreshState(1)
            continue
        if addTiming != curTiming:
            GameWorld.DebugLog("    buff添加时机不同不处理! curID=%s,index=%s,skillID=%s,buffID=%s,addTiming=%s" % (curID, index, skillID, buffID, addTiming))
            continue
        remainTime -= 1
        GameWorld.DebugLog("    更新buff回合: curID=%s,buffID=%s,skillID=%s,remainTime=%s,addTiming=%s" % (curID, buffID, skillID, remainTime, addTiming))
        if remainTime > 0:
            buff.SetRemainTime(remainTime)
            TurnBuff.SyncBuffRefresh(turnFight, batObj, buff)
        else:
            TurnBuff.DoBuffDel(turnFight, batObj, buff)
    return
def AddTurnObjCureHP(curObj, srcObj, addValue, cureHP, skillID=0):
    ## 回合对象添加治疗值
    # @param curObj: 获得治疗的对象
@@ -1361,8 +1600,9 @@
                           % (curID, tagID, skillID, hurtValue, lostHP, curBatObj.GetHP()))
    return
def OnObjAction(turnFight, curBatObj):
def OnObjAction(turnFight, curBatObj, isExtra=False):
    ## 战斗单位行动
    # @param isExtra: 是否额外行动的
    if not curBatObj:
        return
    
@@ -1377,12 +1617,13 @@
    # 是否可行动状态判断
    canAction = curBatObj.CanAction()
    if not canAction:
        GameWorld.DebugLog("★回合%s %s 当前状态不可行动!" % (turnNum, objName))
        GameWorld.DebugLog("★回合%s %s 当前状态不可行动! isExtra=%s" % (turnNum, objName, isExtra))
        return
    
    atk = curBatObj.GetAtk()
    curXP = curBatObj.GetXP()
    GameWorld.DebugLog("★回合%s %s 行动 : atk=%s,curHP=%s/%s,curXP=%s" % (turnNum, objName, atk, curHP, curBatObj.GetMaxHP(), curXP))
    GameWorld.DebugLog("★回合%s %s %s行动 : atk=%s,curHP=%s/%s,curXP=%s" % (turnNum, objName, "额外" if isExtra else "", atk, curHP, curBatObj.GetMaxHP(), curXP))
    turnFight.ResetOneActionUseSkillCnt()
    turnFight.syncObjAction(turnNum, objID)
    
    TurnPassive.OnTriggerPassiveEffect(turnFight, curBatObj, ChConfig.TriggerWay_HeroActionStart)
@@ -1407,13 +1648,16 @@
        skillID = useSkill.GetSkillID()
        # 常规攻击优先xp
        if SkillCommon.isAngerSkill(useSkill):
            if curXP < xpMax:
            if curXP < xpMax and not useSkill.GetEffectByID(ChConfig.SkillEff_AngerSkillNoXP):
                continue
            if curBatObj.IsInState(ChConfig.BatObjState_Sneer):
                GameWorld.DebugLog("嘲讽状态下,无法主动释放怒技!") # 可被动释放怒技,如怒技追击
            if curBatObj.CheckInState(ChConfig.BatObjState_Sneer):
                GameWorld.DebugLog("嘲讽状态下,无法主动释放怒技! skillID=%s" % skillID) # 可被动释放怒技,如怒技追击
                continue
            useCnt = -1 # xp技能优先释放
        else:
            if useSkill.GetEffectByID(ChConfig.SkillEff_ActionUseInvalid): # 配置该效果时如果被嘲讽,相当于无法行动
                GameWorld.DebugLog("行动时无法释放该技能! skillID=%s" % skillID)
                continue
            useCnt = curBatObj.GetSkillUseCnt(skillID)
        useSkillList.append([useCnt, skillID, useSkill])
        
@@ -1429,6 +1673,14 @@
    return True
def SetObjKilled(turnFight, gameObj, killer=None, useSkill=None):
    curPlayer = turnFight.curPlayer
    npcID = gameObj.GetNPCID()
    # 非主线的PVE目标怪物
    if npcID and curPlayer and turnFight.isFBMap() and gameObj.GetFaction() != ChConfig.Def_FactionA:
        if not FBLogic.OnFBNPCKilledBefore(curPlayer, turnFight, gameObj, killer, useSkill):
            return
    objID = gameObj.GetID()
    killerObjID = killer.GetID() if killer else 0
    skillID = useSkill.GetSkillID() if useSkill else 0
@@ -1441,10 +1693,10 @@
    clientPack.SkillID = skillID
    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)
        PlayerTask.AddTaskValue(curPlayer, ChConfig.TaskType_KillNPC, 1)
        PlayerActivity.AddDailyTaskValue(curPlayer, ChConfig.DailyTask_KillNPC, 1)
    return True
def OnTurnAllOver(guid):
@@ -1476,6 +1728,7 @@
    
    turnFight.costTime = time.time() - turnFight.startTime
    winFaction = turnFight.winFaction
    turnFight.isWin = (winFaction == ChConfig.Def_FactionA)
    GameWorld.DebugLog("--- 战斗结束处理 ---, winFaction=%s, costTime=%ss, %s" % (winFaction, turnFight.costTime, guid))
    
    # 统计明细
@@ -1486,11 +1739,13 @@
            statInfo[str(faction)] = {}
        facStatInfo = statInfo[str(faction)]
        batFaction = turnFight.getBatFaction(faction)
        batFaction.totalHurt = 0
        for num in batFaction.lineupDict.keys():
            if str(num) not in facStatInfo:
                facStatInfo[str(num)] = {}
            lineupStatInfo = facStatInfo[str(num)]
            batLineup = batFaction.getBatlineup(num)
            batLineup.totalHurt = 0
            GameWorld.DebugLog("阵容明细: faction=%s,num=%s" % (faction, num))
            for posNum, objID in batLineup.posObjIDDict.items():
                batObj = batObjMgr.getBatObj(objID)
@@ -1502,132 +1757,21 @@
                atkHurt = batObj.hurtStat
                defHurt = batObj.defStat
                cureHP = batObj.cureStat
                batLineup.totalHurt += atkHurt
                batFaction.totalHurt += atkHurt
                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
#
@@ -1637,4 +1781,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