hxp
2025-09-01 5509de7c6274b7d47d84aa45f589490ba0eab648
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Attack/TurnAttack.py
@@ -20,11 +20,11 @@
#-------------------------------------------------------------------------------
import ChConfig
import PlayerTask
import ChPyNetSendPack
import NetPackCommon
import PlayerControl
import GameWorld
import GameObj
import FBCommon
import IpyGameDataPY
import PlayerOnline
@@ -33,8 +33,8 @@
import PyGameData
import ItemControler
import SkillCommon
import AttackCommon
import BattleObj
import TurnPassive
import TurnSkill
import TurnBuff
import ObjPool
@@ -43,7 +43,7 @@
import time
import json
FighterNPCID = 100 # 战斗NPCID,仅后端用,除怪物外,所有玩家武将均使用该NPCID
TimelineSet = 10000 # 单回合最大时间轴
PosNumMax = 10 # 最大站位编号
ActionNumStart = -1 # 起始行动位置编号,一般是从1开始,如果有加主公、红颜等则扣除相应位置值,如从0或-1开始
@@ -68,6 +68,8 @@
        self.shapeType = 0 # 阵型
        self.fightPower = 0 # 阵容总战力
        self.posObjIDDict = {} # 站位对应战斗实体 {站位编号:batObjID, ...}, 站位编号小于0为非主战单位,如主公、红颜等
        self.lingshouObjIDDict = {} # 灵兽战斗单位 {位置编号:batObjID, ...}
        self.beautyObjIDDict = {} # 红颜战斗单位 {位置编号:batObjID, ...}
        self.actionNum = ActionNumStart # 行动位置,从1开始
        return
    
@@ -85,24 +87,18 @@
        SummonLineupObjs(self, self.faction, self.num, lineupInfo, self.getPlayerID())
        return
    
    def resetLineup(self, isReborn=True):
        ## 重置阵容
        batObjMgr = BattleObj.GetBatObjMgr()
        for objID in self.posObjIDDict.values():
            batObj = batObjMgr.getBatObj(objID)
            if not batObj:
                continue
            batObj.ResetBatObj(isReborn)
        return
    def clearLineup(self):
        ## 清除阵容
        if not self.posObjIDDict:
            return
        batObjMgr = BattleObj.GetBatObjMgr()
        for objID in self.posObjIDDict.values():
            batObjMgr.delBatObj(objID)
        for objID in self.lingshouObjIDDict.values():
            batObjMgr.delBatObj(objID)
        for objID in self.beautyObjIDDict.values():
            batObjMgr.delBatObj(objID)
        self.posObjIDDict = {}
        self.lingshouObjIDDict = {}
        self.beautyObjIDDict = {}
        self.fightPower = 0
        return
    
@@ -124,12 +120,6 @@
            lineup = BatLineup(self.faction, num, self.turnFight)
            self.lineupDict[num] = lineup
        return lineup
    def resetLineups(self):
        ## 重置所有战斗阵容
        for lineup in self.lineupDict.values():
            lineup.resetLineup()
        return
    
    def clearLineups(self):
        ## 清除所有战斗阵容
@@ -154,11 +144,10 @@
        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 # 是否需要战报
@@ -166,8 +155,8 @@
        
        self.factionDict = {} # 战斗阵营 {faction:BatFaction, ...},一般是只有两个阵营,faction为1或2,每个阵营支持多个阵容
        self.actionSortList = [] # 阵容行动顺序 [[faction, num], ...]
        self.actionIndex = 0 # 行动顺序索引
        self.actionIndex = 0 # 行动顺序索引
        self.timeline = 0 # 时间轴节点  turnNum*1000+actionIndex*100++actionNum
        self.startTime = 0 # 开始时间戳,支持毫秒小数
        self.costTime = 0 # 单场战斗总耗时,支持毫秒小数
        return
@@ -186,30 +175,23 @@
        ## 一般用于玩家发起的战斗,在需要保留玩家阵容属性及状态的情况下,重置回合进入下一场战斗
        self.turnNum = 1
        self.enterLogic = False
        self.turnStart = 0
        self.turnEnd = 0
        self.winFaction = 0
        self.batBuffer = "" # 战报buffer
        self.msgDict.update(msgDict)
        self.timeline = 0
        self.startTime = time.time()
        self.costTime = 0
        return
    
    def setFactionLineup(self, faction, lineupDict, onlyReset=False):
    def setFactionLineup(self, faction, lineupDict):
        ## 设置阵营阵容
        # @param lineupDict: {阵容编号:阵容信息, ...} 每个阵营支持多个阵容,即支持多V多
        # @param onlyReset: 是否仅重置,一般用于玩家的阵容,重复利用阵容实例,
        batFaction = self.getBatFaction(faction)
        if onlyReset:
            batFaction.resetLineups()
        else:
            batFaction.clearLineups()
        batFaction.clearLineups()
        for num, lineupInfo in lineupDict.items():
            batLineup = batFaction.getBatlineup(num)
            if onlyReset and not batLineup.isEmpty():
                continue
            if not lineupInfo:
                continue
            batLineup = batFaction.getBatlineup(num)
            batLineup.setLineup(lineupInfo)
        return
    
@@ -219,19 +201,35 @@
        for batFaction in self.factionDict.values():
            faction = batFaction.faction
            for num, batLineup in batFaction.lineupDict.items():
                isPlayer = 1 if batLineup.ownerID else 0 # 玩家阵容优先攻击
                fightPower = batLineup.fightPower
                sortValue = -(faction * 10 + num)
                sortList.append([fightPower, sortValue, faction, num])
                sortList.append([isPlayer, fightPower, sortValue, faction, num])
        sortList.sort(reverse=True) # 战力高的先手
        
        self.actionIndex = 0
        self.actionSortList = []
        for _, _, faction, num in sortList:
        for _, _, _, faction, num in sortList:
            self.actionSortList.append([faction, num])
            
        GameWorld.DebugLog("阵容战力排序[fp, sortV, f, n]: %s" % sortList)
        GameWorld.DebugLog("阵容战力排序[isPlayer, fp, sortV, f, n]: %s" % sortList)
        GameWorld.DebugLog("阵容行动顺序[f, n]: %s" % self.actionSortList)
        return
    def getTurnNumStartTimelin(self, turnNum): return turnNum * TimelineSet + 0 # 每回合的时间节点起点
    def getTimeline(self): return self.timeline
    def setTimeline(self, timeline, isEmpty=False):
        '''回合战斗的时间轴节点 ,即第几回合开始,每个回合支持9999个行动节点
        @param turnNum: 第x回合
        '''
        self.timeline = timeline
        GameWorld.DebugLog("时间节点更新: %s" % self.timeline)
        if isEmpty:
            # 空位置的节点可直接跳过
            return timeline
        OnTimelineChange(self)
        return timeline
    
    def getBatFaction(self, faction=ChConfig.Def_FactionA):
        ## 默认阵营1
