hxp
2025-08-25 50086ccb3ade4bdc47b32af7e097ec9e2c2d3f88
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Attack/TurnAttack.py
@@ -10,6 +10,10 @@
# @version 1.0
#
# 详细描述: 回合制攻击逻辑,均使用NPC实例作为战斗主体
#        实时战斗:  仅用于主线,每个玩家最多仅允许存在一个实时战斗
#                    由前端控制战斗流程,后端进行计算
#        瞬时战斗:  用于非主线外的各种战斗,如副本PVE挑战,PVP挑战等,一样仅有一个瞬时战斗,
#                    一次性处理完,后端控制整体战斗流程及计算,前端仅做战报解析表现战斗过程
#
#-------------------------------------------------------------------------------
#"""Version = 2023-11-30 15:30"""
@@ -20,9 +24,27 @@
import NetPackCommon
import PlayerControl
import GameWorld
import GameObj
import FBCommon
import FBLogic
import IpyGameDataPY
import PlayerOnline
import NPCCommon
import ShareDefine
import PyGameData
import ItemControler
import SkillCommon
import BattleObj
import TurnPassive
import TurnSkill
import TurnBuff
import ObjPool
import random
import time
import json
TimelineSet = 10000 # 单回合最大时间轴
PosNumMax = 10 # 最大站位编号
ActionNumStart = -1 # 起始行动位置编号,一般是从1开始,如果有加主公、红颜等则扣除相应位置值,如从0或-1开始
# 回合战斗流程状态
(
@@ -33,170 +55,1485 @@
FightState_Award, # 4 结算奖励
FightState_Over, # 5 结束状态,无特殊意义,仅代表所有处理结束了,与Start对应
) = range(6)
class BatLineup():
    ## 战斗阵容
    def __init__(self, faction, num, turnFight):
        self.turnFight = turnFight # TurnFight
        self.faction = faction # 所属阵营
        self.num = num # 该阵容所在阵营中的编号,不同阵营可重复,多V多
        self.ownerID = 0 # 阵容所属玩家ID,可能为0,0代表非玩家阵容
        self.shapeType = 0 # 阵型
        self.fightPower = 0 # 阵容总战力
        self.posObjIDDict = {} # 站位对应战斗实体 {站位编号:batObjID, ...}, 站位编号小于0为非主战单位,如主公、红颜等
        self.lingshouObjIDDict = {} # 灵兽战斗单位 {位置编号:batObjID, ...}
        self.beautyObjIDDict = {} # 红颜战斗单位 {位置编号:batObjID, ...}
        self.actionNum = ActionNumStart # 行动位置,从1开始
        return
    def getPlayerID(self): return self.turnFight.playerID # 发起的玩家ID
    def isEmpty(self): return not self.posObjIDDict
    def setLineup(self, lineupInfo):
        ## 设置阵容
        # @param lineupInfo: 阵容信息
        self.clearLineup()
        self.ownerID = lineupInfo.get("PlayerID", 0) # 阵容所属的玩家ID
        self.shapeType = lineupInfo.get("ShapeType", 0)
        self.fightPower = lineupInfo.get("FightPower", 0)
        SummonLineupObjs(self, self.faction, self.num, lineupInfo, self.getPlayerID())
        return
    def clearLineup(self):
        ## 清除阵容
        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
class BatFaction():
    ## 战斗阵营
    def __init__(self, faction, turnFight):
        self.turnFight = turnFight # TurnFight
        self.faction = faction
        self.lineupDict = {} # 该阵营所有阵容信息 {编号:BatLineup, ...}
        return
    def getBatlineup(self, num=1):
        ## 获取战斗阵容
        lineup = None
        if num in self.lineupDict:
            lineup = self.lineupDict[num]
        else:
            lineup = BatLineup(self.faction, num, self.turnFight)
            self.lineupDict[num] = lineup
        return lineup
    def clearLineups(self):
        ## 清除所有战斗阵容
        for lineup in self.lineupDict.values():
            lineup.clearLineup()
        return
