ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Attack/MirrorAttack.py
@@ -29,6 +29,7 @@
import OperControlManager
import PlayerViewCacheTube
import PassiveBuffEffMng
import GameWorldProcess
import ChNetSendPack
import IpyGameDataPY
import AttackCommon
@@ -43,6 +44,8 @@
import time
Def_StateTimeList = [3, 60, 10] # 默认阶段时长,秒
class MirrorBattle():
    ## 某场战斗
    
@@ -51,6 +54,7 @@
        return
    
    def Clear(self):
        self.isChangeMap = 0 # 是否切图战斗的
        self.isSysbg = False # 是否系统后台进行战斗的,玩家无感知,仅知道结果
        self.requestID = 0 # 请求ID,一般是玩家ID或者系统自定的ID,如某一场PK的标识信息
        self.playerID = 0 # 所属玩家ID,可能为0
@@ -58,9 +62,8 @@
        self.mapID = 0 # 功能mapID,代表某一个功能
        self.funcLineID = 0
        self.batState = 0 # 状态:0-无;1-准备中;2-战斗中;3-快速结束中,4-结束
        self.startTick = 0 # 开始战斗tick
        self.fightTickMax = 0 # 战斗最大时长,tick
        self.fightTickRemain = 0 # 剩余战斗时长,tick
        self.stateTick = 0 # 阶段状态变更时tick
        self.stateTickRemain = 0 # 阶段剩余时长,tick
        self.mirrorIDDict = {} # 该场所有玩家镜像实例ID对应真实ID {playerID:realPlayerID, ...}
        self.realIDDict = {} # 该场所有真实玩家对应初始信息 {playerID:{k:v, ...}, ...}
        self.playerFactionDict = {} # 该场所有玩家阵营信息,真实玩家+镜像玩家 {playerID:faction, ...}
@@ -72,15 +75,79 @@
        self.isQuick = False # 是否快速战斗结束的
        self.isWin = False # 是否获胜
        self.winFaction = 0 # 获胜阵营
        # 结算时血量明细
        self.curHP = 0
        self.curHPMax = 0
        self.tagHP = 0
        self.tagHPMax = 0
        return
    
    def GetTagPlayerID(self): return self.tagPlayerIDList[0] if self.tagPlayerIDList else 0
    
    def CaclFightTick(self, tick):
        ## 计算战斗时长,返回剩余时长tick
        fightTickCost = tick - self.startTick # 已过战斗时长
        self.fightTickRemain = max(0, self.fightTickMax - fightTickCost) # 剩余战斗时长
        return self.fightTickRemain
    def SyncFBStepTime(self, tick):
        #curPlayer.Sync_TimeTick(IPY_GameWorld.tttWaitStart, 0, max(notify_tick, 0), True)
        if self.isSysbg:
            return
        self.CaclStateTick(tick)
        state = self.batState
        if state == ChConfig.Def_MirrorBatState_Prepare:
            self.__SyncRealPlayerTick(ChConfig.tttWaitStart, self.stateTickRemain)
        elif state == ChConfig.Def_MirrorBatState_Fight:
            self.__SyncRealPlayerTick(ChConfig.tttTowerTake, self.stateTickRemain)
        elif state == ChConfig.Def_MirrorBatState_Over:
            self.__SyncRealPlayerTick(ChConfig.tttLeaveMap, self.stateTickRemain)
        return
    def __SyncRealPlayerTick(self, msgType, msgTick):
        playerMgr = GameWorld.GetMapCopyPlayerManager()
        for playerID in self.realIDDict.keys():
            curPlayer = playerMgr.FindPlayerByID(playerID)
            if not curPlayer:
                continue
            curPlayer.Sync_TimeTick(msgType, 0, msgTick, True)
        return
    def ChangeBattleState(self, state, tick):
        GameWorld.DebugLog("镜像战斗阶段变更: mapID=%s,state=%s" % (self.mapID, state), self.battleID)
        self.batState = state
        self.stateTick = tick
        self.stateTickRemain = self.GetStateTickMax()
        self.SyncFBStepTime(tick)
        return
    def CaclStateTick(self, tick):
        ## 计算状态时长,返回剩余时长tick
        stateTickMax = self.GetStateTickMax()
        passTick = tick - self.stateTick # 已过时长
        self.stateTickRemain = max(0, stateTickMax - passTick) # 剩余时长
        return self.stateTickRemain
    def GetStateTickRemain(self): return self.stateTickRemain
    def GetStateTickMax(self):
        fightTimeLimitDict = IpyGameDataPY.GetFuncEvalCfg("MirrorAttack", 1, {})
        stateTimeList = fightTimeLimitDict.get(self.mapID, []) # 阶段时长列表
        if not stateTimeList or len(stateTimeList) != 3:
            stateTimeList = Def_StateTimeList
        state = self.batState
        stateTime = 0
        if state == ChConfig.Def_MirrorBatState_Prepare:
            stateTime = stateTimeList[0]
        elif state == ChConfig.Def_MirrorBatState_Fight:
            stateTime = stateTimeList[1]
        elif state == ChConfig.Def_MirrorBatState_Over:
            stateTime = stateTimeList[2]
        return stateTime * 1000
    def CalcHPPer(self):
        ## 结算当前阶段双方阵营剩余血量占比,一般用于结算计算
        curHPPer = round(self.curHP / float(self.curHPMax) * 100, 2)
        tagHPPer = round(self.tagHP / float(self.tagHPMax) * 100, 2)
        return curHPPer, tagHPPer
    def CalcRemainTimePer(self):
        ## 结算当前阶段剩余时间占比,一般用于结算计算
        remainTimePer = round(self.stateTickRemain / float(self.GetStateTickMax()) * 100)
        return remainTimePer
    
    def AddBattlePlayer(self, curPlayer, faction, posX=0, posY=0):
        playerID = curPlayer.GetPlayerID()