@@ -263,27 +261,34 @@
                    batObj = batObjMgr.getBatObj(objID)
                    if not batObj:
                        continue
                    if batObj.GetHP() > 0:
                    if batObj.IsAlive():
                        allKilled = False
                        break
                    
            if allKilled:
                self.winFaction = ChConfig.Def_FactionA if faction == ChConfig.Def_FactionB else ChConfig.Def_FactionA
                self.winFaction = ChConfig.Def_FactionA if faction == ChConfig.Def_FactionB else ChConfig.Def_FactionB
                DoTurnFightOver(self.guid)
                return self.winFaction
            
        return 0
    
    def clearBatFaction(self, faction):
        batFaction = self.getBatFaction(faction)
        batFaction.clearLineups()
        return
    def clearFight(self):
    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.turnNum = 1
        self.timeline = self.getTurnNumStartTimelin(self.turnNum)
        self.syncInit()
        return
    def isInFight(self): return self.state != -1
    
    def syncInit(self):
        ## 初始化通知
@@ -336,6 +341,7 @@
        return
    
    def syncState(self, state, msgDict={}):
        self.state = state
        msg = json.dumps(msgDict, ensure_ascii=False)
        msg = msg.replace(" ", "")
        clientPack = ChPyNetSendPack.tagMCTurnFightState()
@@ -390,7 +396,7 @@
        turnFight = self.getTurnFight(guid)
        if not turnFight:
            return
        turnFight.clearFight()
        turnFight.exitFight()
        self.turnFightDict.pop(guid, None)
        return
    
@@ -424,6 +430,8 @@
        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
    
@@ -489,9 +497,11 @@
    
    return lineupInfo
def GetNPCLineupInfo(lineupID):
def GetNPCLineupInfo(lineupID, strongerLV=0, difficulty=0):
    ## 获取NPC阵容信息
    # @param lineupID: 阵容ID
    # @param npcLV: 成长NPC等级
    # @param difficulty: 成长NPC难度系数
    # @return: 阵容全部信息json字典,前端通用格式
    ipyData = IpyGameDataPY.GetIpyGameData("NPCLineup", lineupID)
    if not ipyData:
@@ -504,9 +514,59 @@
        npcID = getattr(ipyData, "GetPosNPCID%s" % posNum)()
        if not npcID:
            continue
        npcData = NPCCommon.GetNPCDataPy(npcID)
        if not npcData:
        battleDict = GetNPCBattleDict(ipyData, npcID, strongerLV, difficulty)
        if not battleDict:
            continue
        heroDict[str(posNum)] = battleDict
    lineupInfo = {"NPCLineupID":lineupID, "Hero":heroDict}
    return lineupInfo