class TurnFight():
    '''某场回合战斗,支持多V多,所有战斗通用,主线、爬塔、PVP等
    可能为系统后台发起的,则攻击方、防守方阵容以玩家所保存的阵容镜像进行战斗
    如果玩家发起的,则攻击方为发起方玩家,目标为防守方,支持多V多,如组队PK
    多V多的情况也是某个队员各自发起战斗,只是用到队友的镜像阵容一起战斗,队友无感知
    多V多可使用两种模式:
    1. 多个阵容一起出场,在一场战斗中完成
    2. 以单阵容方式轮流出场战斗,该方式同样可以使用方式1的逻辑实现,固优先使用方式1战斗逻辑,方便扩展
    '''
    def __init__(self, mapID=0, funcLineID=0, playerID=0, isNeedReport=False):
        self.guid = GameWorld.GetGUID() # 某场战斗的唯一guid,可用于存储记录如战报等
        self.playerID = playerID # 可能为0,系统后台自动处理的战斗时为0,某个玩家发起则为发起玩家ID,同个玩家ID可能同时存在多场战斗,如主线+其他
        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.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.timeline = 0 # 时间轴节点  turnNum*1000+actionIndex*100++actionNum
        self.startTime = 0 # 开始时间戳,支持毫秒小数
        self.costTime = 0 # 单场战斗总耗时,支持毫秒小数
        return
    def setTurn(self, mapID, funcLineID, turnMax, isNeedReport=False, msgDict={}):
        ## 设置本场回合战斗设定
        self.mapID = mapID
        self.funcLineID = funcLineID
        self.turnMax = turnMax # 最大回合数
        self.isNeedReport = isNeedReport
        self.msgDict = {}
        self.resetTurn(msgDict)
        return
    def resetTurn(self, msgDict):
        ## 一般用于玩家发起的战斗,在需要保留玩家阵容属性及状态的情况下,重置回合进入下一场战斗
        self.turnNum = 1
        self.enterLogic = False
        self.turnStart = 0
        self.turnEnd = 0
        self.winFaction = 0
        self.batBuffer = "" # 战报buffer
        self.msgDict.update(msgDict)
        self.startTime = time.time()
        self.costTime = 0
        return
    def setFactionLineup(self, faction, lineupDict):
        ## 设置阵营阵容
        # @param lineupDict: {阵容编号:阵容信息, ...} 每个阵营支持多个阵容,即支持多V多
        batFaction = self.getBatFaction(faction)
        batFaction.clearLineups()
        for num, lineupInfo in lineupDict.items():
            if not lineupInfo:
                continue
            batLineup = batFaction.getBatlineup(num)
            batLineup.setLineup(lineupInfo)
        return
    def sortActionQueue(self):
        ## 刷新出手顺序队列
        sortList = []
        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([isPlayer, fightPower, sortValue, faction, num])
        sortList.sort(reverse=True) # 战力高的先手
        self.actionIndex = 0
        self.actionSortList = []
        for _, _, _, faction, num in sortList:
            self.actionSortList.append([faction, num])
        GameWorld.DebugLog("阵容战力排序[isPlayer, fp, sortV, f, n]: %s" % sortList)
        GameWorld.DebugLog("阵容行动顺序[f, n]: %s" % self.actionSortList)
        return
    def getTimeline(self): return self.timeline
    def setTimeline(self, turnNum):
        '''回合战斗的时间轴节点 ,即第几回合开始,每个回合支持9999个行动节点
        @param turnNum: 第x回合
        '''
        self.timeline = turnNum * TimelineSet + 0
        GameWorld.DebugLog("时间节点更新: %s" % self.timeline)
        OnTimelineChange(self)
        return
    def addTimeline(self):
        ## 每切换一个行动单位可视为一个行动节点,即代表单回合战斗中的某一个时间节点
        self.timeline += 1
        GameWorld.DebugLog("时间节点更新: %s" % self.timeline)
        OnTimelineChange(self)
        return
    def getBatFaction(self, faction=ChConfig.Def_FactionA):
        ## 默认阵营1
        batFaction = None
        if faction in self.factionDict:
            batFaction = self.factionDict[faction]
        else:
            batFaction = BatFaction(faction, self)
            self.factionDict[faction] = batFaction
        return batFaction
    def checkOverByKilled(self):
        ##检查是否击杀结束
        # @return: 0-无获胜阵营,同时也代表战斗未结束;>0-获胜的阵营ID,同时也代表战斗已全部结束
        if self.winFaction:
            return self.winFaction
        batObjMgr = BattleObj.GetBatObjMgr()
        for faction, batFaction in self.factionDict.items():
            allKilled = True
            for batLineup in batFaction.lineupDict.values():
                if not allKilled:
                    break
                for posNum, objID in batLineup.posObjIDDict.items():
                    if posNum <= 0:
                        # 非主战位置不判断
                        continue
                    batObj = batObjMgr.getBatObj(objID)
                    if not batObj:
                        continue
                    if batObj.IsAlive():
                        allKilled = False
                        break
            if allKilled:
                self.winFaction = ChConfig.Def_FactionA if faction == ChConfig.Def_FactionB else ChConfig.Def_FactionB
                DoTurnFightOver(self.guid)
                return self.winFaction
        return 0
    def exitFight(self):
        ## 退出战斗
        self.syncState(FightState_Over)
        for batFaction in self.factionDict.values():
            batFaction.clearLineups()
        self.state = -1
        return
    def startFight(self):
        ## 准备就绪,开始战斗
        self.state = FightState_Start
        self.setTimeline(1)
        self.syncInit()
        return
    def isInFight(self): return self.state != -1
    def syncInit(self):
        ## 初始化通知
        msg = json.dumps(self.msgDict, ensure_ascii=False)
        msg = msg.replace(" ", "")
        clientPack = ChPyNetSendPack.tagSCTurnFightInit()
        clientPack.MapID = self.mapID
        clientPack.FuncLineID = self.funcLineID
        clientPack.TurnMax = self.turnMax
        clientPack.Msg = msg
        clientPack.Len = len(clientPack.Msg)
        clientPack.FactionList = []
        batObjMgr = BattleObj.GetBatObjMgr()
        for faction in self.factionDict.keys():
            batFaction = self.getBatFaction(faction)
            tfFaction = ChPyNetSendPack.tagSCTurnFightFaction()
            tfFaction.Faction = faction
            tfFaction.LineupList = []
            for num in batFaction.lineupDict.keys():
                batLineup = batFaction.getBatlineup(num)
                tfLineup = ChPyNetSendPack.tagSCTurnFightLineup()
                tfLineup.Num = num
                tfLineup.OwnerID = batLineup.ownerID
                tfLineup.ShapeType = batLineup.shapeType
                tfLineup.ObjList = []
                for posNum, objID in batLineup.posObjIDDict.items():
                    batObj = batObjMgr.getBatObj(objID)
                    if not batObj:
                        continue
                    tfObj = ChPyNetSendPack.tagSCTurnFightObj()
                    tfObj.ObjID = batObj.GetID()
                    tfObj.NPCID = batObj.GetNPCID()
                    tfObj.HeroID = batObj.GetHeroID()
                    tfObj.SkinID = batObj.GetSkinID()
                    tfObj.HP = batObj.GetHP() % ChConfig.Def_PerPointValue
                    tfObj.HPEx = batObj.GetHP() / ChConfig.Def_PerPointValue
                    tfObj.MaxHP = batObj.GetMaxHP() % ChConfig.Def_PerPointValue
                    tfObj.MaxHPEx = batObj.GetMaxHP() / ChConfig.Def_PerPointValue
                    tfObj.LV = batObj.GetLV()
                    tfObj.PosNum = posNum
                    tfObj.AngreXP = batObj.GetXP()
                    tfLineup.ObjList.append(tfObj)
                tfLineup.ObjCnt = len(tfLineup.ObjList)
                tfFaction.LineupList.append(tfLineup)
            tfFaction.LineupCnt = len(tfFaction.LineupList)
            clientPack.FactionList.append(tfFaction)
        clientPack.FactionCnt = len(clientPack.FactionList)
        self.addBatPack(clientPack)
        return
    def syncState(self, state, msgDict={}):
        self.state = state
        msg = json.dumps(msgDict, ensure_ascii=False)
        msg = msg.replace(" ", "")
        clientPack = ChPyNetSendPack.tagMCTurnFightState()
        clientPack.Clear()
        clientPack.MapID = self.mapID
        clientPack.FuncLineID = self.funcLineID
        clientPack.State = state
        clientPack.TurnNum = self.turnNum
        clientPack.Msg = msg
        clientPack.Len = len(clientPack.Msg)
        self.addBatPack(clientPack)
        return
    def syncObjAction(self, turnNum, objID):
        clientPack = ChPyNetSendPack.tagMCTurnFightObjAction()
        clientPack.Clear()
        clientPack.TurnNum = turnNum
        clientPack.ObjID = objID
        self.addBatPack(clientPack)
        return
    def addBatPack(self, clientPack):
        ## 添加战斗过程封包,非战斗相关的封包可以不使用该函数发送,如加经验、掉落等
        ## 注:战斗相关的封包需调用本函数方便统一管理战报
        if hasattr(clientPack, "Head"):
            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:
            ObjPool.GetPoolMgr().release(clientPack)
        return