@@ -88,7 +155,7 @@
        if realPlayerID:
            self.mirrorIDDict[playerID] = realPlayerID
        else:
            self.realIDDict[playerID] = {"SightLevel":curPlayer.GetSightLevel(), "Faction":curPlayer.GetFaction(), "PosX":curPlayer.GetPosX(), "PosY":curPlayer.GetPosY()}
            self.realIDDict[playerID] = {"SightLevel":curPlayer.GetSightLevel(), "Faction":curPlayer.GetFaction()}
        self.playerFactionDict[playerID] = faction
                
        curPlayer.SetDict(ChConfig.Def_PlayerKey_MirrorBattleID, self.battleID)
@@ -97,6 +164,7 @@
        curPlayer.SetCanAttack(True)
        curPlayer.SetFaction(faction)
        PlayerControl.SetPlayerSightLevel(curPlayer, self.battleID) # 视野层级默认为战场ID,每场战斗的玩家独立视野
        PlayerControl.SetSight(curPlayer, ChConfig.Def_PlayerSight_Default * 3)
        GameObj.SetHPFull(curPlayer) # 回满血
        SkillCommon.ResetAllSkillCD(curPlayer) # 重置技能CD
        return
@@ -144,7 +212,9 @@
def GetMirrorBattle(curPlayer):
    ## 获取玩家实例所属的战场
    return GetMirrorBattleByID(curPlayer.GetDictByKey(ChConfig.Def_PlayerKey_MirrorBattleID))
    if curPlayer.GetRealPlayerID():
        return GetMirrorBattleByID(curPlayer.GetDictByKey(ChConfig.Def_PlayerKey_MirrorBattleID))
    return GetMirrorBattleByID(curPlayer.GetPlayerID())
def GetMirrorBattleByID(battleID):
    battle = None
@@ -155,17 +225,14 @@
        battle = MirrorBattle()
    return battle
def ClearMirrorBattleByPlayer(curPlayer):
    ## 清除玩家创建的镜像战斗
    ClearMirrorBattleByID(curPlayer.GetPlayerID())
    return
def ClearMirrorBattleByID(battleID):
    ## 清除镜像战斗
    battle = PyGameData.g_mirrorBattleDict.pop(battleID, None)
    if not battle:
        return
    ownerPlayerID = battle.playerID
    isSysbg = battle.isSysbg
    GameWorld.DebugLog("清除镜像战斗: battleID=%s,ownerPlayerID=%s,isSysbg=%s" % (isSysbg, ownerPlayerID, isSysbg), battleID)
    playerMgr = GameWorld.GetPlayerManager()
    tick = GameWorld.GetGameWorld().GetTick()
    