def GetNPCBattleDict(lineupIpyData, npcID, strongerLV=0, difficulty=0):
    ## 获取NPC战斗相关字典,支持成长NPC
    # @param strongerLV: 成长等级
    # @param difficulty: 难度系数
    npcData = NPCCommon.GetNPCDataPy(npcID)
    if not npcData:
        return
    heroID = npcData.GetRelatedHeroID()
    npcLV = npcData.GetLV()
    lvIpyData = None
    heroIpyData = IpyGameDataPY.GetIpyGameData("Hero", heroID) if heroID else None
    npcStronger = IpyGameDataPY.GetIpyGameDataNotLog("NPCStronger", npcID)
    if npcStronger and strongerLV:
        lvIpyData = IpyGameDataPY.GetIpyGameData("PlayerLV", strongerLV)
        if lvIpyData:
            npcLV = strongerLV
    if not lvIpyData:
        lvIpyData = IpyGameDataPY.GetIpyGameData("PlayerLV", npcLV)
    if heroIpyData and lvIpyData:
        skinIDList = heroIpyData.GetSkinIDList()
        skinID = skinIDList[0] if skinIDList else 0
        skillIDList = GetNPCHeroSkillIDList(heroID, heroIpyData, lvIpyData.GetReHeroBreakLV(), lvIpyData.GetReHeroAwakeLV())
    else:
        heroID = 0
        skinID = 0
        skillIDList = [] + npcData.GetSkillIDList()
    # boss额外随机技能
    bossID = lineupIpyData.GetBossID()
    if npcID == bossID:
        skillIDExList = lineupIpyData.GetSkillIDExList()
        if skillIDExList:
            randSkillIDExList = [] + list(skillIDExList)
            skillExCnt = lineupIpyData.GetSkillExCnt()
            if skillExCnt > 0 and len(randSkillIDExList) > skillExCnt:
                random.shuffle(randSkillIDExList)
                randSkillIDExList = randSkillIDExList[:skillExCnt]
            skillIDList += randSkillIDExList
            GameWorld.DebugLog("阵容boss技能: %s, 随机附加技能: %s" % (skillIDList, randSkillIDExList))
    # 成长怪属性
    batAttrDict = GetNPCStrongerAttrDict(npcID, lvIpyData, npcStronger, difficulty)
    if not batAttrDict:
        batAttrDict = {ChConfig.AttrID_Atk:npcData.GetAtk(), ChConfig.AttrID_Def:npcData.GetDef(), ChConfig.AttrID_MaxHP:npcData.GetMaxHP(), 
                       ChConfig.AttrID_FinalDamPer:npcData.GetFinalDamPer(), ChConfig.AttrID_FinalDamPerDef:npcData.GetFinalDamPerDef(), 
                       ChConfig.AttrID_MissRate:npcData.GetMissRate(), ChConfig.AttrID_MissRateDef:npcData.GetMissRateDef(), 
@@ -517,14 +577,68 @@
                       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
    battleDict = {"NPCID":npcID,
                  "HeroID":heroID,
                  "SkinID":skinID,
                  "LV":npcLV,
                  "AttrDict":{str(k):v for k, v in batAttrDict.items() if v > 0},
                  "SkillIDList":skillIDList,
                  }
    GameWorld.DebugLog("GetNPCBattleDict npcID=%s,strongerLV=%s,difficulty=%s,%s" % (npcID, strongerLV, difficulty, battleDict))
    return battleDict
def GetNPCHeroSkillIDList(heroID, heroIpyData, breakLV, awakeLV):
    ## 获取NPC对应武将的技能ID列表
    normalSkillID = heroIpyData.GetNormalSkillID()
    angerSkillID = heroIpyData.GetAngerSkillID()
    skillIDList = [normalSkillID, angerSkillID]
    breakIpyDataList = IpyGameDataPY.GetIpyGameDataList("HeroBreak", heroID)
    if breakIpyDataList:
        for breakIpyData in breakIpyDataList:
            if breakIpyData.GetBreakLV() > breakLV:
                break
            skillID = breakIpyData.GetSkillID()
            if skillID:
                skillIDList.append(skillID)
    awakeIpyDataList = IpyGameDataPY.GetIpyGameDataList("HeroAwake", heroID)
    if awakeIpyDataList:
        for awakeIpyData in awakeIpyDataList:
            if awakeIpyData.GetAwakeLV() > awakeLV:
                break
            skillID = awakeIpyData.GetSkillID()
            if skillID:
                skillIDList.append(skillID)
    return skillIDList
def GetNPCStrongerAttrDict(npcID, lvIpyData, npcStronger, difficulty):
    ## 获取NPC成长属性
    # @param strongerLV: 成长等级
    # @param difficulty: 难度系数
    batAttrDict = {}
    if not lvIpyData or not npcStronger or not difficulty:
        return batAttrDict
    lv = lvIpyData.GetLV()
    for attrID in ChConfig.CalcBattleAttrIDList:
        attrIpyData = IpyGameDataPY.GetIpyGameData("PlayerAttr", attrID)
        if not attrIpyData:
            continue
        attrName = attrIpyData.GetParameter()
        if not hasattr(lvIpyData, "GetRe%s" % attrName):
            continue
        reValue = getattr(lvIpyData, "GetRe%s" % attrName)() # 基础参考值
        ratio = getattr(npcStronger, "Get%sRatio" % attrName)() if hasattr(npcStronger, "Get%sRatio" % attrName) else 1 # 属性系数
        attrValue = int(reValue * ratio * difficulty)
        batAttrDict[attrID] = attrValue
        #GameWorld.DebugLog("    attrID=%s,attrValue=%s,reValue=%s,ratio=%s,difficulty=%s" % (attrID, attrValue, reValue, ratio, difficulty))
    GameWorld.DebugLog("NPC成长属性: npcID=%s,lv=%s,difficulty=%s,%s" % (npcID, lv, difficulty, batAttrDict))
    return batAttrDict
def SummonLineupObjs(batLineup, faction, num, lineupInfo, playerID=0):
    '''召唤阵容战斗实例
@@ -545,52 +659,60 @@
    
    batObjMgr = BattleObj.GetBatObjMgr()
    initXP = IpyGameDataPY.GetFuncCfg("AngerXP", 1)
    atkBackSkillIDList = IpyGameDataPY.GetFuncEvalCfg("ParryCfg", 3)
    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
        npcID = heroInfo.get("NPCID", 0)
        heroID = heroInfo.get("HeroID", 0)
        skinID = heroInfo.get("SkinID", 0)
        lv = heroInfo.get("LV", 1)
        heroIpyData = IpyGameDataPY.GetIpyGameData("Hero", heroID) if heroID else None
        if heroIpyData:
            atkDistType = heroIpyData.GetAtkDistType()
            objName = heroIpyData.GetName()
            country = heroIpyData.GetCountry()
            sex = heroIpyData.GetSex()
        if lineupPlayerID:
            fightPower = heroInfo.get("FightPower", 0)
            if not heroIpyData:
                continue
        else:
            npcID = heroInfo.get("NPCID", 0)
            npcDataEx = NPCCommon.GetNPCDataPy(npcID)
            if not npcDataEx:
                continue
            objName = npcDataEx.GetNPCName()
            atkDistType = npcDataEx.GetAtkDistType()
            lv = npcDataEx.GetLV()
            if not heroIpyData:
                atkDistType = npcDataEx.GetAtkDistType()
                objName = npcDataEx.GetNPCName()
                country = npcDataEx.GetCountry()
                sex = npcDataEx.GetSex()
        batObj = batObjMgr.addBatObj()
        if not batObj:
            break
        objID = batObj.GetID()
        if npcID:
            batObj.SetNPCID(npcID)
        elif lineupPlayerID:
            batObj.SetOwnerID(lineupPlayerID)
        batObj.SetTFGUID(tfGUID)
        batObj.SetName(objName)
        batObj.SetFaction(faction)
        batObj.SetLineupPos(posNum, num)
        batObj.SetFightPower(fightPower)
        batObj.SetLV(lv)
        if npcID:
            batObj.SetNPCID(npcID)
        elif lineupPlayerID:
            batObj.SetOwnerHero(lineupPlayerID, heroID, skinID)
        batObj.InitBatAttr({int(k):v for k, v in attrDict.items()}, initXP)
        batObj.SetAtkDistType(atkDistType)
        batObj.SetCountry(country)
        batObj.SetSex(sex)
        batObj.SetHero(heroID, skinID)
        
        if atkDistType == ChConfig.AtkDistType_Short:
            atkBackSkillID = atkBackSkillIDList[0] if len(atkBackSkillIDList) > 0 else 0
@@ -604,8 +726,8 @@
            skillManager.LearnSkillByID(skillID)
            
        batLineup.posObjIDDict[posNum] = objID
        GameWorld.DebugLog("AddBatObj ID:%s,faction:%s,num=%s,posNum=%s,skill=%s,atk=%s,def=%s,hp=%s"
                           % (objID, faction, num, posNum, skillIDList, batObj.GetAtk(), batObj.GetDef(), batObj.GetHP()))
        GameWorld.DebugLog("AddBatObj %s,skill=%s" % (GetObjName(batObj), skillManager.GetSkillIDList()))
        batObj.InitBatAttr({int(k):v for k, v in attrDict.items()}, initXP)
        
    return
@@ -629,6 +751,7 @@
        __doSetFightPoint(curPlayer, reqValue)
        return
    
    GameWorld.DebugLog("主线战斗请求: reqType=%s" % reqType, curPlayer.GetPlayerID())
    clientPack = ChPyNetSendPack.tagSCTurnFightReportSign()
    clientPack.Sign = 0
    NetPackCommon.SendFakePack(curPlayer, clientPack) # 标记开始
@@ -636,7 +759,7 @@
    if reqType == 2: # 前端主动请求开始关卡小怪的视为从休息中开始
        __doMainLevelWave(curPlayer, True)
    elif reqType == 3:
        __doMainBossStart(curPlayer, tick)
        __doMainBossStart(curPlayer)
    elif reqType == 4:
        __doMainFight(curPlayer, tick)
    else:
@@ -652,7 +775,7 @@
    mainFightMgr = GetMainFightMgr(curPlayer)
    turnFight = mainFightMgr.turnFight
    if turnFight:
        turnFight.clearFight()
        turnFight.exitFight()
    return
def __doSetFightPoint(curPlayer, fightPoint):
@@ -719,6 +842,8 @@
        GameWorld.DebugLog("没有设置主阵容!", playerID)
        return
    
    strongerLV = levelIpyData.GetNPCLV()
    difficulty = levelIpyData.GetDifficulty()
    mainFightMgr = GetMainFightMgr(curPlayer)
    mainFightMgr.nextTeam = False
    mainFightMgr.chapterID = chapterID
@@ -728,20 +853,24 @@
    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"
                       % (chapterID, levelNum, wave, waveMax, teamMax, mapID, funcLineID, lineupID), playerID)
    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)
    
    turnFight = mainFightMgr.turnFight
    turnFight.setTurn(mapID, funcLineID, turnMax, False, {"teamNum":teamNum, "teamMax":teamMax})
    turnFight.setFactionLineup(ChConfig.Def_FactionA, {1:lineupMainInfo}, True)
    turnFight.setFactionLineup(ChConfig.Def_FactionB, {1:GetNPCLineupInfo(lineupID)})
    turnFight.setFactionLineup(ChConfig.Def_FactionA, {1:lineupMainInfo})
    turnFight.setFactionLineup(ChConfig.Def_FactionB, {1:GetNPCLineupInfo(lineupID, strongerLV, difficulty)})
    turnFight.sortActionQueue()
    turnFight.syncInit()
    turnFight.startFight()
    __doMainFight(curPlayer)
    return
def __doMainBossStart(curPlayer, tick):
def __doMainBossStart(curPlayer):
    ## 开始挑战关卡boss
    playerID = curPlayer.GetPlayerID()
    chapterID, levelNum, wave = PlayerControl.GetMainLevelPassInfo(curPlayer)
@@ -792,7 +921,9 @@
    if not lineupMainInfo:
        GameWorld.DebugLog("没有设置主阵容!", playerID)
        return
    strongerLV = levelIpyData.GetNPCLV()
    difficulty = levelIpyData.GetDifficulty()
    mainFightMgr = GetMainFightMgr(curPlayer)
    mainFightMgr.nextTeam = False
    mainFightMgr.chapterID = chapterID
@@ -802,24 +933,38 @@
    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"
                       % (chapterID, levelNum, wave, waveMax, teamMax, mapID, funcLineID, lineupID), playerID)
    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}, True)
    turnFight.setFactionLineup(ChConfig.Def_FactionB, {1:GetNPCLineupInfo(lineupID)})
    turnFight.setFactionLineup(ChConfig.Def_FactionA, {1:lineupMainInfo})
    turnFight.setFactionLineup(ChConfig.Def_FactionB, {1:GetNPCLineupInfo(lineupID, strongerLV, difficulty)})
    turnFight.sortActionQueue()
    turnFight.syncInit()
    turnFight.startFight()
    
    # 挑战boss无中间过程,每次执行直接挑战一队结果
    __processTurnFight(turnFight.guid, tick)
    __processTurnFight(turnFight.guid)
    return
def __doMainFight(curPlayer, tick):
    ## 主线执行战斗
def __doMainFight(curPlayer, tick=0):
    '''执行主线战斗,单场战斗断电式战斗,以前端玩家手动点击节点做为断点处
    每场战斗的初始化、结束默认断点,由前端决定自动继续或者点击继续
    '''
    # 限制请求CD
    if tick:
        key = "MainFightReqTick"
        lastTick = curPlayer.GetDictByKey(key)
        if lastTick and tick - lastTick <= 1000:
            GameWorld.DebugLog("主线战斗请求CD中")
            return
        curPlayer.SetDict(key, tick)
    mainFightMgr = GetMainFightMgr(curPlayer)
    turnFight = mainFightMgr.turnFight
    
@@ -838,14 +983,16 @@
            mainFightMgr.nextTeam = False
            turnFight.resetTurn({"teamNum":teamNum})
            # 切换小队时,玩家阵容不需要处理,保留状态
            turnFight.setFactionLineup(ChConfig.Def_FactionB, {1:GetNPCLineupInfo(lineupID)})
            turnFight.setFactionLineup(ChConfig.Def_FactionB, {1:GetNPCLineupInfo(lineupID, mainFightMgr.strongerLV, mainFightMgr.difficulty)})
            turnFight.sortActionQueue()
            turnFight.syncInit()
            turnFight.startFight()
            
            if mainFightMgr.isLevelBoss():
                # 每次处理一小队的完整战斗,相当于一次完整战报
                __processTurnFight(turnFight.guid, tick)
                __processTurnFight(turnFight.guid)
                return
            else:
                __doMainFight(curPlayer)
        else:
            __doMainLevelWave(curPlayer, False)
        return
@@ -854,128 +1001,114 @@
        ## 关卡boss是一次性处理完的,一般不可能走到这里,这边做下防范
        return
    
    # 以下均是处理关卡小怪分段实时战斗
    EntryLogic(turnFight)
    # 小怪战斗,每次消耗1个战锤
    fightPoint = max(curPlayer.GetFightPoint(), 1) # 主线战斗消耗倍值,默认1
    if not PlayerControl.HaveMoney(curPlayer, ShareDefine.TYPE_Price_Xiantao, fightPoint):
        GameWorld.DebugLog("回合开始时战锤不足!")
        return
    
    # 按阵营阵容执行顺序,逐个遍历
    doCnt = 0
    doMax = (PosNumMax + 2) * len(turnFight.actionSortList) # 防止死循环,做最大循环次数限制 = (最大位置数 + 主公、红颜位置)*行动阵容数
    overLineupList = [] # 本回合已经结束行动的阵容列表 [(faction, num), ...], 所有阵容全部结束代表本回合结束
    # 以下均是处理关卡小怪分段实时战斗
    isEntry = EntryLogic(turnFight)
    # 是否开始检查断点,预判可断的点,方便前端点击体验,点下去就是玩家放的某个行动
    # 初始开始进场后,默认开始断点
    checkBreakpoint = True if isEntry else False
    
    batObjMgr = BattleObj.GetBatObjMgr()
    turnNum = turnFight.turnNum
    GameWorld.DebugLog("turnNum=%s,doMax=%s,actionIndex=%s,%s" % (turnNum, doMax, turnFight.actionIndex, turnFight.actionSortList))
    while doCnt < doMax and len(overLineupList) < len(turnFight.actionSortList):
        doCnt += 1
        turnNum = turnFight.turnNum
        # 执行回合开始逻辑
        if turnFight.turnStart < turnNum:
            GameWorld.DebugLog("执行行动: turnNum=%s, 回合开始" % (turnFight.turnNum))
            turnFight.syncState(FightState_Fighting)
            if not PlayerControl.HaveMoney(curPlayer, ShareDefine.TYPE_Price_Xiantao, fightPoint):
                GameWorld.DebugLog("回合开始时战锤不足!")
                return
            turnFight.turnStart = turnNum
            turnFight.actionIndex = 0
    turnMax = turnFight.turnMax
    for turnNum in range(turnNum, turnMax + 1):
        turnTimeline = turnFight.getTurnNumStartTimelin(turnNum) # 本回合起始时间节点
        curTimeline = turnFight.getTimeline()
        # 回合开始
        turnTimeline += 1 # 每回合开始算一个时间节点
        if curTimeline < turnTimeline:
            curTimeline = turnFight.setTimeline(turnTimeline)
            GameWorld.DebugLog("【----- 回合制战斗轮次: %s -----】 curTimeline=%s" % (turnNum, curTimeline))
            turnFight.turnNum = turnNum
            if curPlayer:
                turnFight.syncState(FightState_Fighting)
            for faction, num in turnFight.actionSortList:
                GameWorld.DebugLog("回合开始逻辑: turnNum=%s,faction=%s, num=%s" % (turnNum, faction, num))
                batFaction = turnFight.getBatFaction(faction)
                batLineup = batFaction.getBatlineup(num)
                batLineup.actionNum = ActionNumStart
                batLineup.actionNum = 1
                for objID in batLineup.posObjIDDict.values():
                    batObj = batObjMgr.getBatObj(objID)
                    TurnFightObjPerTurnStart(turnFight, batObj, turnNum)
                    TurnFightPerTurnBigStart(turnFight, batObj, turnNum)
                    
        if turnFight.actionIndex >= len(turnFight.actionSortList):
            turnFight.actionIndex = 0
        playerHeroAtk = False # 玩家阵容行动标记
        faction, num = turnFight.actionSortList[turnFight.actionIndex]
        batFaction = turnFight.getBatFaction(faction)
        batLineup = batFaction.getBatlineup(num)
        if batLineup.actionNum > max(batLineup.posObjIDDict):
            if (faction, num) not in overLineupList:
                overLineupList.append((faction, num))
            turnFight.actionIndex += 1
            continue
        if faction == ChConfig.Def_FactionA:
            if not PlayerControl.HaveMoney(curPlayer, ShareDefine.TYPE_Price_Xiantao, fightPoint):
                GameWorld.DebugLog("战锤不足!")
                return
        GameWorld.DebugLog("执行行动: turnNum=%s,faction=%s,num=%s,actionNum=%s" % (turnFight.turnNum, faction, num, batLineup.actionNum))
        # 主公
        if batLineup.actionNum == -1:
            pass
        # 红颜
        elif batLineup.actionNum == 0:
            pass
        # 灵兽
        
        # 武将
        elif batLineup.actionNum > 0:
            for posNum in range(batLineup.actionNum, PosNumMax + 1):
                batLineup.actionNum = posNum
                if posNum not in batLineup.posObjIDDict:
                    continue
                objID = batLineup.posObjIDDict[posNum]
                batObj = batObjMgr.getBatObj(objID)
                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
        # 武将
        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):
                turnTimeline += 1 # 每个武将位算一个时间节点
                if turnTimeline <= curTimeline:
                    # 该时间节点已经处理过了
                    GameWorld.DebugLog("该时间节点已经处理过了! turnTimeline=%s <= %s" % (turnTimeline, curTimeline))
                    continue
                if posNum not in batLineup.posObjIDDict:
                    batLineup.actionNum = posNum + 1
                    curTimeline = turnFight.setTimeline(turnTimeline, True)
                    continue
                objID = batLineup.posObjIDDict[posNum]
                batObj = batObjMgr.getBatObj(objID)
                # 玩家自己阵营,预判可否行动
                if checkBreakpoint and faction == ChConfig.Def_FactionA and batObj:
                    if batObj.CanAction():
                        GameWorld.DebugLog("玩家武将已经行动过了,且有下一个可行动的武将,断点: curTimeline=%s,nextPosNum=%s" % (curTimeline, posNum))
                        return
                batLineup.actionNum = posNum + 1
                curTimeline = turnFight.setTimeline(turnTimeline)
                TurnFightHeroTurnStart(turnFight, batObj, turnNum)
                if not OnObjAction(turnFight, batObj):
                    continue
                if not checkBreakpoint and faction == ChConfig.Def_FactionA:
                    checkBreakpoint = True
            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():
                    pass
            if turnFight.checkOverByKilled():
                return
        if turnNum < turnFight.turnMax:
            turnFight.turnNum += 1
        else:
            OnTurnAllOver(turnFight.guid)
                    batObj = batObjMgr.getBatObj(objID)
                    TurnFightPerTurnBigEnd(turnFight, batObj, turnNum)
        if turnFight.checkOverByKilled():
            break
    if not turnFight.winFaction:
        OnTurnAllOver(turnFight.guid)
    return
def __processTurnFight(guid, tick):
def __processTurnFight(guid):
    ## 一次性处理完一个小队的战斗
    turnFight = GetTurnFightMgr().getTurnFight(guid)
    curPlayer = turnFight.curPlayer
@@ -985,6 +1118,8 @@
    for turnNum in range(1, turnMax + 1):
        turnFight.turnNum = turnNum
        GameWorld.DebugLog("【----- 回合制战斗轮次: %s -----】" % turnNum)
        curTimeline = turnFight.getTurnNumStartTimelin(turnNum) # 本回合起始时间节点
        curTimeline = turnFight.setTimeline(curTimeline + 1)
        if curPlayer:
            turnFight.syncState(FightState_Fighting)
            
@@ -996,19 +1131,11 @@
            batLineup.actionNum = 1
            for objID in batLineup.posObjIDDict.values():
                batObj = batObjMgr.getBatObj(objID)
                TurnFightObjPerTurnStart(turnFight, batObj, turnNum)
                TurnFightPerTurnBigStart(turnFight, batObj, turnNum)
                
        # 主公
        for faction, num in turnFight.actionSortList:
            GameWorld.DebugLog("主公逻辑: turnNum=%s,faction=%s, num=%s" % (turnNum, faction, num))
            batFaction = turnFight.getBatFaction(faction)
            batLineup = batFaction.getBatlineup(num)
        # 红颜
        for faction, num in turnFight.actionSortList:
            GameWorld.DebugLog("红颜逻辑: turnNum=%s,faction=%s, num=%s" % (turnNum, faction, num))
            batFaction = turnFight.getBatFaction(faction)
            batLineup = batFaction.getBatlineup(num)
        # 灵兽
            
        if turnFight.checkOverByKilled():
            break
@@ -1024,9 +1151,12 @@
            for posNum in range(batLineup.actionNum, PosNumMax + 1):
                batLineup.actionNum = posNum + 1
                if posNum not in batLineup.posObjIDDict:
                    curTimeline = turnFight.setTimeline(curTimeline + 1, True)
                    continue
                curTimeline = turnFight.setTimeline(curTimeline + 1) # 每个武将位算一个时间节点
                objID = batLineup.posObjIDDict[posNum]
                batObj = batObjMgr.getBatObj(objID)
                TurnFightHeroTurnStart(turnFight, batObj, turnNum)
                if not OnObjAction(turnFight, batObj):
                    continue
                
@@ -1038,13 +1168,15 @@
                turnFight.actionIndex += 1
                
        # 回合结束
        curTimeline = turnFight.setTimeline(curTimeline + 1) # 每回合结束算一个时间节点
        for faction, num in turnFight.actionSortList:
            GameWorld.DebugLog("回合结束逻辑: turnNum=%s,faction=%s, num=%s" % (turnNum, faction, num))
            batFaction = turnFight.getBatFaction(faction)
            batLineup = batFaction.getBatlineup(num)
            for objID in batLineup.posObjIDDict.values():
                pass
                batObj = batObjMgr.getBatObj(objID)
                TurnFightPerTurnBigEnd(turnFight, batObj, turnNum)
        if turnFight.checkOverByKilled():
            break
        
@@ -1052,33 +1184,18 @@
        OnTurnAllOver(turnFight.guid)
    return
#// B4 10 回合制战斗 #tagCMTurnFight
#
#struct    tagCMTurnFight
#{
#    tagHead        Head;
#    DWORD        MapID;        // 自定义地图ID,可用于绑定战斗地图场景功能(如主线关卡、主线boss、爬塔、竞技场等)
#    DWORD        FuncLineID;    // MapID对应的扩展值,如具体某个关卡等
#    BYTE        TagType;    // 目标类型,0-NPC阵容,1-玩家
#    DWORD        TagID;    // 目标类型对应的ID,如阵容ID或玩家ID
#    BYTE        ValueCount;
#    DWORD        ValueList[ValueCount]; // 附加值列表,可选,具体含义由MapID决定
#};
def OnTurnFight(index, clientData, tick):
    #curPlayer = GameWorld.GetPlayerManager().GetPlayerByIndex(index)
    #mapID = clientData.MapID
    #funcLineID = clientData.FuncLineID
    return
def GetObjName(batObj):
    faction = batObj.faction
    num = batObj.lineupNum
    posNum = batObj.posNum
    heroID = batObj.heroID
    if not heroID:
        heroID = batObj.npcID
    npcID = batObj.npcID
    objName = GameWorld.CodeToGbk(batObj.GetName())
    return "%s%s-%s [%s-%s] %s" % ("A" if faction == ChConfig.Def_FactionA else "B", num, posNum, batObj.GetID(), heroID, objName)
    if heroID:
        objName += " Hero:%s" % heroID
    if npcID:
        objName += " NPC:%s" % npcID
    return "%s%s-P%s ID:%s %s" % ("A" if faction == ChConfig.Def_FactionA else "B", num, posNum, batObj.GetID(), objName)
def EntryLogic(turnFight):
    ## 执行进场逻辑
@@ -1086,68 +1203,136 @@
        return
    GameWorld.DebugLog("执行进场逻辑...")
    
    batObjMgr = BattleObj.GetBatObjMgr()
    for faction, num in turnFight.actionSortList:
        batFaction = turnFight.getBatFaction(faction)
        batLineup = batFaction.getBatlineup(num)
        batLineup.actionNum = ActionNumStart
        for objID in batLineup.posObjIDDict.values():
            batObj = batObjMgr.getBatObj(objID)
            TurnPassive.OnTriggerPassiveEffect(turnFight, batObj, ChConfig.TriggerWay_FightStart)
    turnFight.enterLogic = True
    return True
def OnTimelineChange(turnFight):
    ## 每个时间节点变化时处理
    nowTimeline = turnFight.getTimeline()
    batObjMgr = BattleObj.GetBatObjMgr()
    for batFaction in turnFight.factionDict.values():
        for batLineup in batFaction.lineupDict.values():
            for objID in batLineup.posObjIDDict.values():
                batObj = batObjMgr.getBatObj(objID)
                #GameWorld.DebugLog("OnTimelineChange! objID=%s" % (objID))
                if not batObj or not batObj.IsAlive():
                    continue
                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)
    return
def TurnFightObjPerTurnStart(turnFight, batObj, turnNum):
    ## 回合制战斗实例 - 每回合开始时处理
def __calcPassturn(calcTimeline, nowTimeline, equalOK):
    ## 计算已经过了的回合数
    # @param equalOK: 时间节点相同时是否算1回合,一般技能可以算,buff不算
    calcTurnNum = calcTimeline / TimelineSet
    calcTimeNode = calcTimeline % TimelineSet
    nowTurnNum = nowTimeline / TimelineSet
    nowTimeNode = nowTimeline % TimelineSet
    if equalOK:
        if nowTimeNode >= calcTimeNode:
            return max(0, nowTurnNum - calcTurnNum)
        return max(0, nowTurnNum - calcTurnNum - 1)
    else:
        if nowTimeNode > calcTimeNode:
            return max(0, nowTurnNum - calcTurnNum)
        return max(0, nowTurnNum - calcTurnNum - 1)
    return 0
def TurnFightPerTurnBigStart(turnFight, batObj, turnNum):
    ## 大回合开始时
    if not batObj:
        return
    
    if batObj.GetHP() <= 0:
        return
    
    curID = batObj.GetID()
    buffMgr = batObj.GetBuffManager()
    GameWorld.DebugLog("更新buff: curID=%s,buffCount=%s" % (curID, buffMgr.GetBuffCount()))
    for index in range(buffMgr.GetBuffCount()):
        buff = buffMgr.GetBuffByIndex(index)
        curRemainTime = buff.GetRemainTime()
        if not curRemainTime:
            # 永久buff不处理
            continue
        buffID = buff.GetBuffID()
        updRemainTime = curRemainTime - 1
        GameWorld.DebugLog("    更新buff剩余回合数: buffID=%s,updRemainTime=%s" % (buffID, updRemainTime))
        if updRemainTime > 0:
            buff.SetRemainTime(curRemainTime - 1)
            TurnBuff.SyncBuffRefresh(turnFight, batObj, buff)
        else:
            TurnBuff.SyncBuffDel(turnFight, batObj, buffID)
#    SetTimeline(gameObj, turnNum, 0)
#    # 重置连击、反击数
#    gameObj.SetDict(ChConfig.Def_Obj_Dict_TurnComboNum, 0)
#    gameObj.SetDict(ChConfig.Def_Obj_Dict_TurnAtkBackNum, 0)
#    gameObj.SetDict(ChConfig.Def_Obj_Dict_TurnParryNum, 0)
#    gameObj.SetDict(ChConfig.Def_Obj_Dict_TurnMissNum, 0)
#    gameObj.SetDict(ChConfig.Def_Obj_Dict_TurnAtkAddXPCount, 0)
#    gameObj.SetDict(ChConfig.Def_Obj_Dict_TurnXPUseState, 0)
#    gameObj.SetDict(ChConfig.Def_PlayerKey_AttrFaintCD, 0) # 击晕CD
#
#    objType = gameObj.GetGameObjType()
#    objID = gameObj.GetID()
#    objName = GetObjName(gameObj)
#    GameWorld.DebugLog("ObjPerTurnStart: 回合%s, %s objType-ID-HP(%s-%s-%s)" % (turnNum, objName, objType, objID, GameObj.GetHP(gameObj)))
#
#    # 每回合开始减技能CD
#    skillManager = gameObj.GetSkillManager()
#    for i in range(skillManager.GetSkillCount()):
#        skill = skillManager.GetSkillByIndex(i)
#        remainTime = skill.GetRemainTime()
#        if not remainTime:
#            continue
#        skillID = skill.GetSkillID()
#        updRemainTime = max(0, remainTime - ChConfig.Def_PerTurnTick)
#        skill.SetRemainTime(updRemainTime)
#        GameWorld.DebugLog("    skillID=%s,remainTime=%s,updRemainTime=%s" % (skillID, remainTime, updRemainTime))
#
#    # 刷新定时处理的buff效果
#    SkillShell.ProcessPersistBuff(gameObj, tick)
#
#    PassiveBuffEffMng.OnPassiveSkillTrigger(gameObj, tagObj, None, ChConfig.TriggerType_TurnNum, tick)
#
#    __logGameObjAttr(gameObj)
    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):
@@ -1166,19 +1351,20 @@
        
    return
def AddTurnObjHurtValue(curBatObj, tagBatObj, hurtValue, lostHP, curSkill=None, isBounce=False):
def AddTurnObjHurtValue(curBatObj, tagBatObj, hurtValue, lostHP, skillID=0, isBounce=False):
    ## 回合对象添加伤害值
    # @param isBounce: 是否反弹伤害
    if hurtValue <= 0:
        return
    curID = curBatObj.GetID()
    tagID = tagBatObj.GetID()
    skillID = curSkill.GetSkillID() if curSkill else 0
    if curID != tagID:
        updStatValue = curBatObj.StatHurtValue(hurtValue)
        GameWorld.DebugLog("        统计伤血: curID=%s,tagID=%s,skillID=%s,hurtValue=%s,lostHP=%s,updStatValue=%s,tagHP=%s,isBounce=%s" 
                       % (curID, tagID, skillID, hurtValue, lostHP, updStatValue, tagBatObj.GetHP(), isBounce))
        
        if tagBatObj:
            updStatValue = tagBatObj.StatDefValue(lostHP)
            updStatValue = tagBatObj.StatDefValue(hurtValue)
            GameWorld.DebugLog("        统计承伤: curID=%s,tagID=%s,skillID=%s,hurtValue=%s,lostHP=%s,updStatValue=%s,curHP=%s,isBounce=%s" 
                           % (tagID, curID, skillID, hurtValue, lostHP, updStatValue, tagBatObj.GetHP(), isBounce))
            
@@ -1202,16 +1388,18 @@
    objName = GetObjName(curBatObj)
    
    # 是否可行动状态判断
    canAction = True
    canAction = curBatObj.CanAction()
    if not canAction:
        GameWorld.DebugLog("★回合%s %s 当前状态不可行动!" % (turnNum, objName))
        return
    
    GameWorld.DebugLog("★回合%s %s 行动 : curHP=%s" % (turnNum, objName, curHP))
    atk = curBatObj.GetAtk()
    curXP = curBatObj.GetXP()
    GameWorld.DebugLog("★回合%s %s 行动 : atk=%s,curHP=%s/%s,curXP=%s" % (turnNum, objName, atk, curHP, curBatObj.GetMaxHP(), curXP))
    turnFight.syncObjAction(turnNum, objID)
    
    curXP = curBatObj.GetXP()
    TurnPassive.OnTriggerPassiveEffect(turnFight, curBatObj, ChConfig.TriggerWay_HeroActionStart)
    xpMax = IpyGameDataPY.GetFuncCfg("AngerXP", 2) 
    skillManager = curBatObj.GetSkillManager()
    useSkillList = []
@@ -1220,8 +1408,8 @@
        useSkill = skillManager.GetSkillByIndex(index)
        if not useSkill:
            continue
        if useSkill.GetFuncType() in [ChConfig.Def_SkillFuncType_AtkbackSkill]:
            #基础普攻不能主动释放,目前仅用于反击
        if useSkill.GetFuncType() not in [ChConfig.Def_SkillFuncType_TurnNormaSkill, ChConfig.Def_SkillFuncType_AngerSkill]:
            #只能主动释放普攻或怒技
            continue
        #被动技能无法使用
        if SkillCommon.isPassiveSkill(useSkill):
@@ -1234,6 +1422,9 @@
        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)
@@ -1245,215 +1436,28 @@
    for useInfo in useSkillList:
        useSkill = useInfo[-1]
        if TurnSkill.OnUseSkill(turnFight, curBatObj, useSkill):
            return True
            break
        
    return
def DoAttack(curBatObj, tagBatObj, tick, turnBattleType=ChConfig.TurnBattleType_Normal, useSkill=None):
#    curID = curBatObj.GetID()
#    npcID = curBatObj.GetNPCID()
#    objName = GetObjName(curBatObj)
#    GameWorld.DebugLog("    ● %s DoAttack: curID=%s,,turnBattleType=%s" % (objName, curID, turnBattleType))
#
#    atkOK = False
#    curBatObj.SetDict(ChConfig.Def_Obj_Dict_TurnBattleType, turnBattleType)
#
#    if turnBattleType == ChConfig.TurnBattleType_AtkBack:
#        if not tagBatObj:
#            return
#        skillManager = curBatObj.GetSkillManager()
#        for index in range(0, skillManager.GetSkillCount()):
#            skill = skillManager.GetSkillByIndex(index)
#            if not skill:
#                continue
#            if skill.GetFuncType() == ChConfig.Def_SkillFuncType_AtkbackSkill:
#                useSkill = skill
#                break
#        atkOK = SkillShell.DoLogic_UseSkill(curBatObj, tagBatObj, useSkill, tick)
#    elif turnBattleType == ChConfig.TurnBattleType_Combo:
#        if not tagBatObj:
#            return
#        atkOK = SkillShell.DoLogic_UseSkill(curBatObj, tagBatObj, useSkill, tick)
#    else:
#        curXP = GameObj.GetXP(curBatObj)
#        xpMax = IpyGameDataPY.GetFuncCfg("AngerXP", 2)
#        skillManager = curBatObj.GetSkillManager()
#        useSkillList = []
#        #GameWorld.DebugLog('skillCount=%s' % skillManager.GetSkillCount(), npcID)
#        for index in range(0, skillManager.GetSkillCount()):
#            useSkill = skillManager.GetSkillByIndex(index)
#            if not useSkill:
#                continue
#            if useSkill.GetFuncType() in [ChConfig.Def_SkillFuncType_AtkbackSkill]:
#                #基础普攻不能主动释放,目前仅用于反击
#                continue
#            #被动技能无法使用
#            if SkillCommon.isPassiveSkill(useSkill):
#                continue
#            #还在冷却时间内无法释放
#            if useSkill.GetRemainTime():
#                continue
#            skillID = useSkill.GetSkillID()
#            # 常规攻击优先xp
#            if SkillCommon.isAngerSkill(useSkill) and turnBattleType == ChConfig.TurnBattleType_Normal:
#                if curXP < xpMax:
#                    continue
#                useCnt = -1 # xp技能优先释放
#            else:
#                useCnt = curBatObj.GetDictByKey(ChConfig.Def_NPC_Dict_SkillUseCnt % skillID) # 该技能已使用次数
#            useSkillList.append([useCnt, index, skillID, useSkill])
#
#        useSkillList.sort() # 按使用次数优先升序排,使用次数低的优先判断使用
#        #GameWorld.DebugLog('    技能使用顺序 = useSkillList%s' % str(useSkillList), npcID)
#
#        for useInfo in useSkillList:
#            useSkill = useInfo[-1]
#            #skillID = useSkill.GetSkillID()
#            atkOK, tagBatObj = OnUseSkill(curBatObj, useSkill, tick)
#            if atkOK:
#                break
#
#    curBatObj.SetDict(ChConfig.Def_Obj_Dict_TurnBattleType, 0) # 无论攻击成功与否都重置战斗类型
#
#    tagID = 0
#    tagHP = 0
#    if tagObj:
#        tagID = tagObj.GetID()
#        tagHP = GameObj.GetHP(tagObj)
#    GameWorld.DebugLog('        atkOK=%s,tagID=%s,tagHP=%s' % (atkOK, tagID, tagHP), npcID)
#    if not atkOK:
#        return
#
#    if not tagObj:
#        return
#
#    if GameObj.GetFaction(curNPC) == GameObj.GetFaction(tagObj):
#        # 同阵营直接返回,不处理连击、反击
#        return True
#
#    curHP = GameObj.GetHP(curNPC)
#    tagHP = GameObj.GetHP(tagObj)
#    tagID = tagObj.GetID()
#    GameWorld.DebugLog("            curID-HP=(%s-%s),tagID-HP=(%s-%s)" % (curID, curHP, tagID, tagHP))
#    if tagHP <= 0 or curHP <= 0:
#        return True
#
#    # 反击,反击可打断连击,所以优先判断
#    if CanAtkBack(curNPC, tagObj):
#        DoAttack(tagObj, curNPC, tick, ChConfig.TurnBattleType_AtkBack)
#        return True
#
#    # 连击
#    if CanCombo(curNPC, tagObj):
#        DoAttack(curNPC, tagObj, tick, ChConfig.TurnBattleType_Combo, useSkill)
#
    TurnPassive.OnTriggerPassiveEffect(turnFight, curBatObj, ChConfig.TriggerWay_HeroActionEnd)
    return True
def CanAtkBack(atkObj, defObj):
    ## 可否反击
    posNum = atkObj.GetDictByKey(ChConfig.Def_Obj_Dict_TurnFightPosInfo) % 100
    if posNum <= 0:
        GameWorld.DebugLog("            被非主战单位攻击时无法触发反击: atkID=%s,posNum=%s" % (atkObj.GetID(), posNum))
        return False
    atkDistType = AttackCommon.GetAtkDistType(defObj)
    if atkDistType == ChConfig.AtkDistType_Long:
        if not IpyGameDataPY.GetFuncCfg("ParryCfg", 2):
            GameWorld.DebugLog("            远程单位不可反击: defID=%s" % (defObj.GetID()))
            return False
    defAtkBackRate = GameObj.GetAtkBackRate(defObj) # 防方反击率
    atkBackNum = defObj.GetDictByKey(ChConfig.Def_Obj_Dict_TurnAtkBackNum) # 已反击次数
    if atkBackNum > 10:
        # 内置最高反击数防范
        return False
    if atkBackNum > 0:
        validPerList = IpyGameDataPY.GetFuncEvalCfg("TurnFight", 4)
        vaildPer = validPerList[atkBackNum - 1] if len(validPerList) >= atkBackNum else 0
        defAtkBackRate = int(defAtkBackRate * vaildPer / 100.0)
    atkAtkBackDefRate = GameObj.GetAtkBackDefRate(atkObj) # 攻方抵抗反击率
    atkBackRate = max(0, defAtkBackRate - atkAtkBackDefRate)
    if atkBackRate <= 0 or not GameWorld.CanHappen(atkBackRate):
        GameWorld.DebugLog("            无法反击: defID=%s,atkBackNum=%s,atkBackRate=%s=(defAtkBackRate=%s - atkAtkBackDefRate=%s)"
                           % (defObj.GetID(), atkBackNum, atkBackRate, defAtkBackRate, atkAtkBackDefRate))
        return False
    GameWorld.DebugLog("            可以反击: defID=%s,atkBackNum=%s,atkBackRate=%s=(defAtkBackRate=%s - atkAtkBackDefRate=%s)"
                       % (defObj.GetID(), atkBackNum, atkBackRate, defAtkBackRate, atkAtkBackDefRate))
    return True
def CanCombo(atkObj, defObj):
    ## 可否连击
    atkComboRate = GameObj.GetComboRate(atkObj) # 攻方连击率
    comboNum = atkObj.GetDictByKey(ChConfig.Def_Obj_Dict_TurnComboNum) # 已连击次数
    if comboNum > 10:
        # 内置最高连击数防范
        return False
    if comboNum > 0:
        validPerList = IpyGameDataPY.GetFuncEvalCfg("TurnFight", 3)
        vaildPer = validPerList[comboNum - 1] if len(validPerList) >= comboNum else 0
        atkComboRate = int(atkComboRate * vaildPer / 100.0)
    defComboReduce = GameObj.GetComboDefRate(defObj) # 防方抵抗连击率
    comboRate = max(0, atkComboRate - defComboReduce)
    if comboRate <= 0 or not GameWorld.CanHappen(comboRate):
        GameWorld.DebugLog("            无法连击: atkID=%s,comboNum=%s,comboRate=%s=(atkComboRate=%s - defComboReduce=%s)"
                           % (atkObj.GetID(), comboNum, comboRate, atkComboRate, defComboReduce))
        return False
    GameWorld.DebugLog("            可以连击: atkID=%s,comboNum=%s,comboRate=%s=(atkComboRate=%s - defComboReduce=%s)"
                       % (atkObj.GetID(), comboNum, comboRate, atkComboRate, defComboReduce))
    return True
#def GetEnemyObj(curNPC, ruleType=0):
#    ## 获取一个敌对单位,针对所有敌对阵容主战单位,优先选择对位阵容单位,仅限活着的单位
#    # @param ruleType: 选择规则,默认0任意,1-最低血量;2-最高血量
#    objID = curNPC.GetID()
#    faction = GameObj.GetFaction(curNPC)
#    posInfo = curNPC.GetDictByKey(ChConfig.Def_Obj_Dict_TurnFightPosInfo)
#    num = posInfo / 100 # 阵容编号
#    posNum = posInfo % 100 # 所在阵容站位
#    turnFight = GetTurnFightMgr().getTurnFight(curNPC.GetTFGUID())
#    if not turnFight:
#        return
#
#    tagBatFaction = turnFight.getBatFaction(Def_FactionB if faction == Def_FactionA else Def_FactionA)
#
#    tagLineupNumList = [num] # 目标阵容选择顺序,优先对位阵容,再其他阵容
#    if num in tagBatFaction.lineupDict:
#        tagLineupNumList = [num]
#    for tagNum in tagBatFaction.lineupDict.keys():
#        if tagNum not in tagLineupNumList:
#            tagLineupNumList.append(tagNum)
#
#    batObjMgr = BattleObj.GetBatObjMgr()
#    for tagNum in tagLineupNumList:
#        tagLineup = tagBatFaction.getBatlineup(tagNum)
#        tagPosNumList = [posNum] # 优先对位位置,再其他位置按顺序遍历
#        pNumList = tagLineup.posObjIDDict.keys()
#        pNumList.sort()
#        for pNum in pNumList:
#            if pNum > 0 and pNum not in tagPosNumList:
#                tagPosNumList.append(pNum)
#        for pNum in tagPosNumList:
#            batObj = batObjMgr.getBatObj(objID)
#            if not batObj:
#                continue
#            if batObj.GetHP( )<= 0:
#                continue
#            return batObj
#    return
def SetObjKilled(turnFight, gameObj, killer=None, useSkill=None):
    objID = gameObj.GetID()
    GameWorld.DebugLog("        %s 回合战斗主体被击杀: curID=%s" % (GetObjName(gameObj), objID))
    gameObj.SetHP(0)
    killerObjID = killer.GetID() if killer else 0
    skillID = useSkill.GetSkillID() if useSkill else 0
    GameWorld.DebugLog("        %s 回合战斗主体被击杀: curID=%s,killerObjID=%s,skillID=%s" % (GetObjName(gameObj), objID, killerObjID, skillID))
    gameObj.SetDead()
    
    clientPack = ObjPool.GetPoolMgr().acquire(ChPyNetSendPack.tagMCTurnFightObjDead)
    clientPack.ObjID = objID
    clientPack.KillerObjID = killerObjID
    clientPack.SkillID = skillID
    turnFight.addBatPack(clientPack)
    turnFight.addBatPack(clientPack)
    curPlayer = turnFight.curPlayer
    # 暂时只算主线小怪
    if curPlayer and turnFight.mapID == ChConfig.Def_FBMapID_Main and gameObj.GetFaction() != ChConfig.Def_FactionA:
        PlayerTask.AddTaskValue(curPlayer, ChConfig.TaskType_KillNPC, 1)
    return True
def OnTurnAllOver(guid):
@@ -1485,7 +1489,7 @@
    
    turnFight.costTime = time.time() - turnFight.startTime
    winFaction = turnFight.winFaction
    GameWorld.DebugLog("--- 战斗结束处理 --- %s, winFaction=%s, costTime=%ss" % (guid, winFaction, turnFight.costTime))
    GameWorld.DebugLog("--- 战斗结束处理 ---, winFaction=%s, costTime=%ss, %s" % (winFaction, turnFight.costTime, guid))
    
    # 统计明细
    batObjMgr = BattleObj.GetBatObjMgr()
@@ -1511,7 +1515,7 @@
                atkHurt = batObj.hurtStat
                defHurt = batObj.defStat
                cureHP = batObj.cureStat
                GameWorld.DebugLog("    Pos:%s ID=%s-%s-%s,,HP=%s/%s, 输出=%s,承伤=%s,治疗=%s"
                GameWorld.DebugLog("    Pos:%s ID=%s,npcID=%s,heroID=%s,HP=%s/%s, 输出=%s,承伤=%s,治疗=%s"
                                   % (posNum, objID, npcID, heroID, batObj.GetHP(), batObj.GetMaxHP(), atkHurt, defHurt, cureHP))
                lineupStatInfo[str(posNum)] = {"ObjID":objID, "HeroID":heroID, "NPCID":npcID, "AtkHurt":atkHurt, "DefHurt":defHurt, "CureHP":cureHP}