class TurnFightMgr():
    ## 回合战斗管理器
    def __init__(self):
        self.turnFightDict = {} # {guid:TurnFight, ...}
        return
    def addTurnFight(self, mapID, funcLineID=0, playerID=0):
        tf = TurnFight(mapID, funcLineID, playerID, False)
        self.turnFightDict[tf.guid] = tf
        return tf
    def delTurnFight(self, guid):
        turnFight = self.getTurnFight(guid)
        if not turnFight:
            return
        turnFight.exitFight()
        self.turnFightDict.pop(guid, None)
        return
    def getTurnFight(self, guid):
        tf = None
        if guid in self.turnFightDict:
            tf = self.turnFightDict[guid]
        elif False:
            tf = TurnFight()
        return tf
def GetTurnFightMgr():
    tfMgr = None
    if PyGameData.g_turnFightMgr:
        tfMgr = PyGameData.g_turnFightMgr
    else:
        tfMgr = TurnFightMgr()
        PyGameData.g_turnFightMgr = tfMgr
    return tfMgr
class MainFight():
    ## 主线战斗管理
    def __init__(self, playerID):
        self.playerID = playerID
        self.chapterID = 0 # 章节ID
        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
    def isLevelBoss(self):
        ## 当前战斗是否关卡boss
        return self.turnFight.mapID == ChConfig.Def_FBMapID_MainBoss
def GetMainFightMgr(curPlayer):
    ## 获取主线战斗管理
    olPlayer = PlayerOnline.GetOnlineMgr().GetOnlinePlayer(curPlayer)
    return olPlayer.mainFight
#// B4 10 回合制战斗 #tagCMTurnFight
def OnPlayerLogin(curPlayer):
    chapterID, levelNum, _ = PlayerControl.GetMainLevelPassInfo(curPlayer)
    nowChapterID, _, _ = PlayerControl.GetMainLevelNowInfo(curPlayer)
    if chapterID != nowChapterID:
        if IpyGameDataPY.GetIpyGameDataNotLog("MainChapter", chapterID) and IpyGameDataPY.GetIpyGameDataNotLog("MainLevel", chapterID, levelNum):
            fixNowValue = PlayerControl.SetMainLevelNowInfo(curPlayer, chapterID, levelNum, 1)
            GameWorld.Log("当前主线关卡章节与过关章节不一致时,且过关进度章节已经存在,强制修正当前刷怪进度为过关章节进度! passChapterID-LV=%s-%s,nowChapterID=%s,fixNowValue=%s"
                          % (chapterID, levelNum, nowChapterID, fixNowValue), curPlayer.GetPlayerID())
    return
def GetPlayerLineupInfoByCache(playerID, lineupID):
    ## 获取玩家阵容信息 - 根据玩家查看缓存
    lineupInfo = {}
    return lineupInfo
def GetPlayerLineupInfo(curPlayer, lineupID):
    ## 获取玩家阵容信息,可用于战斗或查看缓存,因为可能取玩家的缓存进行对战,所以统一使用json格式,前端通用
    # @param lineupID: 阵容ID
    # @return: 阵容全部信息json字典,前端通用格式
    playerID = curPlayer.GetPlayerID()
    lineup = PlayerOnline.GetOnlinePlayer(curPlayer).GetLineup(lineupID)
    lineupInfo = {"PlayerID":playerID, "FightPower":lineup.fightPower, "ShapeType":lineup.shapeType}
    heroDict = {}
    curPack = curPlayer.GetItemManager().GetPack(ShareDefine.rptHero)
    for posNum in lineup.lineupHeroDict.keys():
        hero = lineup.GetLineupHero(posNum)
        heroID = hero.heroID
        itemIndex = hero.itemIndex
        userData = ""
        heroLV = 1
        if itemIndex >= 0 and itemIndex < curPack.GetCount():
            heroItem = curPack.GetAt(itemIndex)
            if heroItem and not heroItem.IsEmpty():
                userData = heroItem.GetUserData()
                heroLV = heroItem.GetUserAttr(ShareDefine.Def_IudetHeroLV)
        skillIDlist = []
        skillIDlist += hero.heroSkillIDList
        heroDict[str(posNum)] = {
                                 "HeroID":heroID,
                                 "SkinID":hero.skinID,
                                 "LV":heroLV,
                                 "Data":userData,
                                 "FightPower":hero.fightPower,
                                 "AttrDict":{str(k):v for k, v in hero.heroBatAttrDict.items() if v > 0},
                                 "SkillIDList":skillIDlist,
                                 }
    lineupInfo.update({"Hero":heroDict})
    return lineupInfo