@@ -176,30 +243,25 @@
            PlayerControl.DeleteMirror(mirrorPlayer, isSysbg) # 系统场延迟回收
            
    # 重置真实玩家
    for playerID, info in battle.realIDDict.items():
        curPlayer = playerMgr.FindPlayerByID(playerID)
    for realPlayerID, info in battle.realIDDict.items():
        curPlayer = playerMgr.FindPlayerByID(realPlayerID)
        if not curPlayer:
            continue
        curPlayer.SetDict(ChConfig.Def_PlayerKey_MirrorBattleID, 0)
        curPlayer.SetDict(ChConfig.Def_PlayerKey_MirrorBattleTime, 0)
        if curPlayer.GetPlayerAction() == IPY_GameWorld.paDie or GameObj.GetHP(curPlayer) <= 0:
            ChPlayer.PlayerRebornByType(curPlayer, ChConfig.rebornType_System, tick, isAddSuperBuff=False)
        if "PosX" in info:
            curPlayer.ResetPos(info["PosX"], info["PosY"]) # 回到进去前的坐标
        curPlayer.SetFaction(0)
        PlayerControl.SetPlayerSightLevel(curPlayer, 0)
        curPlayer.SetFaction(info.get("Faction", 0))
        PlayerControl.SetPlayerSightLevel(curPlayer, info.get("SightLevel", 0))
        PlayerControl.SetSight(curPlayer, ChConfig.Def_PlayerSight_Default)
        GameObj.SetHPFull(curPlayer) # 回满血
        SkillCommon.ResetAllSkillCD(curPlayer) # 重置技能CD
        curPlayer.SetAttackTick(tick)
        ChPlayer.__Sync_ClientBuff(curPlayer)
        
    # 所属玩家
    playerID = battle.playerID
    curPlayer = playerMgr.FindPlayerByID(playerID)
    if curPlayer:
        curPlayer.SetDict(ChConfig.Def_PlayerKey_MirrorBattleID, 0)
        curPlayer.SetDict(ChConfig.Def_PlayerKey_MirrorBattleTime, 0)
    # 如果是真实地图战斗的,关闭副本
    if battle.isChangeMap:
        GameWorldProcess.CloseFB(GameWorld.GetGameWorld().GetTick())
    return
def CreateMirrorPlayer(battleID, mirrorPlayerID, mirrorPlayerData, posX=0, posY=0, faction=0, curPlayer=None):
@@ -227,7 +289,10 @@
        GameWorld.ErrLog("CreateMirrorPlayer mirrorPlayerID=%s,posX=%s,posY=%s,faction=%s" 
                         % (mirrorPlayerID, posX, posY, faction), playerID)
        return
    PlayerControl.SetCustomMap(mirrorPlayer, mapID, funcLineID)
    if not battle.isChangeMap:
        PlayerControl.SetCustomMap(mirrorPlayer, mapID, funcLineID)
    else:
        PlayerControl.SetCustomMap(mirrorPlayer, 0, 0)
    mirrorID = mirrorPlayer.GetID()
    realPlayerID = mirrorPlayer.GetRealPlayerID()
    dataFightPower = PlayerControl.GetFightPower(mirrorPlayer)
@@ -289,8 +354,11 @@
    realPlayerID = curPlayer.GetRealPlayerID()
    posX = curPlayer.GetPosX()
    posY = curPlayer.GetPosY()
    faction = curPlayer.GetFaction()
    sightLevel = curPlayer.GetSightLevel()
    GameWorld.DebugLog("realPlayerID=%s,posX=%s,posY=%s,sightLevel=%s" % (realPlayerID, posX, posY, sightLevel), playerID)
    sight = curPlayer.GetSight()
    GameWorld.DebugLog("realPlayerID=%s,posX=%s,posY=%s,faction=%s,sightLevel=%s,sight=%s"
                       % (realPlayerID, posX, posY, faction, sightLevel, sight), playerID)
    GameWorld.DebugLog("生命=%s/%s, 护盾=%s/%s" 
                       % (GameObj.GetHP(curPlayer), GameObj.GetMaxHP(curPlayer), 
                          PlayerControl.GetProDef(curPlayer), PlayerControl.GetMaxProDef(curPlayer)), playerID)
@@ -394,21 +462,15 @@
    
    # 创建战斗,玩家自身参与
    if cmdType == 0:
        isSysbg = False
        requestID = playerID
        posX, posY = curPlayer.GetPosX(), curPlayer.GetPosY()
        battlePlayerList = []
        battlePlayerList.append({"playerID":playerID, "faction":1, "posX":posX, "posY":posY})
        battlePlayerList.append({"playerID":tagPlayeID, "faction":2, "posX":posX + 5, "posY":posY})
        OnRequestCreateMirrorBattle(mapID, funcLineID, requestID, battlePlayerList, isSysbg, curPlayer)
        OnRequestCreateMirrorBattle(mapID, funcLineID, playerID, [[playerID], [tagPlayeID]], False, curPlayer, True)
        return
    
    # 开始战斗
    if cmdType == 1:
        battle = GetMirrorBattle(curPlayer)
        if battle:
            OnMirrorBattleStart(battle.battleID)
        return
    # 开始战斗,该流程改为后端控制
    #if cmdType == 1:
    #    battle = GetMirrorBattle(curPlayer)
    #   if battle:
    #        OnMirrorBattleStart(battle.battleID)
    #    return
    
    # 战斗中跳过
    if cmdType == 2:
@@ -419,48 +481,29 @@
    
    # 不战斗直接跳过,即玩家没有参与,创建系统战斗场,之后扩展
    if cmdType == 3:
        isSysbg = True
        requestID = playerID
        posX, posY = curPlayer.GetPosX(), curPlayer.GetPosY()
        battlePlayerList = []
        battlePlayerList.append({"playerID":playerID, "faction":1, "posX":posX, "posY":posY})
        battlePlayerList.append({"playerID":tagPlayeID, "faction":2, "posX":posX + 5, "posY":posY})
        OnRequestCreateMirrorBattle(mapID, funcLineID, requestID, battlePlayerList, isSysbg, curPlayer)
        OnRequestCreateMirrorBattle(mapID, funcLineID, playerID, [[playerID], [tagPlayeID]], True, curPlayer)
        return
    
    # 可不做验证,PK结束后由各个功能自行做结算验证
    return
def OnPlayerLeaveMap(curPlayer):
    ## 玩家离开地图
    if curPlayer.GetRealPlayerID():
        return
    battle = GetMirrorBattle(curPlayer)
    if not battle:
        return
    # 如果还在战斗中,直接快速执行战斗结果
    if battle.batState == ChConfig.Def_MirrorBatState_Fight:
        DoMirrorBattleQuick(battle.battleID, isLogout=True)
    # 统一退出
    if PlayerControl.GetCustomMapID(curPlayer):
        PlayerFB.DoExitCustomScene(curPlayer)
    return
def OnRequestCreateMirrorBattle(mapID, funcLineID, requestID, battlePlayerList, isSysbg=False, curPlayer=None):
def OnRequestCreateMirrorBattle(mapID, funcLineID, requestID, factionPlayerList, isSysbg=False, curPlayer=None, isChangeMap=False):
    ''' 请求创建镜像战斗,支持多对多,支持跨服,本服跨服地图中均可直接请求
    @param mapID: 功能地图ID
    @param funcLineID: 功能地图线路ID
    @param requestID: 请求ID,如果是玩家发起的,一般传入玩家ID;如果是系统发起的,由系统自行决定,比如roomID之类
    @param battlePlayerList: 战斗的玩家信息列表 [{"playerID":玩家ID, "posX":坐标x, "posY":坐标y, "faction":阵营}, ...]
    @param factionPlayerList: 战斗的阵营玩家列表 [[阵营1玩家ID, ...], [阵营2玩家ID, ...]]
    @param isSysbg: 是否后台战斗,默认否,但是系统发起的默认是
    @param curPlayer: 发起的玩家,为空时代表系统发起创建的
    @param isChangeMap: 是否切战斗场景地图
    '''
    
    playerID = 0
    if curPlayer:
        if not FBLogic.OnMirrorBattleRequest(curPlayer, mapID, funcLineID):
        if GameWorld.IsCrossServer():
            GameWorld.DebugLog("跨服服务器中不允许玩家镜像战斗请求!", playerID)
            return
        if not FBLogic.OnMirrorBattleRequest(curPlayer, mapID, funcLineID, factionPlayerList):
            GameWorld.DebugLog("当前不允许该镜像战斗请求! mapID=%s,funcLineID=%s" % (mapID, funcLineID), playerID)
            return
        playerID = curPlayer.GetPlayerID()