def GetNPCLineupInfo(lineupID):
    ## 获取NPC阵容信息
    # @param lineupID: 阵容ID
    # @return: 阵容全部信息json字典,前端通用格式
    ipyData = IpyGameDataPY.GetIpyGameData("NPCLineup", lineupID)
    if not ipyData:
        return {}
    heroDict = {}
    for posNum in range(1, 1 + 10):
        if not hasattr(ipyData, "GetPosNPCID%s" % posNum):
            break
        npcID = getattr(ipyData, "GetPosNPCID%s" % posNum)()
        if not npcID:
            continue
        npcData = NPCCommon.GetNPCDataPy(npcID)
        if not npcData:
            continue
        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(),
                       ChConfig.AttrID_SuperHitRate:npcData.GetSuperHitRate(), ChConfig.AttrID_SuperHitRateDef:npcData.GetSuperHitRateDef(),
                       ChConfig.AttrID_StunRate:npcData.GetStunRate(), ChConfig.AttrID_StunRateDef:npcData.GetStunRateDef(),
                       ChConfig.AttrID_ComboRate:npcData.GetComboRate(), ChConfig.AttrID_ComboRateDef:npcData.GetComboRateDef(),
                       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
                                 }
    lineupInfo = {"NPCLineupID":lineupID, "Hero":heroDict}
    return lineupInfo
def SummonLineupObjs(batLineup, faction, num, lineupInfo, playerID=0):
    '''召唤阵容战斗实例
    @param faction: 所属阵营,目前支持两个阵营,1或2
    @param num: 战斗阵容在该阵营上的编号,1V1时默认1,多对多时1~n
    @param lineupInfo: 阵容信息
    @param playerID: 发起的玩家ID,系统场次为0
    '''
    GameWorld.DebugLog("SummonLineupObjs faction:%s,num:%s,lineupInfo=%s" % (faction, num, lineupInfo), playerID)
    if playerID:
        curPlayer = GameWorld.GetPlayerManager().FindPlayerByID(playerID)
        if not curPlayer:
            return
    tfGUID = batLineup.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)
        npcID, heroID, skinID = 0, 0, 0
        atkBackSkillID = 0 # 反击技能ID
        fightPower = 0
        skillIDList = [] # 战斗对象可能改变属性或技能,重新创建,防止误修改来源值
        attrDict = {}
        skillIDList += heroInfo.get("SkillIDList", [])
        attrDict.update(heroInfo.get("AttrDict", {}))
        objName = ""
        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()
        batObj = batObjMgr.addBatObj()
        if not batObj:
            break
        objID = batObj.GetID()
        batObj.SetTFGUID(tfGUID)
        batObj.SetName(objName)
        batObj.SetFaction(faction)
        batObj.SetLineupPos(posNum, num)
        batObj.SetFightPower(fightPower)
        batObj.SetLV(lv)
        batObj.SetAtkDistType(atkDistType)
        if npcID:
            batObj.SetNPCID(npcID)
        elif lineupPlayerID:
            batObj.SetOwnerHero(lineupPlayerID, 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,%s,skill=%s" % (objID, GetObjName(batObj), skillIDList))
        batObj.InitBatAttr({int(k):v for k, v in attrDict.items()}, initXP)
    return
#// B4 13 主线战斗请求 #tagCSMainFightReq
#
#struct    tagCMTurnFight
#struct    tagCSMainFightReq
#{
#    tagHead        Head;
#    DWORD        MapID;    // 自定义地图ID,可用于绑定战斗场景功能(如野外关卡,爬塔功能,竞技场等)
#    WORD        FuncLineID;
#    BYTE        TagType;    // 战斗目标类型,0-NPC,1-玩家,2-队伍
#    DWORD        TagID;    // 战斗目标类型对应的ID
#    BYTE        ValueCount;
#    DWORD        ValueList[ValueCount]; // 附加值列表,可选,具体含义由MapID决定
#    BYTE        ReqType;        // 0-停止战斗回城;1-设置消耗倍值;2-挑战关卡小怪;3-挑战关卡boss;4-继续战斗;
#    DWORD        ReqValue;    // 请求值,ReqType为1时发送消耗倍值
#};
def OnTurnFight(index, clientData, tick):
def OnMainFightReq(index, clientData, tick):
    curPlayer = GameWorld.GetPlayerManager().GetPlayerByIndex(index)
    mapID = clientData.MapID
    funcLineID = clientData.FuncLineID
    tagType = clientData.TagType
    tagID = clientData.TagID
    valueList = clientData.ValueList
    reqType = clientData.ReqType
    reqValue = clientData.ReqValue
    
    if reqType == 0:
        __doExitMainFight(curPlayer)
        return
    elif reqType == 1:
        __doSetFightPoint(curPlayer, reqValue)
        return
    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:
        pass
    # 标记结束
    clientPack.Sign = 1
    NetPackCommon.SendFakePack(curPlayer, clientPack)
    return
def __doExitMainFight(curPlayer):
    ## 主线退出战斗 - 回城休息
    mainFightMgr = GetMainFightMgr(curPlayer)
    turnFight = mainFightMgr.turnFight
    if turnFight:
        turnFight.exitFight()
    return
def __doSetFightPoint(curPlayer, fightPoint):
    ## 设置消耗倍值
    GameWorld.DebugLog("设置战锤消耗倍值: %s" % fightPoint)
    if fightPoint == 1:
        pass
    elif fightPoint == 2:
        # 条件验证
        pass
    elif fightPoint == 3:
        # 条件验证
        pass
    else:
        return
    curPlayer.SetFightPoint(fightPoint)
    return
def __doMainLevelWave(curPlayer, isRestStart=False):
    ## 开始新的关卡波
    # @param isRestStart: 是否从休息状态重新开始的
    playerID = curPlayer.GetPlayerID()
    if tagType == ChConfig.TurnBattle_TagType_Player:
        if tagID == playerID:
            GameWorld.DebugLog("不能打自己!", playerID)
            return
    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):
        GameWorld.DebugLog("战锤不足,无法开始!")
        return
    chapterIpyData = IpyGameDataPY.GetIpyGameData("MainChapter", chapterID)
    if not chapterIpyData:
        return
    levelIpyData = IpyGameDataPY.GetIpyGameData("MainLevel", chapterID, levelNum)
    if not levelIpyData:
        # 如上线后减少某个章节关卡数的情况,导致找不到,则从该章节回退到存在的关卡开始
        while levelNum > 1:
            levelNum -= 1
            levelIpyData = IpyGameDataPY.GetIpyGameDataNotLog("MainLevel", chapterID, levelNum)
            if levelIpyData:
                break
    if not levelIpyData:
        return
    # 本关卡最大波数,暂时支持最大6波
    waveMax = 6
    while waveMax >= 1 and (not hasattr(levelIpyData, "GetWaveLineupIDList%s" % waveMax) or not getattr(levelIpyData, "GetWaveLineupIDList%s" % waveMax)()):
        waveMax -= 1
    if waveMax < 1:
        return
    if wave > waveMax:
        wave = waveMax
        
    reqRet = FBLogic.OnTurnFightRequest(curPlayer, mapID, funcLineID, tagType, tagID, valueList)
    if not reqRet:
    waveLineupList = getattr(levelIpyData, "GetWaveLineupIDList%s" % wave)() # 小队1阵容ID|小队2阵容ID|...
    if not waveLineupList:
        return
    teamMax = len(waveLineupList)
    teamNum = 1
    lineupID = waveLineupList[teamNum - 1] # NPC阵容ID
    lineupMainInfo = GetPlayerLineupInfo(curPlayer, ShareDefine.Lineup_Main)
    if not lineupMainInfo:
        GameWorld.DebugLog("没有设置主阵容!", playerID)
        return
    
    # 需要发送到GameServer验证
    if mapID in ChConfig.Def_TFMapID_SendToGameServer:
        SendToGameServer_TurnFight(curPlayer, "TurnFightRequest", [mapID, funcLineID, tagType, tagID, valueList])
        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", 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)
    
    DoTurnFightProcess(curPlayer, mapID, funcLineID, tagType, tagID, valueList, tick)
    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)})
    turnFight.sortActionQueue()
    turnFight.startFight()
    return
def SendToGameServer_TurnFight(curPlayer, msgType, dataMsg=""):
def __doMainBossStart(curPlayer):
    ## 开始挑战关卡boss
    playerID = curPlayer.GetPlayerID()
    msgList = str([msgType, dataMsg])
    GameWorld.GetPlayerManager().GameServer_QueryPlayerResult(playerID, 0, 0, "TurnFight", msgList, len(msgList))
    GameWorld.Log("回合战斗发送GameServer: %s, %s" % (msgType, dataMsg), playerID)
    return
def GameServer_TurnFight_DoResult(curPlayer, msgData, tick):
    msgType, dataMsg, ret = msgData
    if not ret:
    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
    
    if msgType == "TurnFightRequest":
        mapID, funcLineID, tagType, tagID, valueList = dataMsg
        DoTurnFightProcess(curPlayer, mapID, funcLineID, tagType, tagID, valueList, tick)
    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
        
    elif msgType == "TurnFightOver":
        mapID, funcLineID, tagType, tagID, valueList, fightRet, awardItemList = dataMsg
        FBLogic.OnTurnFightOver_GameServerRet(curPlayer, mapID, funcLineID, tagType, tagID, valueList, fightRet, awardItemList, ret)
    elif msgType == "TurnFightTagPlayerInfo":
        mapID, funcLineID, tagType, tagID, valueList = dataMsg
        DoTrunFightVSPlayer(curPlayer, tagID, [mapID, funcLineID, valueList], ret)
    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})
    turnFight.setFactionLineup(ChConfig.Def_FactionB, {1:GetNPCLineupInfo(lineupID)})
    turnFight.sortActionQueue()
    turnFight.startFight()
    # 挑战boss无中间过程,每次执行直接挑战一队结果
    __processTurnFight(turnFight.guid)
    return
def DoTurnFightProcess(curPlayer, mapID, funcLineID, tagType, tagID, valueList, tick):
    ## 执行回合制战斗的完整流程