@@ -477,27 +520,56 @@
    else:
        isSysbg = True # 系统发起的默认后台战斗
        
    mirrorIDList = []
    for battleInfo in battlePlayerList:
        batPlayerID = battleInfo["playerID"]
        faction = battleInfo["faction"]
        if batPlayerID == playerID and faction == 1 and not isSysbg:
            # 自己不用,使用自身进行战斗即可
            continue
        if batPlayerID not in mirrorIDList:
            mirrorIDList.append(batPlayerID)
    mirrorIDList = [] # 需要使用镜像的玩家ID列表
    for faction, batPlayerIDList in enumerate(factionPlayerList, 1):
        for batPlayerID in batPlayerIDList:
            if batPlayerID == playerID and faction == 1 and not isSysbg:
                # 自己不用,使用自身进行战斗即可
                continue
            if batPlayerID not in mirrorIDList:
                mirrorIDList.append(batPlayerID)
    # 战斗相关的数据
    msgData = {"mapID":mapID, "funcLineID":funcLineID, "battlePlayerList":battlePlayerList, "isSysbg":isSysbg}
    msgData = {"mapID":mapID, "funcLineID":funcLineID, "factionPlayerList":factionPlayerList, "isSysbg":isSysbg}
    
    # 发送GameServer请求玩家打包数据
    requestTime = curTime # 请求的时间戳,每个玩家最多允许同时存在一场战斗,每次重新请求后覆盖数据
    requestMapID = GameWorld.GetGameWorld().GetRealMapID()
    sendMsg = str({"msgType":"MirrorBattle", "msgData":msgData, "mirrorIDList":mirrorIDList,
                   "requestTime":requestTime, "requestID":requestID, "requestMapID":requestMapID, "playerID":playerID})
    sceneMapID = GameWorld.GetGameWorld().GetRealMapID()
    sendMsg = {"msgType":"MirrorBattle", "msgData":msgData, "mirrorIDList":mirrorIDList,
               "requestTime":requestTime, "requestID":requestID, "sceneMapID":sceneMapID, "playerID":playerID}
    if isChangeMap:
        # 默认切到PK地图
        sendMsg["isChangeMap"] = 1
        PlayerControl.PlayerEnterFB(curPlayer, mapID, funcLineID, reqInfoEx=sendMsg)
        return True
    sendMsg = str(sendMsg)
    GameWorld.GetPlayerManager().GameServer_QueryPlayerResult(0, 0, 0, "ReuestPlayerPackData", sendMsg, len(sendMsg))
    GameWorld.DebugLog("请求创建镜像战斗: %s" % sendMsg, playerID)
    return True
def OnMirrorBattleEnterMapInit(curPlayer, tick):
    ''' 镜像战斗初始化  - 需要切图的,玩家切图成功或地图收到打包数据同步时会触发,即这两个条件都准备好后才开始初始化
    '''
    if not curPlayer or curPlayer.IsEmpty() or not curPlayer.GetMapLoadOK():
        return
    battle = GetMirrorBattle(curPlayer)
    if battle:
        GameWorld.DebugLog("镜像战斗玩家断线重连成功!", curPlayer.GetPlayerID())
        curPlayer.SetDict(ChConfig.Def_PlayerKey_MirrorBattleID, battle.battleID)
        curPlayer.SetFaction(1)
        PlayerControl.SetPlayerSightLevel(curPlayer, battle.battleID)
        PlayerControl.SetSight(curPlayer, ChConfig.Def_PlayerSight_Default * 3)
        battle.SyncFBStepTime(tick)
        return
    playerID = curPlayer.GetPlayerID()
    if playerID not in PyGameData.g_playerReqEnterFBEx:
        return
    msgInfo, packDataDict = PyGameData.g_playerReqEnterFBEx[playerID]
    OnMirrorBattleInit(msgInfo, packDataDict, curPlayer)
    return