def __doMainFight(curPlayer, tick):
    ## 主线执行战斗
    
    #if curPlayer.GetSightLevel() != curPlayer.GetID():
    #    PlayerControl.SetPlayerSightLevel(curPlayer, curPlayer.GetID())
    SyncTurnFightState(curPlayer, mapID, funcLineID, tagType, tagID, FightState_Start)
    # 限制请求CD
    #if not GameWorld.GetGameWorld().GetDebugLevel():
    key = "MainFightReqTick"
    lastTick = curPlayer.GetDictByKey(key)
    if lastTick and tick - lastTick <= 1000:
        GameWorld.DebugLog("主线战斗请求CD中")
        return
    curPlayer.SetDict(key, tick)
    
    tagPlayerInfo = {}
    if tagType == ChConfig.TurnBattle_TagType_Player and tagID >= 10000 :
        tagPlayer = GameWorld.GetMapCopyPlayerManager().FindPlayerByID(tagID)
        if tagPlayer:
            tagPlayerInfo = __GetPlayerInfo(tagPlayer)
    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.startFight()
            if mainFightMgr.isLevelBoss():
                # 每次处理一小队的完整战斗,相当于一次完整战报
                __processTurnFight(turnFight.guid)
                return
        else:
            SendToGameServer_TurnFight(curPlayer, "TurnFightTagPlayerInfo", [mapID, funcLineID, tagType, tagID, valueList])
            return
    DoTrunFight(curPlayer, mapID, funcLineID, tagType, tagID, valueList, tick, tagPlayerInfo)
    SyncTurnFightState(curPlayer, mapID, funcLineID, tagType, tagID, FightState_Over)
    return
def __GetPlayerInfo(curPlayer):
    infoDict = {
                "Name":curPlayer.GetPlayerName(),
                "Job":curPlayer.GetJob(),
                "LV":curPlayer.GetLV(),
                "RealmLV":curPlayer.GetOfficialRank(),
                "MaxHP":GameObj.GetMaxHP(curPlayer),
                "FightPower":PlayerControl.GetFightPower(curPlayer),
                }
    return infoDict
def DoTrunFightVSPlayer(curPlayer, tagPlayerID, callData, tagPlayerInfo):
    tagType = ChConfig.TurnBattle_TagType_Player
    tagID = tagPlayerID
    mapID, funcLineID, valueList = callData
    if tagPlayerInfo and curPlayer.GetPlayerID() != tagPlayerID:
        tick = GameWorld.GetGameWorld().GetTick()
        DoTrunFight(curPlayer, mapID, funcLineID, tagType, tagID, valueList, tick, tagPlayerInfo)
    SyncTurnFightState(curPlayer, mapID, funcLineID, tagType, tagID, FightState_Over)
    return
def DoTrunFight(curPlayer, mapID, funcLineID, tagType, tagID, valueList, tick, tagInfo=None):
    if not tagID:
            __doMainLevelWave(curPlayer, False)
        return
    if not tagInfo:
        tagInfo = {}
    playerID = curPlayer.GetPlayerID()
    GameWorld.DebugLog("回合战斗: mapID=%s,funcLineID=%s,tagType=%s,tagID=%s,valueList=%s,tagInfo=%s"
                       % (mapID, funcLineID, tagType, tagID, valueList, tagInfo), playerID)
    
    factionSyncInfoA = __GetPlayerInfo(curPlayer)
    factionSyncInfoB = tagInfo
    SyncTurnFightState(curPlayer, mapID, funcLineID, tagType, tagID, FightState_PrepareOK, msg=[factionSyncInfoA, factionSyncInfoB])
    if isLevelBoss:
        ## 关卡boss是一次性处理完的,一般不可能走到这里,这边做下防范
        return
    
    turnNum, turnMax = 1, 15
    # 小怪战斗,每次消耗1个战锤
    fightPoint = max(curPlayer.GetFightPoint(), 1) # 主线战斗消耗倍值,默认1
    
    curFightPower = PlayerControl.GetFightPower(curPlayer)
    tagFightPower = tagInfo.get("FightPower", 0)
    isWin = 1 if curFightPower >= tagFightPower else 0
    GameWorld.DebugLog("    战斗结果: isWin=%s,curFightPower=%s,tagFightPower=%s" % (isWin, curFightPower, tagFightPower), playerID)
    if not PlayerControl.HaveMoney(curPlayer, ShareDefine.TYPE_Price_Xiantao, fightPoint):
        GameWorld.DebugLog("回合开始时战锤不足!")
        return
    
    factionTotalHurtDict = {}
    # 以下均是处理关卡小怪分段实时战斗
    EntryLogic(turnFight)
    
    playbackID = 0 # 战斗回放ID,可根据该ID查看回放
    # 按阵营阵容执行顺序,逐个遍历
    doCnt = 0
    doMax = (PosNumMax + 2) * len(turnFight.actionSortList) # 防止死循环,做最大循环次数限制 = (最大位置数 + 主公、红颜位置)*行动阵容数
    overLineupList = [] # 本回合已经结束行动的阵容列表 [(faction, num), ...], 所有阵容全部结束代表本回合结束
    
    # 战斗结束后处理
    fightRet = [isWin, turnNum, turnMax, factionTotalHurtDict, playbackID]
    needSendGameServer, awardItemList, overInfoEx = False, [], {}
    overRet = FBLogic.OnTurnFightOver(curPlayer, mapID, funcLineID, tagType, tagID, valueList, fightRet)
    if overRet != None:
        needSendGameServer, awardItemList, overInfoEx = overRet
    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
            turnFight.addTimeline() # 每回合开始算一个时间节点
            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)
                    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 needSendGameServer or mapID in ChConfig.Def_TFMapID_SendToGameServer:
        SendToGameServer_TurnFight(curPlayer, "TurnFightOver", [mapID, funcLineID, tagType, tagID, valueList, fightRet, awardItemList])
        GameWorld.DebugLog("执行行动: turnNum=%s,faction=%s,num=%s,actionNum=%s" % (turnFight.turnNum, faction, num, batLineup.actionNum))
        
    overMsg = {"isWin":isWin, FBCommon.Over_itemInfo:FBCommon.GetJsonItemList(awardItemList), "totalHurt":0}
    playbackID and overMsg.update({"playbackID":playbackID})
    overInfoEx and overMsg.update(overInfoEx)
    SyncTurnFightState(curPlayer, mapID, funcLineID, tagType, tagID, FightState_Award, turnNum, turnMax, overMsg)
        # 主公
        if batLineup.actionNum == -1:
            pass
        # 红颜
        elif batLineup.actionNum == 0:
            pass
        # 武将
        elif batLineup.actionNum > 0:
            for posNum in range(batLineup.actionNum, PosNumMax + 1):
                turnFight.addTimeline() # 每个武将位算一个时间节点
                batLineup.actionNum = posNum
                if posNum not in batLineup.posObjIDDict:
                    continue
                objID = batLineup.posObjIDDict[posNum]
                batObj = batObjMgr.getBatObj(objID)
                TurnFightHeroTurnStart(turnFight, batObj, turnNum)
                if not OnObjAction(turnFight, batObj):
                    continue
                if faction == ChConfig.Def_FactionA:
                    playerHeroAtk = 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.checkOverByKilled():
            break
        if playerHeroAtk:
            # 玩家武将有行动则停止,一段段执行
            GameWorld.DebugLog("玩家武将有行动则停止!")
            break
    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
            turnFight.addTimeline() # 每回合结束算一个时间节点
            for faction, num in turnFight.actionSortList:
                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():
                return
        if turnNum < turnFight.turnMax:
            turnFight.turnNum += 1
            turnFight.setTimeline(turnFight.turnNum) # 回合变更,直接设置新回合时间节点
        else:
            OnTurnAllOver(turnFight.guid)
    return
def SyncTurnFightState(curPlayer, mapID, funcLineID, tagType, tagID, state, turnNum=0, turnMax=0, msg=""):
def __processTurnFight(guid):
    ## 一次性处理完一个小队的战斗
    turnFight = GetTurnFightMgr().getTurnFight(guid)
    curPlayer = turnFight.curPlayer
    turnMax = turnFight.turnMax
    EntryLogic(turnFight)
    batObjMgr = BattleObj.GetBatObjMgr()
    for turnNum in range(1, turnMax + 1):
        turnFight.turnNum = turnNum
        GameWorld.DebugLog("【----- 回合制战斗轮次: %s -----】" % turnNum)
        turnFight.setTimeline(turnNum)
        if curPlayer:
            turnFight.syncState(FightState_Fighting)
        # 回合开始
        turnFight.addTimeline() # 每回合开始算一个时间节点
        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)
        # 主公
        #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
        # 武将
        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):
                turnFight.addTimeline() # 每个武将位算一个时间节点
                batLineup.actionNum = posNum + 1
                if posNum not in batLineup.posObjIDDict:
                    continue
                objID = batLineup.posObjIDDict[posNum]
                batObj = batObjMgr.getBatObj(objID)
                TurnFightHeroTurnStart(turnFight, batObj, turnNum)
                if not OnObjAction(turnFight, batObj):
                    continue
                break
            if turnFight.actionIndex >= len(turnFight.actionSortList) - 1:
                turnFight.actionIndex = 0
            else:
                turnFight.actionIndex += 1
        # 回合结束
        turnFight.addTimeline() # 每回合结束算一个时间节点
        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 not turnFight.winFaction:
        OnTurnAllOver(turnFight.guid)
    return
def GetObjName(batObj):
    faction = batObj.faction
    num = batObj.lineupNum
    posNum = batObj.posNum
    heroID = batObj.heroID
    if not heroID:
        heroID = 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)
def EntryLogic(turnFight):
    ## 执行进场逻辑
    if turnFight.enterLogic:
        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)
            TurnPassive.OnTriggerPassiveEffect(turnFight, batObj, ChConfig.TriggerWay_FightStart)
    turnFight.enterLogic = True
    return
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
                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)
                    remainTime = buff.GetRemainTime()
                    if not remainTime:
                        # 永久buff不处理
                        #GameWorld.DebugLog("    永久buff不处理! curID=%s,index=%s,skillID=%s" % (curID, index, buff.GetSkillID()))
                        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, buff.GetSkillID()))
                        continue
                    updRemainTime = max(0, remainTime - passTurn)
                    buffID = buff.GetBuffID()
                    skillID = buff.GetSkillID()
                    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 __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):
    ## 大回合结束时
    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
    TurnPassive.OnTriggerPassiveEffect(turnFight, batObj, ChConfig.TriggerWay_HeroTurnStart)
    return
def AddTurnObjCureHP(curObj, srcObj, addValue, cureHP, skillID=0):
    ## 回合对象添加治疗值
    # @param curObj: 获得治疗的对象
    # @param srcObj: 来自谁的治疗
    # @param addValue: 治疗值
    # @param cureHP: 实际回血量
    if not srcObj:
        return
    curID = curObj.GetID()
    srcID = srcObj.GetID()
    updStatValue = srcObj.StatCureValue(cureHP)
    GameWorld.DebugLog("        统计治疗: curID=%s,srcID=%s,skillID=%s,addValue=%s,cureHP=%s,updStatValue=%s"
                   % (curID, srcID, skillID, addValue, cureHP, updStatValue))
    return
def AddTurnObjHurtValue(curBatObj, tagBatObj, hurtValue, lostHP, skillID=0, isBounce=False):
    ## 回合对象添加伤害值
    # @param isBounce: 是否反弹伤害
    if hurtValue <= 0:
        return
    curID = curBatObj.GetID()
    tagID = tagBatObj.GetID()
    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(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))
    else:
        # 如换血类技能,自残的伤害不算输出
        GameWorld.DebugLog("        自残: curID=%s,tagID=%s,skillID=%s,hurtValue=%s,lostHP=%s,curHP=%s"
                           % (curID, tagID, skillID, hurtValue, lostHP, curBatObj.GetHP()))
    return