def OnMirrorBattleInit(msgInfo, packDataDict, curPlayer=None):
    ''' 镜像战斗初始化 
@@ -512,7 +584,7 @@
    
    mapID = msgData.get("mapID", 0)
    funcLineID = msgData.get("funcLineID", 0)
    battlePlayerList = msgData.get("battlePlayerList", [])
    factionPlayerList = msgData.get("factionPlayerList", [])
    isSysbg = msgData.get("isSysbg", 0) # 系统后台战斗
    
    battleID = 0
@@ -529,28 +601,28 @@
    if not battle:
        GameWorld.ErrLog("镜像战场ID已存在! battleID=%s,msgInfo=%s" % (battleID, msgInfo), requestID)
        return
    battle.isChangeMap = msgInfo.get("isChangeMap", 0)
    GameWorld.DebugLog("镜像战斗初始化: msgData=%s,packIDList=%s" % (msgData, packDataDict.keys()), battleID)
    
    for battleInfo in battlePlayerList:
        batPlayerID = battleInfo["playerID"]
        posX = battleInfo.get("posX", 0)
        posY = battleInfo.get("posY", 0)
        faction = battleInfo.get("faction", 0)
        if curPlayer and batPlayerID == playerID and faction == 1 and not isSysbg:
            battle.AddBattlePlayer(curPlayer, faction, posX, posY)
            continue
        packData = packDataDict.get(batPlayerID)
        if not packData:
            GameWorld.ErrLog("初始化镜像战斗时没有玩家镜像数据! batPlayerID=%s" % batPlayerID, playerID)
            continue
        CreateMirrorPlayer(battleID, batPlayerID, packData, posX, posY, faction, curPlayer)
    battle.batState = ChConfig.Def_MirrorBatState_Prepare
    fightTimeLimitDict = IpyGameDataPY.GetFuncEvalCfg("MirrorAttack", 1, {})
    battle.fightTickMax = fightTimeLimitDict.get(str(mapID), 60) * 1000 # 最大战斗时长,默认60秒
    factionPosList = OnGetMirrorBattlePos(mapID, funcLineID, isSysbg)
    for faction, batPlayerIDList in enumerate(factionPlayerList, 1):
        posInfo = factionPosList[faction - 1]
        factionPosX, factionPosY = posInfo
        for index, batPlayerID in enumerate(batPlayerIDList):
            posX, posY = factionPosX, factionPosY + index * 5
            if curPlayer and batPlayerID == playerID and faction == 1 and not isSysbg:
                battle.AddBattlePlayer(curPlayer, faction, posX, posY)
                continue
            packData = packDataDict.get(batPlayerID)
            if not packData:
                GameWorld.ErrLog("初始化镜像战斗时没有玩家镜像数据! batPlayerID=%s" % batPlayerID, playerID)
                continue
            CreateMirrorPlayer(battleID, batPlayerID, packData, posX, posY, faction, curPlayer)
    tick = GameWorld.GetGameWorld().GetTick()
    battle.ChangeBattleState(ChConfig.Def_MirrorBatState_Prepare, tick)
    
    if not isSysbg:
        return
@@ -558,7 +630,18 @@
    # 系统场默认直接开始、快速战斗结束
    OnMirrorBattleStart(battleID)
    DoMirrorBattleQuick(battleID)
    ClearMirrorBattleByID(battleID)
    return
def OnGetMirrorBattlePos(mapID, lineID, isSysbg=False):
    if isSysbg:
        gameMap = GameWorld.GetMap()
        posX, posY = gameMap.GetRebornMapX(), gameMap.GetRebornMapY() # 系统战斗默认取当前地图的复活点
        factionPosList = [[posX, posY], [posX + 5, posY]]
    else:
        factionPosDict = IpyGameDataPY.GetFuncEvalCfg("MirrorAttack", 4, {})
        factionPosList = factionPosDict.get(mapID, [[10, 7], [40,37]])
    return factionPosList
def OnMirrorBattleStart(battleID):
    ## 镜像战斗开始
@@ -567,8 +650,8 @@
        return
    if battle.batState >= ChConfig.Def_MirrorBatState_Fight:
        return
    battle.batState = ChConfig.Def_MirrorBatState_Fight
    battle.startTick = GameWorld.GetGameWorld().GetTick()
    tick = GameWorld.GetGameWorld().GetTick()
    battle.ChangeBattleState(ChConfig.Def_MirrorBatState_Fight, tick)
    return
def ProcessPlayerMirrorAI(curPlayer, tick):
@@ -576,31 +659,39 @@
    battle = GetMirrorBattle(curPlayer)
    if not battle:
        return
    playerID = curPlayer.GetPlayerID()
    realPlayerID = curPlayer.GetRealPlayerID()
    if realPlayerID:
        # 用第一个对手镜像玩家来定时判断战斗阶段,因为玩家自身可能会掉线,所以不用自己,而用镜像玩家
        if realPlayerID == battle.GetTagPlayerID():
            if battle.stateTick and battle.CaclStateTick(tick) <= 0:
                if battle.batState == ChConfig.Def_MirrorBatState_Prepare:
                    OnMirrorBattleStart(battle.battleID)
                elif battle.batState == ChConfig.Def_MirrorBatState_Fight:
                    OnMirrorAttackOver(battle.battleID)
                elif battle.batState == ChConfig.Def_MirrorBatState_Over:
                    ClearMirrorBattleByID(battle.battleID)
    else:
        # 常规战斗下,真实玩家不处理,由玩家自行控制
        if not battle.isQuick:
            return
    if battle.batState != ChConfig.Def_MirrorBatState_Fight:
        #GameWorld.DebugLog("镜像玩家仅自由战斗状态下需要处理! battleID=%s,batState=%s" % (battle.battleID, battle.batState), playerID)
        return
    
    if not battle.isQuick:
        realPlayerID = curPlayer.GetRealPlayerID()
        if not realPlayerID:
            # 常规战斗下,真实玩家不处理,由玩家自行控制
            # 真实玩家附加判断是否PK超时
            if battle.startTick and battle.CaclFightTick(tick) <= 0:
                OnMirrorAttackOver(battle.battleID)
            return
    if GameObj.GetHP(curPlayer) <= 0:
        #GameWorld.DebugLog("镜像玩家已被击杀", playerID)
        return
    
    # 攻击间隔
    if tick - curPlayer.GetPlayerAttackTick() < curPlayer.GetAtkInterval():
        GameWorld.DebugLog("攻击间隔: %s < %s" % (tick - curPlayer.GetPlayerAttackTick(), curPlayer.GetAtkInterval()), playerID)
        #GameWorld.DebugLog("攻击间隔: %s < %s" % (tick - curPlayer.GetPlayerAttackTick(), curPlayer.GetAtkInterval()), playerID)
        return
    
    autoUseSkillList = battle.GetPlayerAutoUseSkillList(curPlayer)
    GameWorld.DebugLog("镜像AI攻击: autoUseSkillList=%s" % (autoUseSkillList), playerID)
    #GameWorld.DebugLog("镜像AI攻击: autoUseSkillList=%s" % (autoUseSkillList), playerID)
    
    if curPlayer.GetPlayerVehicle() == IPY_GameWorld.pvHorse:
        PlayerHorse.PlayerRideHorseDown(curPlayer)
@@ -670,10 +761,10 @@
        curPlayer.SetUseSkill(curSkill.GetSkillData())
        useSkillData = curPlayer.GetUseSkill()
        if not PlayerState.__DoClientUseSkillEx(curPlayer, useSkillData, tick):
            GameWorld.DebugLog("        技能攻击失败: playerID=%s,tagID=%s,skillID=%s" % (playerID, tagObjID, skillID))
            #GameWorld.DebugLog("        技能攻击失败: playerID=%s,tagID=%s,skillID=%s" % (playerID, tagObjID, skillID))
            continue
        useSkillResult = True
        GameWorld.DebugLog("        技能攻击成功: playerID=%s,tagID=%s,skillID=%s" % (playerID, tagObjID, skillID))
        #GameWorld.DebugLog("        技能攻击成功: playerID=%s,tagID=%s,skillID=%s" % (playerID, tagObjID, skillID))
        
        if useSkillData and useSkillData.GetSkillID() != ChConfig.Def_SkillID_Somersault:
            # 跟随玩家同频率攻击
@@ -735,7 +826,7 @@
    battle = GetMirrorBattleByID(battleID)
    if not battle:
        return
    if battle.batState > ChConfig.Def_MirrorBatState_Fight:
    if battle.batState != ChConfig.Def_MirrorBatState_Fight:
        return
    if battle.isQuick:
        #不重复触发处理
@@ -747,22 +838,16 @@
            if mapID in quickLimitMapIDList:
                GameWorld.DebugLog("战斗中不允许点击快速战斗! mapID=%s" % mapID, battle.playerID)
                return
        if isLogout:
            logoutQuickLimitMapIDList = IpyGameDataPY.GetFuncEvalCfg("MirrorAttack", 3)
            if mapID in logoutQuickLimitMapIDList:
                GameWorld.DebugLog("掉线不允许快速战斗! mapID=%s" % mapID, battle.playerID)
                return
    tick = GameWorld.GetGameWorld().GetTick()
    battle.batState = ChConfig.Def_MirrorBatState_Fight
    battle.isQuick = True
    battle.isLogout = isLogout
    battle.CaclFightTick(tick)
    battle.CaclStateTick(tick)
    
    playerMgr = GameWorld.GetMapCopyPlayerManager()
    perLoopTick = 100 # 每次循环视为已过毫秒
    maxLoopCount = battle.fightTickRemain / perLoopTick # 循环次数上限
    GameWorld.DebugLog("DoMirrorBattleQuick isLogout=%s,maxLoopCount=%s,tick=%s,fightTickRemain=%s"
                       % (isLogout, maxLoopCount, tick, battle.fightTickRemain), battleID)
    maxLoopCount = battle.stateTickRemain / perLoopTick # 循环次数上限
    GameWorld.DebugLog("镜像PK快速结算: isLogout=%s,maxLoopCount=%s,tick=%s,stateTickRemain=%s"
                       % (isLogout, maxLoopCount, tick, battle.stateTickRemain), battleID)
    
    # 屏蔽发包
    for batPlayerID in battle.realIDDict.keys():
@@ -776,8 +861,7 @@
            # 可能还没循环完毕就结束了
            break
        tick += perLoopTick # 修改每次循环的tick
        battle.CaclFightTick(tick)
        GameWorld.DebugLog("    loopCount=%s,tick=%s,fightTickRemain=%s" % (loopCount, tick, battle.fightTickRemain), battleID)
        #GameWorld.DebugLog("    loopCount=%s,tick=%s,stateTickRemain=%s" % (loopCount, tick, battle.stateTickRemain), battleID)
        for batPlayerID in battle.playerFactionDict.keys():
            if batPlayerID in battle.deadPlayerIDList:
                continue
@@ -846,10 +930,9 @@
    if battle.batState >= ChConfig.Def_MirrorBatState_Over:
        # 已经结算过
        return
    battle.batState = ChConfig.Def_MirrorBatState_Over
    tick = GameWorld.GetGameWorld().GetTick()
    if not battle.fightTickRemain:
        battle.CaclFightTick(tick)
    if not battle.stateTickRemain:
        battle.CaclStateTick(tick)
    # 暂定没击杀算输,发起方为1
    if not battle.winFaction:
        battle.winFaction = 2
@@ -882,9 +965,72 @@
                curPlayer.SetDead(curPlayer.GetDictByKey(ChConfig.Def_NPCDead_KillerID),
                                  curPlayer.GetDictByKey(ChConfig.Def_NPCDead_KillerType))
                
    #统计明细
    for playerID, faction in battle.playerFactionDict.items():
        player = playerMgr.FindPlayerByID(playerID)
        if not player:
            continue
        realPlayerID = player.GetRealPlayerID()
        hp = GameObj.GetHP(player)
        hpMax = GameObj.GetMaxHP(player)
        GameWorld.DebugLog("剩余血量: %s/%s, 护盾:%s/%s,playerID=%s,realPlayerID=%s,faction=%s"
                           % (hp, hpMax, PlayerControl.GetProDef(player), PlayerControl.GetMaxProDef(player), playerID, realPlayerID, faction), battleID)
        if faction == 1:
            battle.curHP += hp
            battle.curHPMax += hpMax
        else:
            battle.tagHP += hp
            battle.tagHPMax += hpMax
    FBLogic.OnMirrorBattleOver(battleID, mapID)
    
    if battle.isSysbg:
        ClearMirrorBattleByID(battleID)
    # 改变状态需放在最后
    battle.ChangeBattleState(ChConfig.Def_MirrorBatState_Over, tick)
    return
def DoPlayerLeaveFB(curPlayer, tick):
    ##玩家主动离开副本
    if curPlayer.GetRealPlayerID():
        return
    battle = GetMirrorBattle(curPlayer)
    if not battle:
        return
    GameWorld.DebugLog("玩家主动退出镜像战斗强制快速结算战斗!", curPlayer.GetPlayerID())
    DoQuickOverByExit(curPlayer)
    return
def DoExitFB(curPlayer, tick):
    ##玩家退出副本
    if curPlayer.GetRealPlayerID():
        return
    battle = GetMirrorBattle(curPlayer)
    if not battle:
        return
    # 退出时还有战场数据一般是掉线的,因为主动退出默认强制结算
    mapID = battle.mapID
    logoutQuickLimitMapIDList = IpyGameDataPY.GetFuncEvalCfg("MirrorAttack", 3)
    if mapID in logoutQuickLimitMapIDList:
        GameWorld.DebugLog("掉线不允许快速战斗! mapID=%s" % mapID, battle.playerID)
        return
    GameWorld.DebugLog("玩家掉线退出镜像战斗快速结算战斗!", curPlayer.GetPlayerID())
    DoQuickOverByExit(curPlayer)
    return
def DoQuickOverByExit(curPlayer):
    ## 执行退出快速结算
    battle = GetMirrorBattle(curPlayer)
    if not battle:
        return
    battleID = battle.battleID
    if battle.batState < ChConfig.Def_MirrorBatState_Fight:
        OnMirrorBattleStart(battleID) # 还未开始则强制开始
    # 如果还在战斗中,直接快速执行战斗结果
    if battle.batState == ChConfig.Def_MirrorBatState_Fight:
        DoMirrorBattleQuick(battle.battleID, isLogout=True)
    ClearMirrorBattleByID(battleID)
    return