def OnObjAction(turnFight, curBatObj):
    ## 战斗单位行动
    if not curBatObj:
        return
    curHP = curBatObj.GetHP()
    objID = curBatObj.GetID()
    if curHP <= 0:
        return
    turnNum = turnFight.turnNum
    objName = GetObjName(curBatObj)
    # 是否可行动状态判断
    canAction = curBatObj.CanAction()
    if not canAction:
        GameWorld.DebugLog("★回合%s %s 当前状态不可行动!" % (turnNum, objName))
        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))
    turnFight.syncObjAction(turnNum, objID)
    TurnPassive.OnTriggerPassiveEffect(turnFight, curBatObj, ChConfig.TriggerWay_HeroActionStart)
    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() not in [ChConfig.Def_SkillFuncType_TurnNormaSkill, ChConfig.Def_SkillFuncType_AngerSkill]:
            #只能主动释放普攻或怒技
            continue
        #被动技能无法使用
        if SkillCommon.isPassiveSkill(useSkill):
            continue
        #还在冷却时间内无法释放
        if useSkill.GetRemainTime():
            continue
        skillID = useSkill.GetSkillID()
        # 常规攻击优先xp
        if SkillCommon.isAngerSkill(useSkill):
            if curXP < xpMax:
                continue
            if curBatObj.IsInState(ChConfig.BatObjState_Sneer):
                GameWorld.DebugLog("嘲讽状态下,无法主动释放怒技!") # 可被动释放怒技,如怒技追击
                continue
            useCnt = -1 # xp技能优先释放
        else:
            useCnt = curBatObj.GetSkillUseCnt(skillID)
        useSkillList.append([useCnt, skillID, useSkill])
    useSkillList.sort() # 按使用次数优先升序排,使用次数低的优先判断使用
    #GameWorld.DebugLog('    技能使用顺序 = useSkillList%s' % str(useSkillList), npcID)
    for useInfo in useSkillList:
        useSkill = useInfo[-1]
        if TurnSkill.OnUseSkill(turnFight, curBatObj, useSkill):
            break
    TurnPassive.OnTriggerPassiveEffect(turnFight, curBatObj, ChConfig.TriggerWay_HeroActionEnd)
    return True
def SetObjKilled(turnFight, gameObj, killer=None, useSkill=None):
    objID = gameObj.GetID()
    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)
    return True
def OnTurnAllOver(guid):
    ## 所有回合已经全部执行完毕
    GameWorld.DebugLog("所有回合结束")
    turnFight = GetTurnFightMgr().getTurnFight(guid)
    if not turnFight:
        return
    if turnFight.winFaction:
        return
    if turnFight.playerID:
        # 玩家发起的,未击杀对方,算玩家输
        turnFight.winFaction = ChConfig.Def_FactionB
    else:
        # 系统场次,按一定规则来,这里先随机
        turnFight.winFaction = random.choice([ChConfig.Def_FactionA, ChConfig.Def_FactionB])
    DoTurnFightOver(guid)
    return
def DoTurnFightOver(guid):
    ## 执行回合战斗结算逻辑
    tfMgr = GetTurnFightMgr()
    turnFight = tfMgr.getTurnFight(guid)
    if not turnFight:
        return
    turnFight.costTime = time.time() - turnFight.startTime
    winFaction = turnFight.winFaction
    GameWorld.DebugLog("--- 战斗结束处理 ---, winFaction=%s, costTime=%ss, %s" % (winFaction, turnFight.costTime, guid))
    # 统计明细
    batObjMgr = BattleObj.GetBatObjMgr()
    statInfo = {}
    for faction in turnFight.factionDict.keys():
        if str(faction) not in statInfo:
            statInfo[str(faction)] = {}
        facStatInfo = statInfo[str(faction)]
        batFaction = turnFight.getBatFaction(faction)
        for num in batFaction.lineupDict.keys():
            if str(num) not in facStatInfo:
                facStatInfo[str(num)] = {}
            lineupStatInfo = facStatInfo[str(num)]
            batLineup = batFaction.getBatlineup(num)
            GameWorld.DebugLog("阵容明细: faction=%s,num=%s" % (faction, num))
            for posNum, objID in batLineup.posObjIDDict.items():
                batObj = batObjMgr.getBatObj(objID)
                if not batObj:
                    continue
                objID = batObj.GetID()
                npcID = batObj.GetNPCID()
                heroID = batObj.GetHeroID()
                atkHurt = batObj.hurtStat
                defHurt = batObj.defStat
                cureHP = batObj.cureStat
                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)}
    turnFight.syncState(FightState_Award, overMsg)
    # 除了主线外,其余战斗结束后均直接删除
    if not turnFight.playerID:
        tfMgr.delTurnFight(guid)
    return
def OnOver_MainLevel(curPlayer, isWin, awardItemList):
    ## 战斗结束额外处理 - 主线关卡
    if not curPlayer:
        return
    clientPack = ChPyNetSendPack.tagMCTurnFightState()
    clientPack.Clear()
    clientPack.MapID = mapID
    clientPack.FuncLineID = funcLineID
    clientPack.TagType = tagType
    clientPack.TagID = tagID
    clientPack.State = state
    clientPack.TurnNum = turnNum
    clientPack.TurnMax = turnMax
    clientPack.Msg = str(msg)
    clientPack.Len = len(clientPack.Msg)
    NetPackCommon.SendFakePack(curPlayer, clientPack)
    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
#
#struct    tagCSTurnFightReportView
#{
#    tagHead        Head;
#    char        GUID[40];    //战报guid
#};
def OnTurnFightReportView(index, clientData, tick):
    return