hxp
2025-06-30 388823edfe6308cba6f76ca6dc4f20022c5cb2be
ServerPython/CoreServerGroup/GameServer/Script/GameWorldLogic/CrossRealmPK.py
@@ -18,892 +18,935 @@
import GameWorld
import PlayerControl
import CrossRealmMsg
import CrossRealmPlayer
import DataRecordPack
import ChPyNetSendPack
import PyGameDataStruct
import PlayerDBGSEvent
import CrossBillboard
import PyDataManager
import NetPackCommon
import IpyGameDataPY
import ShareDefine
import PyGameData
import CommFunc
import ChConfig
import datetime
import operator
import random
import time
#跨服PK排行榜管理,注意该类只处理数据逻辑,功能相关逻辑不要写在该类,不然重读脚本不会生效
class CrossPKBillboardManager(object):
    
PKPlayerState_Matching = 0
PKPlayerState_Fighting = 1
    DBKEY_CrossPKFinalBillboardData = "CrossPKFinalBillboard_%s_%s" # 是否收到赛区赛季最终榜单同步,参数(zoneID, seasonID)
    def __init__(self):
        self.__ZoneSeasonDataList = "ZoneSeasonDataList_%s_%s" # 分区赛季排行数据列表 [tagDBCrossPKBillboard, ...]
        self.__ZoneSeasonPlayerOrderDict = "ZoneSeasonPlayerOrderDict_%s_%s" # 分区赛季排行数据字典 {playerID:rank, ...}
        self.__ZoneSeasonList = [] # 分区赛季列表 [(zoneID, seasonID), ...]
        self.__UnSortZoneSeasonTimeDict = {} # 未排序的分区赛季排行 {(zoneID, seasonID):tick, ...}
        return
    def GetCrossPKBillboardInfo(self, zoneID, seasonID, isSort=False):
        ## 获取跨服PK排行榜信息
        # @return: billboardList, orderDict
        listAttrName = self.__ZoneSeasonDataList % (zoneID, seasonID)
        orderDictAttrName = self.__ZoneSeasonPlayerOrderDict % (zoneID, seasonID)
        if not hasattr(self, listAttrName):
            setattr(self, listAttrName, [])
            setattr(self, orderDictAttrName, {})
            self.__ZoneSeasonList.append((zoneID, seasonID))
        billboardList = getattr(self, listAttrName)
        if isSort:
            self.__PKBillboardSort(zoneID, seasonID, billboardList)
        orderDict = getattr(self, orderDictAttrName)
        return billboardList, orderDict
    def __PKBillboardSort(self, zoneID, seasonID, billboardList):
        ## 排序
        if (zoneID, seasonID) not in self.__UnSortZoneSeasonTimeDict:
            return
        self.__UnSortZoneSeasonTimeDict.pop((zoneID, seasonID))
        billboardList.sort(key=operator.attrgetter("PKScore", "Time"), reverse=True)
        orderDict = {}
        for order, billboardData in enumerate(billboardList, 1):
            orderDict[billboardData.PlayerID] = order # 缓存赛季玩家排名
        orderDictAttrName = self.__ZoneSeasonPlayerOrderDict % (zoneID, seasonID)
        setattr(self, orderDictAttrName, orderDict)
        GameWorld.DebugLog("跨服PK榜单排序: zoneID=%s,seasonID=%s,orderDict=%s" % (zoneID, seasonID, orderDict))
        return
    def PKBillboardSortByTime(self, tick):
        ## 定时排序
        if not self.__UnSortZoneSeasonTimeDict:
            return
        sortCD = 60 * 1000 # 超过1分钟强制排序
        for key, updTick in self.__UnSortZoneSeasonTimeDict.items():
            zoneID, seasonID = key
            if tick - updTick < sortCD:
                continue
            self.GetCrossPKBillboardInfo(zoneID, seasonID, True)
        return
    def SetNeedSort(self, zoneID, seasonID):
        if (zoneID, seasonID) not in self.__UnSortZoneSeasonTimeDict:
            self.__UnSortZoneSeasonTimeDict[(zoneID, seasonID)] = GameWorld.GetGameWorld().GetTick()
        return
    ## ===========================================================================================
    def CopyToCrossBillboard(self):
        toBillboardType = ShareDefine.Def_CBT_CrossRealmPK
        billboardMgr = PyDataManager.GetCrossBillboardManager()
        for zoneID, seasonID in self.__ZoneSeasonList:
            groupValue1, groupValue2 = zoneID, seasonID
            toBillboardObj = billboardMgr.GetCrossBillboard(toBillboardType, groupValue1, groupValue2)
            billboardList = self.GetCrossPKBillboardInfo(zoneID, seasonID)[0]
            GameWorld.Log("CopyToCrossBillboard: zoneID=%s,seasonID=%s,%s" % (zoneID, seasonID, len(billboardList)))
            for billboardData in billboardList:
                tobillboardData = PyGameDataStruct.tagDBCrossBillboard()
                tobillboardData.GroupValue1 = groupValue1
                tobillboardData.GroupValue2 = groupValue2
                tobillboardData.BillboardType = toBillboardType
                tobillboardData.ID = billboardData.PlayerID
                tobillboardData.Name1 = billboardData.PlayerName
                tobillboardData.Type2 = billboardData.Job
                tobillboardData.Value1 = billboardData.RealmLV
                tobillboardData.Value2 = billboardData.DanLV
                tobillboardData.Value3 = billboardData.Face
                tobillboardData.Value4 = billboardData.FacePic
                tobillboardData.CmpValue = billboardData.PKScore
                tobillboardData.CmpValue3 = billboardData.Time
                toBillboardObj.AddBillboardData(tobillboardData)
        self.__ZoneSeasonList = []
        return
    # 保存数据 存数据库和realtimebackup
    def GetSaveData(self):
        savaData = ""
        cntData = ""
        cnt = 0
        for zoneID, seasonID in self.__ZoneSeasonList:
            billboardList = self.GetCrossPKBillboardInfo(zoneID, seasonID)[0]
            for billboardData in billboardList:
                cnt += 1
                savaData += billboardData.getBuffer()
        GameWorld.Log("SaveCrossPKBillboard cnt :%s len=%s" % (cnt, len(savaData)))
        return CommFunc.WriteDWORD(cntData, cnt) + savaData
    # 从数据库载入数据
    def LoadPyGameData(self, datas, pos, dataslen):
        cnt, pos = CommFunc.ReadDWORD(datas, pos)
        GameWorld.Log("LoadCrossPKBillboard cnt :%s" % cnt)
        for _ in xrange(cnt):
            billboardData = PyGameDataStruct.tagDBCrossPKBillboard()
            billboardData.clear()
            pos += billboardData.readData(datas, pos, dataslen)
            zoneID = billboardData.ZoneID
            seasonID = billboardData.SeasonID
            billboardList = self.GetCrossPKBillboardInfo(zoneID, seasonID)[0]
            billboardList.append(billboardData)
        # 排序
        for zoneID, seasonID in self.__ZoneSeasonList:
            self.SetNeedSort(zoneID, seasonID)
            billboardList = self.GetCrossPKBillboardInfo(zoneID, seasonID, True)[0]
            GameWorld.Log("    zoneID=%s, seasonID=%s, count=%s" % (zoneID, seasonID, len(billboardList)))
        self.CopyToCrossBillboard()
        return pos
def MapServer_QueryCrossPKSeasonOrder(curPlayer, msgList):
    ## 地图服务器查询玩家赛区赛季PK榜排名
    playerID = curPlayer.GetPlayerID()
    zoneID, seasonID, eventName, eventData = msgList
    funcData = {"zoneID":zoneID, "seasonID":seasonID, "eventName":eventName, "eventData":eventData}
    CrossBillboard.OnQueryPlayerBillboardRank(playerID, "QueryCrossPKSeasonOrder", funcData, ShareDefine.Def_CBT_CrossRealmPK, zoneID, seasonID)
    return
class CrossPKPlayer():
    ## 跨服PK玩家类
    def __init__(self):
        self.accID = ""
        self.playerID = 0
        self.playerName = ""
        self.playerJob = 0
        self.playerLV = 0
        self.maxHP = 0
        self.fightPower = 0
        self.pkScore = 0
        self.danLV = 0
        self.matchTick = 0
        self.cWinCount = 0 # 连胜次数
        self.ondayScore = 0 # 过天时的积分
        self.serverGroupID = 0 # 所属服务器ID,一个服务器ID由多个服组成
        self.pkZoneID = 0 # 所属赛区ID,一个赛区由多个服务器ID组成
        self.seasonID = 0 # 赛季ID
def OnQueryCrossPKSeasonOrderRet(playerID, funcData, orderIndex):
    ## 查询排名返回
    queryPlayer = GameWorld.GetPlayerManager().FindPlayerByID(playerID)
    if not queryPlayer:
        return
class CrossPKRoom():
    ## 跨服PK房间类
    def __init__(self):
        self.pkZoneID = 0
        self.roomID = 0
        self.mapID = 0
        self.openTick = 0 # 开房时间
        self.readyTick = 0 # 玩家都准备好的时间
        self.roomState = ShareDefine.Def_VsRoom_State_WaitPlayer # 默认状态
        self.roomPlayerIDList = [] # 对战玩家ID列表
        self.readyPlayerIDList = [] # 已经准备好的玩家ID列表
        self.isMapOpen = False # 地图是否已经开启该房间,未开启的房间超时后,本次匹配视为无效,有玩家进地图才会开启副本分线
        return
    zoneID = funcData["zoneID"]
    seasonID = funcData["seasonID"]
    eventName = funcData["eventName"]
    eventData = funcData["eventData"]
    order = orderIndex + 1
    sysMsg = str([zoneID, seasonID, eventName, eventData, order])
    queryPlayer.MapServer_QueryPlayerResult(0, 0, "CrossPKSeasonOrder", sysMsg, len(sysMsg))
    return
################################################################################
def OnPlayerLogin(curPlayer):
def __GetCrossPKZoneSeasonTimeInfo():
    key = "CrossPKZoneSeasonTimeInfo"
    openServerDay = PlayerDBGSEvent.GetDBGSTrig_ByKey(PlayerDBGSEvent.Def_ServerDay) + 1
    ZoneSeasonTimeInfo = IpyGameDataPY.GetConfigEx(key)
    if ZoneSeasonTimeInfo and ZoneSeasonTimeInfo[0] == openServerDay:
        #GameWorld.DebugLog("已经加载过本日跨服PK赛季处理信息!openServerDay=%s" % openServerDay)
        return ZoneSeasonTimeInfo[1]
    GameWorld.Log("加载跨服PK赛季时间信息: openServerDay=%s" % (openServerDay))
    zoneSeasonTimeDict = {}
    serverTime = GameWorld.GetServerTime()
    crossZoneName = GameWorld.GetCrossZoneName()
    crossZoneList = IpyGameDataPY.GetIpyGameDataByCondition("CrossZonePK", {"CrossZoneName":crossZoneName}, True)
    if not crossZoneList:
        return zoneSeasonTimeDict
    for zoneIpyData in crossZoneList:
        zoneID = zoneIpyData.GetZoneID()
        # 规定每个赛区的赛季时间需按顺序配置
        seasonIpyDataList = IpyGameDataPY.GetIpyGameDataList("CrossRealmPKSeason", crossZoneName, zoneID)
        if not seasonIpyDataList:
            continue
        for i, seasonIpyData in enumerate(seasonIpyDataList):
            seasonID = seasonIpyData.GetSeasonID()
            startDateStr = seasonIpyData.GetStartDate()
            endDateStr = seasonIpyData.GetEndDate()
            endTimeStr = seasonIpyData.GetEndTime()
            startDateTime = datetime.datetime.strptime("%s 00:00:00" % (startDateStr), ChConfig.TYPE_Time_Format)
            endDateTime = datetime.datetime.strptime("%s %s:00" % (endDateStr, endTimeStr), ChConfig.TYPE_Time_Format)
            # 最后一个赛季
            if i == len(seasonIpyDataList) - 1:
                nextSeasonIpyData = None
            else:
                nextSeasonIpyData = seasonIpyDataList[i + 1]
            if serverTime < startDateTime:
                GameWorld.Log("    赛季未开始! zoneID=%s,seasonID=%s,startDateStr=%s,endDateStr=%s" % (zoneID, seasonID, startDateStr, endDateStr))
                break
            elif startDateTime <= serverTime <= endDateTime:
                pass
            elif endDateTime < serverTime:
                if nextSeasonIpyData:
                    nestStartDateStr = nextSeasonIpyData.GetStartDate()
                    nextStartDateTime = datetime.datetime.strptime("%s 00:00:00" % (nestStartDateStr), ChConfig.TYPE_Time_Format)
                    if serverTime >= nextStartDateTime:
                        GameWorld.Log("    赛季已过期! zoneID=%s,seasonID=%s,startDateStr=%s,endDateStr=%s" % (zoneID, seasonID, startDateStr, endDateStr))
                        continue
                else:
                    pass
            else:
                continue
            nextSeasonID = 0 if not nextSeasonIpyData else nextSeasonIpyData.GetSeasonID()
            zoneSeasonTimeDict[zoneID] = [seasonIpyData]
            GameWorld.Log("    赛季活动中! zoneID=%s,seasonID=%s,startDateStr=%s,endDateStr=%s,nextSeasonID=%s" % (zoneID, seasonID, startDateStr, endDateStr, nextSeasonID))
            break
    ZoneSeasonTimeInfo = IpyGameDataPY.SetConfigEx(key, [openServerDay, zoneSeasonTimeDict])
    GameWorld.Log("跨服PK赛季时间信息加载完毕!")
    GameWorld.Log("=============================================================")
    return ZoneSeasonTimeInfo[1]
def OnMinuteProcess():
    ## 每分钟处理
    if not GameWorld.IsCrossServer():
        __OnLoginNotifyPKOverInfo(curPlayer)
        return
    zoneSeasonTimeDict = __GetCrossPKZoneSeasonTimeInfo()
    if not zoneSeasonTimeDict:
        return
    gameWorld = GameWorld.GetGameWorld()
    serverTime = GameWorld.GetServerTime()
    crossZoneName = GameWorld.GetCrossZoneName()
    for zoneID, seasonTimeInfo in zoneSeasonTimeDict.items():
        zoneIpyData = IpyGameDataPY.GetIpyGameData("CrossZonePK", crossZoneName, zoneID)
        if not zoneIpyData:
            continue
        curSeasonIpyData = seasonTimeInfo[0]
        if not curSeasonIpyData:
            continue
        seasonID = curSeasonIpyData.GetSeasonID()
        startDateStr = curSeasonIpyData.GetStartDate()
        endDateStr = curSeasonIpyData.GetEndDate()
        endTimeStr = curSeasonIpyData.GetEndTime()
        startDateTime = datetime.datetime.strptime("%s 00:00:00" % (startDateStr), ChConfig.TYPE_Time_Format)
        endDateTime = datetime.datetime.strptime("%s %s:00" % (endDateStr, endTimeStr), ChConfig.TYPE_Time_Format)
        seasonState = 0
        if startDateTime <= serverTime < endDateTime:
            seasonState = 1
        elif serverTime >= endDateTime:
            seasonState = 2
        zoneSeasonIDDictName = ChConfig.Def_WorldKey_CrossPKZoneSeasonID % zoneID
        seasonStateDictName = ChConfig.Def_WorldKey_CrossPKZoneSeasonState % zoneID
        dictSeasonID = gameWorld.GetDictByKey(zoneSeasonIDDictName)
        curSeasonState = gameWorld.GetDictByKey(seasonStateDictName)
        if curSeasonState == seasonState and dictSeasonID == seasonID:
            #已经是这个状态了
            continue
        gameWorld.SetDict(zoneSeasonIDDictName, seasonID)
        gameWorld.SetDict(seasonStateDictName, seasonState)
        GameWorld.Log("跨服PK赛季状态变更: zoneID=%s,seasonID=%s,seasonState=%s" % (zoneID, seasonID, seasonState))
        serverGroupIDList = zoneIpyData.GetServerGroupIDList()
        # 赛季结束
        if seasonState == 2:
            PyGameData.g_crossPKMatchDict = {}
        # 广播当前赛区的所有子服跨服PK赛季状态变更
        matchState = gameWorld.GetDictByKey(ShareDefine.Def_Notify_WorldKey_CrossDailyActionState % ShareDefine.DailyActionID_CrossReamPK)
        seasonInfo = {"ZoneID":zoneID, "SeasonID":seasonID, "SeasonState":seasonState, "MatchState":matchState}
        CrossRealmMsg.SendMsgToClientServer(ShareDefine.CrossServerMsg_PKSeasonInfo, seasonInfo, serverGroupIDList)
    return
## 玩家离线处理
def OnLeaveServer(curPlayer):
    # 发送取消匹配
    SendCancelCrossRealmPKMatch(curPlayer, "PlayerDisconnect")
def OnCrossRealmPKDailyActionStateChange(isOpen):
    ## 跨服PK每日活动匹配状态变更
    if not GameWorld.IsCrossServer():
        return
    gameWorld = GameWorld.GetGameWorld()
    preState = gameWorld.GetDictByKey(ShareDefine.Def_Notify_WorldKey_CrossDailyActionState % ShareDefine.DailyActionID_CrossReamPK)
    gameWorld.SetDict(ShareDefine.Def_Notify_WorldKey_CrossDailyActionState % ShareDefine.DailyActionID_CrossReamPK, isOpen)
    # 匹配状态从关闭到开启
    if not preState and isOpen:
        GameWorld.Log("跨服PK匹配状态开启,重置相关匹配数据!")
    crossZoneName = GameWorld.GetCrossZoneName()
    crossZoneList = IpyGameDataPY.GetIpyGameDataByCondition("CrossZonePK", {"CrossZoneName":crossZoneName}, True)
    if not crossZoneList:
        return
    for zoneIpyData in crossZoneList:
        zoneID = zoneIpyData.GetZoneID()
        # 广播当前赛区的所有子服跨服PK赛季状态变更
        serverGroupIDList = zoneIpyData.GetServerGroupIDList()
        seasonID = gameWorld.GetDictByKey(ChConfig.Def_WorldKey_CrossPKZoneSeasonID % zoneID)
        seasonState = gameWorld.GetDictByKey(ChConfig.Def_WorldKey_CrossPKZoneSeasonState % zoneID)
        seasonInfo = {"ZoneID":zoneID, "SeasonID":seasonID, "SeasonState":seasonState, "MatchState":isOpen}
        GameWorld.Log("跨服PK匹配状态变更: zoneID=%s,seasonID=%s,seasonState=%s,matchState=%s" % (zoneID, seasonID, seasonState, isOpen))
        CrossRealmMsg.SendMsgToClientServer(ShareDefine.CrossServerMsg_PKSeasonInfo, seasonInfo, serverGroupIDList)
    return
def IsCrossRealmPKOpen():
def Sync_CrossPKInitDataToClientServer(tick, serverGroupID=0):
    ''' 同步跨服PK活动数据到子服务器
    @param serverGroupID: 为0时同步所有子服
    '''
    GameWorld.Log("同步给子服对应的赛季信息: syncServerGroupID=%s" % (serverGroupID))
    if serverGroupID:
        ipyData = GetCrossPKServerGroupZone(serverGroupID)
        if not ipyData:
            return
        crossZoneList = [ipyData]
    else:
        crossZoneName = GameWorld.GetCrossZoneName()
        crossZoneList = IpyGameDataPY.GetIpyGameDataByCondition("CrossZonePK", {"CrossZoneName":crossZoneName}, True)
    if not crossZoneList:
        return
    gameWorld = GameWorld.GetGameWorld()
    for zoneIpyData in crossZoneList:
        zoneID = zoneIpyData.GetZoneID()
        seasonID = gameWorld.GetDictByKey(ChConfig.Def_WorldKey_CrossPKZoneSeasonID % zoneID)
        seasonState = gameWorld.GetDictByKey(ChConfig.Def_WorldKey_CrossPKZoneSeasonState % zoneID)
        matchState = gameWorld.GetDictByKey(ShareDefine.Def_Notify_WorldKey_CrossDailyActionState % ShareDefine.DailyActionID_CrossReamPK)
        seasonInfo = {"ZoneID":zoneID, "SeasonID":seasonID, "SeasonState":seasonState, "MatchState":matchState}
        serverGroupIDList = [serverGroupID] if serverGroupID else zoneIpyData.GetServerGroupIDList()
        CrossRealmMsg.SendMsgToClientServer(ShareDefine.CrossServerMsg_PKSeasonInfo, seasonInfo, serverGroupIDList)
    return
def GetCrossPKServerGroupZone(serverGroupID):
    ## 获取服务器组ID对应的跨服PK所属赛区,返回0代表该服务器没有分配赛区
    crossZoneName = GameWorld.GetCrossZoneName()
    crossZoneList = IpyGameDataPY.GetIpyGameDataByCondition("CrossZonePK", {"CrossZoneName":crossZoneName}, True)
    if not crossZoneList:
        return
    for zoneIpyData in crossZoneList:
        for groupInfo in zoneIpyData.GetServerGroupIDList():
            if (isinstance(groupInfo, int) and serverGroupID == groupInfo) \
                or (isinstance(groupInfo, tuple) and len(groupInfo) == 2 and groupInfo[0] <= serverGroupID <= groupInfo[1]):
                return zoneIpyData
    return
def OnPlayerLogin(curPlayer):
    # 本服登录处理
    # 本服没有分配赛区不处理
    if not GameWorld.GetGameWorld().GetDictByKey(ShareDefine.Def_Notify_WorldKey_CrossPKZoneID):
        return
    __OnLoginNotifyPKOverInfo(curPlayer)
    crossZoneName = GameWorld.GetCrossZoneName()
    crossZoneList = IpyGameDataPY.GetIpyGameDataByCondition("CrossZonePK", {"CrossZoneName":crossZoneName}, True)
    if not crossZoneList:
        return
    # 同步所有赛区赛季信息
    zoneSeasonInfo = ChPyNetSendPack.tagGCCrossRealmPKSeasonInfo()
    zoneSeasonInfo.ZoneList = []
    for zoneIpyData in crossZoneList:
        zoneID = zoneIpyData.GetZoneID()
        zoneInfo = ChPyNetSendPack.tagGCCrossRealmPKZone()
        zoneInfo.ZoneID = zoneID
        zoneInfo.ZoneName = zoneIpyData.GetZoneName().decode(ShareDefine.Def_Game_Character_Encoding).encode(GameWorld.GetCharacterEncoding())
        zoneInfo.ZoneNameLen = len(zoneInfo.ZoneName)
        zoneInfo.SeasonList = []
        seasonList = IpyGameDataPY.GetIpyGameDataList("CrossRealmPKSeason", crossZoneName, zoneID)
        seasonList = [] if not seasonList else seasonList
        for seasonIpyData in seasonList:
            seasonInfo = ChPyNetSendPack.tagGCCrossRealmPKSeason()
            seasonInfo.SeasonID = seasonIpyData.GetSeasonID()
            seasonInfo.StartDate = seasonIpyData.GetStartDate()
            seasonInfo.EndDate = seasonIpyData.GetEndDate()
            seasonInfo.EndTime = seasonIpyData.GetEndTime()
            zoneInfo.SeasonList.append(seasonInfo)
        zoneInfo.SeasonCount = len(zoneInfo.SeasonList)
        zoneSeasonInfo.ZoneList.append(zoneInfo)
    zoneSeasonInfo.ZoneCount = len(zoneSeasonInfo.ZoneList)
    NetPackCommon.SendFakePack(curPlayer, zoneSeasonInfo)
    # 同步本服赛季状态
    gameWorld = GameWorld.GetGameWorld()
    seasonStatePack = ChPyNetSendPack.tagGCCrossRealmPKSeasonState()
    seasonStatePack.ZoneID = gameWorld.GetDictByKey(ShareDefine.Def_Notify_WorldKey_CrossPKZoneID)
    seasonStatePack.SeasonID = gameWorld.GetDictByKey(ShareDefine.Def_Notify_WorldKey_CrossPKSeasonID)
    seasonStatePack.SeasonState = gameWorld.GetDictByKey(ShareDefine.Def_Notify_WorldKey_CrossPKSeasonState)
    seasonStatePack.MatchState = gameWorld.GetDictByKey(ShareDefine.Def_Notify_WorldKey_CrossDailyActionState % ShareDefine.DailyActionID_CrossReamPK)
    seasonStatePack.CrossZoneName = GameWorld.GetCrossZoneName()
    seasonStatePack.CrossZoneNameLen = len(seasonStatePack.CrossZoneName)
    NetPackCommon.SendFakePack(curPlayer, seasonStatePack)
    return
def IsCrossRealmPKMatchState():
    ## 跨服PK匹配赛是否开启
    return 1
    return GameWorld.GetGameWorld().GetDictByKey(ShareDefine.Def_Notify_WorldKey_MergePKState) == ChConfig.Def_Action_Open
def ClientServerMsg_ServerInitOK(serverGroupID, tick):
    ## 子服启动成功
    GameWorld.Log("同步跨服PK赛季信息及状态到子服: serverGroupID=%s" % (serverGroupID))
    seasonInfo = {"SeasonID":1, "SeasonState":1, "MatchState":1}
    CrossRealmMsg.SendMsgToClientServer(ShareDefine.CrossServerMsg_PKSeasonInfo, seasonInfo, [serverGroupID])
    return
def SendCancelCrossRealmPKMatch(curPlayer, reason):
    ## 发送取消匹配
    # 跨服服务器不处理
    if GameWorld.IsCrossServer():
        return
    # 非活动中不处理
    if not IsCrossRealmPKOpen():
        return
#    # 如果是要登陆到跨服服务器的,不发送取消
#    if curPlayer.GetDictByKey(ChConfig.Def_PlayerKey_IsLoginToMergeServer):
#        GameWorld.DebugLog("本次离线为要登陆跨服服务器的自动离线行为,不发送取消匹配!", curPlayer.GetPlayerID())
#        curPlayer.SetDict(ChConfig.Def_PlayerKey_IsLoginToMergeServer, 0)
#        return
    vsRoomID = curPlayer.GetVsRoomId()
    if vsRoomID and PlayerControl.GetCrossRealmState(curPlayer) == 1:
        GameWorld.DebugLog("玩家跨服PK状态,不能取消匹配!vsRoomID=%s" % vsRoomID, curPlayer.GetPlayerID())
        return
    dataMsg = {"accID":curPlayer.GetAccID(), # 账号
               "playerID":curPlayer.GetPlayerID(), # 玩家ID
               "playerName":curPlayer.GetName(), # 跨服子服玩家名
               "reason":reason, # 取消原因
               "vsRoomID":vsRoomID, # 对战房间ID
               }
    CrossRealmMsg.SendMsgToCrossServer(ShareDefine.ClientServerMsg_PKCancel, dataMsg)
    PlayerControl.SetVsRoomId(curPlayer, 0)
    GameWorld.Log("发送取消跨服PK匹配到跨服服务器:dataMsg=%s" % str(dataMsg), curPlayer.GetPlayerID())
    return
    return GameWorld.GetGameWorld().GetDictByKey(ShareDefine.Def_Notify_WorldKey_CrossDailyActionState \
                                                 % ShareDefine.DailyActionID_CrossReamPK) == ChConfig.Def_Action_Open
def ClientServerMsg_PKMatch(serverGroupID, playerInfoDict, tick):
    ## 请求匹配
    if not GameWorld.IsCrossServer():
        GameWorld.ErrLog("非跨服服务器不处理跨服PK匹配请求!")
        return
    if not IsCrossRealmPKOpen():
        GameWorld.Log("跨服匹配PK活动未开启,不允许请求匹配!")
        return
    seasonID = playerInfoDict["seasonID"] # 赛季ID
    pkZoneID = playerInfoDict["pkZoneID"] # 所属赛区
    accID = playerInfoDict["accID"] # 角色账号
    zoneID = playerInfoDict["zoneID"] # 所属赛区
    playerID = playerInfoDict["playerID"] # 角色ID
    playerName = playerInfoDict["playerName"] # 玩家名
    job = playerInfoDict["playerJob"] # ְҵ
    playerLV = playerInfoDict["playerLV"] # ְҵ
    maxHP = playerInfoDict["maxHP"] # ְҵ
    fightPower = playerInfoDict["fightPower"] # 战斗力
    pkScore = playerInfoDict["pkScore"] # 当前积分
    danLV = playerInfoDict["danLV"] # 当前段位
    cWinCount = playerInfoDict["cWinCount"] # 连胜次数
    ondayScore = playerInfoDict["ondayScore"] # 过天时的积分
    fightPower = playerInfoDict["fightPower"]
    requestType = playerInfoDict.get("requestType", 0)
    
    zoneMatchPlayerList = PyGameData.g_crossPKZoneMatchPlayerDict.get(pkZoneID, [])
#    if playerID in zoneMatchPlayerList:
#        GameWorld.Log("玩家正在匹配中,无法重复发起匹配!playerID=%s,accID=%s" % (playerID, accID))
#        CrossRealmMsg.SendMsgToClientServer(ShareDefine.CrossServerMsg_PKMatchReqRet, [playerID, 1], [serverGroupID])
#        return
#    if playerID in PyGameData.g_crossPKPlayerDict:
#        GameWorld.Log("玩家正在战斗中,无法重复发起匹配!playerID=%s,accID=%s" % (playerID, accID))
#        CrossRealmMsg.SendMsgToClientServer(ShareDefine.CrossServerMsg_PKMatchReqRet, [playerID, -2], [serverGroupID])
#        return
    pkPlayer = CrossPKPlayer()
    pkPlayer.accID = accID
    pkPlayer.playerID = playerID
    pkPlayer.playerName = playerName
    pkPlayer.playerJob = job
    pkPlayer.playerLV = playerLV
    pkPlayer.maxHP = maxHP
    pkPlayer.pkScore = pkScore
    pkPlayer.danLV = danLV
    pkPlayer.fightPower = fightPower
    pkPlayer.matchTick = tick
    pkPlayer.cWinCount = cWinCount
    pkPlayer.ondayScore = ondayScore
    pkPlayer.serverGroupID = serverGroupID
    pkPlayer.pkZoneID = pkZoneID
    pkPlayer.seasonID = seasonID
    PyGameData.g_crossPKPlayerDict[playerID] = pkPlayer
    # 加入赛区匹配列表
    zoneMatchPlayerList.append(playerID)
    PyGameData.g_crossPKZoneMatchPlayerDict[pkZoneID] = zoneMatchPlayerList
    GameWorld.Log("玩家加入匹配: seasonID=%s,pkZoneID=%s,serverGroupID=%s,accID=%s,playerID=%s,pkScore=%s,fightPower=%s,cWinCount=%s,len(zoneMatchPlayerList)=%s"
                  % (seasonID, pkZoneID, serverGroupID, accID, playerID, pkScore, fightPower, cWinCount, len(zoneMatchPlayerList)))
    CrossRealmMsg.SendMsgToClientServer(ShareDefine.CrossServerMsg_PKMatchReqRet, [playerID, 1], [serverGroupID])
    isRefresh = requestType == 1
    OnRefreshPKMatch(zoneID, seasonID, playerID, fightPower, serverGroupID, isRefresh)
    return
def ClientServerMsg_PKCancel(playerInfoDict, tick):
    ## 取消匹配
def ClientServerMsg_PKOver(serverGroupID, playerInfoDict, tick):
    ## 收到子服同步的PK结算
    
    if not GameWorld.IsCrossServer():
        GameWorld.ErrLog("非跨服服务器不处理取消跨服PK匹配!")
    playerID = playerInfoDict["playerID"] # 角色ID
    tagPlayerID = playerInfoDict["tagPlayerID"] # 目标玩家ID
    isWinner = playerInfoDict["isWinner"] # 是否获胜
    zoneID = playerInfoDict["pkZoneID"]
    seasonID = playerInfoDict["seasonID"]
    playerName = playerInfoDict["playerName"]
    playerJob = playerInfoDict["playerJob"]
    face = playerInfoDict["face"]
    facePic = playerInfoDict["facePic"]
    realmLV = playerInfoDict["realmLV"]
    fightPower = playerInfoDict["fightPower"]
    pkScore = playerInfoDict["pkScore"]
    danLV = playerInfoDict["danLV"]
    cWinCount = playerInfoDict["cWinCount"]
    if playerID not in PyGameData.g_crossPKMatchDict:
        GameWorld.ErrLog("玩家匹配,无法结算跨服PK奖励! tagPlayerID=%s,isWinner=%s,pkScore=%s,cWinCount=%s"
                         % (tagPlayerID, isWinner, pkScore, cWinCount), playerID)
        return
    # 非活动中不处理
    if not IsCrossRealmPKOpen():
    matchIDList = PyGameData.g_crossPKMatchDict[playerID]
    if tagPlayerID not in matchIDList:
        GameWorld.ErrLog("目标玩家ID不在玩家匹配列表里,无法结算跨服PK奖励! tagPlayerID=%s not in %s ,isWinner=%s,pkScore=%s,cWinCount=%s"
                         % (tagPlayerID, matchIDList, isWinner, pkScore, cWinCount), playerID)
        return
    matchIndex = matchIDList.index(tagPlayerID)
    
    accID = playerInfoDict["accID"] # 角色账号
    playerID = playerInfoDict["playerID"] # 玩家ID
    reason = playerInfoDict["reason"] # 取消原因
    vsRoomID = playerInfoDict["vsRoomID"] # 所属对战房间ID
    if vsRoomID in PyGameData.g_crossPKRoomDict:
        pkRoom = PyGameData.g_crossPKRoomDict[vsRoomID]
        if pkRoom.isMapOpen or pkRoom.readyTick:
            GameWorld.Log("跨服对战房间已经开启了线路,或者双方数据都已传输完毕,不可再取消匹配!vsRoomID=%s" % vsRoomID)
            return
    addScore = IpyGameDataPY.GetFuncCfg("CrossRealmPKScore2", 2) # 默认失败积分
    if isWinner:
        baseScoreList = IpyGameDataPY.GetFuncEvalCfg("CrossRealmPKScore2", 1)
        baseScore = baseScoreList[matchIndex] if len(baseScoreList) > matchIndex else 0
        wExScore = eval(IpyGameDataPY.GetFuncCompileCfg("CrossRealmPKScore2", 3)) # 胜方附加分
        addScore = baseScore + wExScore
        cWinCount += 1
    else:
        cWinCount = 0
        
    GameWorld.Log("玩家取消匹配: reason=%s,accID=%s,playerID=%s,vsRoomID=%s" % (reason, accID, playerID, vsRoomID))
    updScore = pkScore + addScore
    pkDanIpyData = IpyGameDataPY.GetIpyGameData("CrossRealmPKDan", danLV)
    if pkDanIpyData and pkDanIpyData.GetLVUpScore() and updScore >= pkDanIpyData.GetLVUpScore():
        danLV += 1
    GameWorld.Log("结算跨服PK奖励: tagPlayerID=%s,isWinner=%s,pkScore=%s,cWinCount=%s,addScore=%s,updScore=%s,danLV=%s"
                         % (tagPlayerID, isWinner, pkScore, cWinCount, addScore, updScore, danLV), playerID)
    
    pkZoneID = 0
    if playerID in PyGameData.g_crossPKPlayerDict:
        pkPlayer = PyGameData.g_crossPKPlayerDict.pop(playerID)
        pkZoneID = pkPlayer.pkZoneID
        GameWorld.Log("    移除PK玩家: pkZoneID=%s,accID=%s,playerID=%s" % (pkZoneID, accID, playerID))
    zoneMatchPlayerList = PyGameData.g_crossPKZoneMatchPlayerDict.get(pkZoneID, [])
    if playerID in zoneMatchPlayerList:
        zoneMatchPlayerList.remove(playerID)
        GameWorld.Log("    从匹配队列中删除,匹配队列剩余人数=%s" % (len(zoneMatchPlayerList)))
    #取消所有存在该玩家的房间,子服不一定知道玩家当前最新所属房间ID, 故只能通过遍历删除已经为玩家创建的房间
    for roomID, pkRoom in PyGameData.g_crossPKRoomDict.items():
        if playerID not in pkRoom.roomPlayerIDList:
            continue
        for roomPlayerID in pkRoom.roomPlayerIDList:
            if roomPlayerID == playerID:
                GameWorld.Log("    自己不处理: roomID=%s,playerID=%s" % (roomID, playerID))
                continue
            zoneMatchPlayerList = PyGameData.g_crossPKZoneMatchPlayerDict.get(pkZoneID, [])
            zoneMatchPlayerList.append(roomPlayerID)
            PyGameData.g_crossPKZoneMatchPlayerDict[pkZoneID] = zoneMatchPlayerList
            GameWorld.Log("    将之前匹配的对手重新加入匹配队列: roomID=%s,roomPlayerID=%s,当前匹配人数=%s"
                          % (roomID, roomPlayerID, len(zoneMatchPlayerList)))
        PyGameData.g_crossPKRoomDict.pop(roomID)
        GameWorld.Log("    移除房间: popRoomID=%s" % (roomID))
        break
    # 更新榜单
    groupValue1, groupValue2 = zoneID, seasonID
    name2, type2 = "", playerJob
    value1, value2 = realmLV, danLV
    cmpValue = updScore
    CrossBillboard.UpdCrossBillboard(ShareDefine.Def_CBT_CrossRealmPK, groupValue1, playerID, playerName, name2, type2,
                                     value1, value2, cmpValue, groupValue2=groupValue2, value3=face, value4=facePic)
    
    return
def ClientServerMsg_PKPrepareOK(playerInfoDict, tick):
    ## 玩家跨服对战数据准备OK
    if not GameWorld.IsCrossServer():
        GameWorld.ErrLog("非跨服服务器不处理取消跨服PK匹配!")
        return
    accID = playerInfoDict["accID"] # 玩家账号
    playerID = playerInfoDict["playerID"] # 玩家ID
    vsRoomID = playerInfoDict["vsRoomID"] # 所属对战房间ID
    if playerID not in PyGameData.g_crossPKPlayerDict:
        GameWorld.ErrLog("玩家跨服对战数据准备OK, 但找不到该对战玩家信息!vsRoomID=%s,playerID=%s" % (vsRoomID, playerID))
        return
    #pkPlayer = PyGameData.g_crossPKPlayerDict[playerID]
    if vsRoomID not in PyGameData.g_crossPKRoomDict:
        GameWorld.ErrLog("玩家跨服对战数据准备OK, 但找不到该对战房间(%s)!可能对手已取消!" % vsRoomID)
        return
    vsRoom = PyGameData.g_crossPKRoomDict[vsRoomID]
    if vsRoom.roomState != ShareDefine.Def_VsRoom_State_WaitPlayer:
        GameWorld.ErrLog("玩家跨服对战数据准备OK, 但房间状态非等待状态, state=%s!" % vsRoom.roomState)
        return
    if playerID not in vsRoom.readyPlayerIDList:
        vsRoom.readyPlayerIDList.append(playerID)
    GameWorld.Log("玩家跨服PK准备完毕: accID=%s,playerID=%s,vsRoomID=%s" % (accID, playerID, vsRoomID))
    return
def __ReadyOKRoomPlayerProcess(tick):
    ## 玩家跨服PK已准备好的房间处理
    #GameWorld.Log("===已准备好的对战房间处理===")
    serverGroupIDList = []
    sendReadyOKRoomList = []
    for roomID, vsRoom in PyGameData.g_crossPKRoomDict.items():
        # 非等待状态的房间不处理
        if vsRoom.roomState != ShareDefine.Def_VsRoom_State_WaitPlayer:
            continue
        if not vsRoom.roomPlayerIDList:
            continue
        pkZoneID = 0
        isAllReady = True
        roomGroupIDList = []
        readyMemberDict = {} # 已准备好的玩家信息
        for roomPlayerID in vsRoom.roomPlayerIDList:
            if roomPlayerID not in vsRoom.readyPlayerIDList or roomPlayerID not in PyGameData.g_crossPKPlayerDict:
                isAllReady = False
                break
            roomPlayer = PyGameData.g_crossPKPlayerDict[roomPlayerID]
            pkZoneID = roomPlayer.pkZoneID
            roomGroupIDList.append(roomPlayer.serverGroupID)
            readyMemberDict[roomPlayerID] = {"ServerGroupID":roomPlayer.serverGroupID, "Name":roomPlayer.playerName,
                                             "Job":roomPlayer.playerJob, "LV":roomPlayer.playerLV, "MaxHP":roomPlayer.maxHP}
        if not isAllReady:
            continue
        vsRoom.roomState = ShareDefine.Def_VsRoom_State_PrepareFight
        vsRoom.readyTick = tick
        GameWorld.Log("    准备好的房间: pkZoneID=%s,roomID=%s,mapID=%s,readyMemberDict=%s" % (pkZoneID, roomID, vsRoom.mapID, str(readyMemberDict)))
        sendReadyOKRoomList.append([roomID, readyMemberDict])
        serverGroupIDList += roomGroupIDList
    # 将已准备好的房间广播到子服
    if sendReadyOKRoomList:
        GameWorld.Log("    已准备好的对战房间数: %s" % len(sendReadyOKRoomList))
        CrossRealmMsg.SendMsgToClientServer(ShareDefine.CrossServerMsg_PKReadyOKRoomList, sendReadyOKRoomList, serverGroupIDList)
    return
def OnPKMatchProcess(tick):
    ## 玩家跨服PK匹配定时处理逻辑
    # 非跨服服务器不处理跨服PK匹配逻辑
    if not GameWorld.IsCrossServer():
        return
    if not IsCrossRealmPKOpen():
        return
    # 同步子服排行榜
    #__SyncBillboardToClientServer(False, tick)
    processTick = IpyGameDataPY.GetFuncCfg("CrossRealmPKMatch", 1) * 1000
    processTickKey = "PKMatchLastTick"
    lastProcessTick = GameWorld.GetGameWorld().GetDictByKey(processTickKey)
    if tick - lastProcessTick < processTick:
        return
    GameWorld.GetGameWorld().SetDict(processTickKey, tick)
    # 处理超时的房间
    __DoCheckRoomTimeout(tick)
    # 通知已准备好的房间玩家可进入跨服
    __ReadyOKRoomPlayerProcess(tick)
    maxGroupCnt = IpyGameDataPY.GetFuncCfg("CrossRealmPKMatch", 2)
    outTimeTick = IpyGameDataPY.GetFuncCfg("CrossRealmPKMatch", 3) * 1000
    # 每个赛区单独匹配
    for pkZoneID, matchPlayerIDList in PyGameData.g_crossPKZoneMatchPlayerDict.items():
        matchPlayerCount = len(matchPlayerIDList)
        if matchPlayerCount < 2:
            #GameWorld.Log("匹配PK人数不足,不处理!pkZoneID=%s, 总人数:%s" % (pkZoneID, matchPlayerCount))
            continue
        GameWorld.Log("★★★★★★★★★★开始跨服PK匹配(pkZoneID=%s, 总人数:%s)★★★★★★★★★★" % (pkZoneID, matchPlayerCount))
        matchPlayerList = []
        for matchPlayerID in matchPlayerIDList:
            if matchPlayerID not in PyGameData.g_crossPKPlayerDict:
                continue
            matchPlayerList.append(PyGameData.g_crossPKPlayerDict[matchPlayerID])
        # 按匹配时间、积分升序排序
        matchTickSortList = sorted(matchPlayerList, key=operator.attrgetter("matchTick"))
        scoreSortList = sorted(matchPlayerList, key=operator.attrgetter("pkScore"))
        matchPlayerVSList = [] # 成功匹配玩家对战列表
        # 优先匹配等待超时玩家
        __DoMatch_OutTimePlayer(matchTickSortList, scoreSortList, outTimeTick, matchPlayerVSList, tick)
        if len(matchPlayerVSList) < maxGroupCnt:
            # 再按积分段匹配玩家
            __DoMatch_DanScorePlayer(scoreSortList, maxGroupCnt, matchPlayerVSList)
        # 给成功匹配的玩家非配对战房间
        matchPlayerVSList = matchPlayerVSList[:maxGroupCnt]
        __DoSetVSRoom(pkZoneID, matchPlayerVSList, tick)
        GameWorld.Log("==========匹配结束(总匹配队伍:%s)==========" % len(matchPlayerVSList))
    return
def __DoMatch_OutTimePlayer(matchTickSortList, scoreSortList, outTimeTick, matchPlayerVSList, tick):
    '''匹配超时玩家
        匹配中的玩家按积分排序,最后一个默认匹配上一个,第一个默认匹配下一个,其他匹配前后积分差绝对值较小的一个
    '''
    GameWorld.Log(" ==优先匹配超时等待玩家==最大等待时间:%s, tick=%s" % (outTimeTick, tick))
    GameWorld.Log(" scoreSortListLen=%s" % len(scoreSortList))
    for i, matchPlayer in enumerate(matchTickSortList):
        # 只有一个玩家
        if len(scoreSortList) <= 1:
            GameWorld.Log("    当前玩家数%s<=1,不再匹配!" % len(scoreSortList))
            break
        if tick - matchPlayer.matchTick < outTimeTick:
            GameWorld.Log("    i=%s,玩家未超时,不再匹配!" % (i))
            break
        GameWorld.Log("    i=%s,超时玩家, %s-%s=%s >= outTimeTick(%s)"
                      % (i, tick, matchPlayer.matchTick, tick - matchPlayer.matchTick, outTimeTick))
        # 已经被匹配走了
        if matchPlayer not in scoreSortList:
            GameWorld.Log("        已经被匹配走了!")
            continue
        outTimeIndex = scoreSortList.index(matchPlayer)
        # 最后一个默认匹配上一个
        if outTimeIndex == len(scoreSortList) - 1:
            vsIndex = outTimeIndex - 1
            GameWorld.Log("        超时玩家积分排序索引%s,最后一个,默认匹配上一个索引%s!" % (outTimeIndex, vsIndex))
        # 第一个默认匹配下一个
        elif outTimeIndex == 0:
            vsIndex = outTimeIndex + 1
            GameWorld.Log("        超时玩家积分排序索引%s,第一个,默认匹配下一个索引%s!" % (outTimeIndex, vsIndex))
        # 其他情况匹配积分较近的一个
        else:
            preIndex = outTimeIndex - 1
            nextIndex = outTimeIndex + 1
            prePlayer = scoreSortList[preIndex]
            nextPlayer = scoreSortList[nextIndex]
            preDiff = abs(prePlayer.pkScore - matchPlayer.pkScore)
            nextDiff = abs(matchPlayer.pkScore - nextPlayer.pkScore)
            vsIndex = preIndex if preDiff <= nextDiff else nextIndex
            GameWorld.Log("        超时玩家积分排序索引-积分(%s-%s),上一个(%s-%s),下一个(%s-%s),preDiff=%s,nextDiff=%s,vsIndex=%s"
                          % (outTimeIndex, matchPlayer.pkScore, preIndex, prePlayer.pkScore,
                             nextIndex, nextPlayer.pkScore, preDiff, nextDiff, vsIndex))
        if outTimeIndex > vsIndex:
            scoreSortList.pop(outTimeIndex)
            vsPlayer = scoreSortList.pop(vsIndex)
        elif outTimeIndex < vsIndex:
            vsPlayer = scoreSortList.pop(vsIndex)
            scoreSortList.pop(outTimeIndex)
        else:
            continue
        # 加入成功匹配列表
        matchPlayerVSList.append([matchPlayer, vsPlayer])
    return
def __DoMatch_DanScorePlayer(scoreSortList, maxGroupCnt, matchPlayerVSList):
    ''' 匹配积分分段玩家
            匹配中的玩家按段位积分归组,归组后,随机段位顺序,每个段位组中的玩家随机两两PK
    '''
    GameWorld.Log(" ==匹配积分分段玩家== maxGroupCnt=%s,scoreSortListLen=%s" % (maxGroupCnt, len(scoreSortList)))
    danPlayerListDict = {} # 按积分分段列表分散玩家
    for matchPlayer in scoreSortList:
        danLV = matchPlayer.danLV
        danPlayerList = danPlayerListDict.get(danLV, [])
        danPlayerList.append(matchPlayer)
        danPlayerListDict[danLV] = danPlayerList
    # 按分段玩家随机匹配
    danList = danPlayerListDict.keys()
    random.shuffle(danList) # 打乱段位顺序
    GameWorld.Log("    积分分段个数: %s, %s" % (len(danList), danList))
    # 日志输出分组明细
    for danLV in danList:
        strList = []
        for player in danPlayerListDict[danLV]:
            strList.append((player.playerID, player.pkScore, player.fightPower))
        GameWorld.Log("        积分段组, danLV=%s, %s" % (danLV, str(strList)))
    doCount = 0
    while len(matchPlayerVSList) < maxGroupCnt and doCount < maxGroupCnt:
        doCount += 1
        isMatchOK = False
        for danLV in danList:
            danPlayerList = danPlayerListDict[danLV]
            danPlayerCount = len(danPlayerList)
            if danPlayerCount < 2:
                GameWorld.Log("    段位人数少于2个,此段位本轮轮空!doCount=%s,danLV=%s" % (doCount, danLV))
                continue
            vsIndexList = random.sample(xrange(danPlayerCount), 2) # 随机取两个索引对战
            vsIndexList.sort()
            aPlayer = danPlayerList.pop(vsIndexList[1])
            bPlayer = danPlayerList.pop(vsIndexList[0])
            matchPlayerVSList.append([aPlayer, bPlayer])
            isMatchOK = True
            GameWorld.Log("    成功匹配玩家: aPlayerID=%s,aScore=%s,aFP=%s VS bPlayerID=%s,bScore=%s,bFP=%s"
                          % (aPlayer.playerID, aPlayer.pkScore, aPlayer.fightPower, bPlayer.playerID, bPlayer.pkScore, bPlayer.fightPower))
            if len(matchPlayerVSList) >= maxGroupCnt:
                GameWorld.Log("    已经达到最大匹配数! 已匹配对战数=%s, 不再匹配!doCount=%s" % (len(matchPlayerVSList), doCount))
                break
        if not isMatchOK:
            GameWorld.Log("    已经没有满足匹配条件的玩家! 不再匹配!doCount=%s" % (doCount))
            break
    return
def __DoSetVSRoom(pkZoneID, matchPlayerVSList, tick):
    ## 设置对战房间
    if not matchPlayerVSList:
        return
    vsRoomDict = {}
    serverGroupIDList = []
    zoneMatchPlayerList = PyGameData.g_crossPKZoneMatchPlayerDict.get(pkZoneID, [])
    mapIDList = IpyGameDataPY.GetFuncCfg("CrossRealmPKMatch", 4)
    GameWorld.Log("===给配对的玩家开房间(pkZoneID=%s,配对数:%s)===" % (pkZoneID, len(matchPlayerVSList)))
    for aPlayer, bPlayer in matchPlayerVSList:
        if not aPlayer or not bPlayer:
            continue
        aPlayerID = aPlayer.playerID
        bPlayerID = bPlayer.playerID
        if aPlayerID not in PyGameData.g_crossPKPlayerDict or bPlayerID not in PyGameData.g_crossPKPlayerDict:
            GameWorld.ErrLog("玩家匹配数据异常!aPlayerID=%s,bPlayerID=%s" % (aPlayerID, bPlayerID))
            continue
        roomID = __GetNewRoomID()
        if not roomID:
            GameWorld.ErrLog("无法创建房间!该房间已经存在!PyGameData.g_crossPKRoomID=%s" % PyGameData.g_crossPKRoomID)
            continue
        mapID = random.choice(mapIDList)
        newRoom = CrossPKRoom()
        newRoom.pkZoneID = pkZoneID
        newRoom.roomID = roomID
        newRoom.mapID = mapID
        newRoom.openTick = tick
        newRoom.roomPlayerIDList = [aPlayerID, bPlayerID]
        PyGameData.g_crossPKRoomDict[roomID] = newRoom
        aServerGroupID, bServerGroupID = aPlayer.serverGroupID, bPlayer.serverGroupID
        GameWorld.Log("    开房:pkZoneID=%s,mapID=%s,roomID=%s,aPlayerID=%s,bPlayerID=%s" % (pkZoneID, mapID, roomID, aPlayerID, bPlayerID))
        vsRoomDict[roomID] = [mapID, [[aServerGroupID, aPlayerID], [bServerGroupID, bPlayerID]]]
        serverGroupIDList.append(aServerGroupID)
        serverGroupIDList.append(bServerGroupID)
        # 移除匹配队列
        if aPlayerID in zoneMatchPlayerList:
            zoneMatchPlayerList.remove(aPlayerID)
        if bPlayerID in zoneMatchPlayerList:
            zoneMatchPlayerList.remove(bPlayerID)
        PyGameData.g_crossPKZoneMatchPlayerDict[pkZoneID] = zoneMatchPlayerList
    # 将匹配结果广播到子服
    if vsRoomDict:
        CrossRealmMsg.SendMsgToClientServer(ShareDefine.CrossServerMsg_PKMatchResult, vsRoomDict, serverGroupIDList)
    return
def __GetNewRoomID():
    ## 获取新房间ID, 房间号直接自增,一定不会重复,除非自增一轮后房间ID还没有释放
    for _ in xrange(100):
        newRoomID = PyGameData.g_crossPKRoomID + 1
        if newRoomID > 65530:
            newRoomID = 1
        PyGameData.g_crossPKRoomID = newRoomID
        if newRoomID not in PyGameData.g_crossPKRoomDict:
            return newRoomID
    return 0
def __DoCheckRoomTimeout(tick):
    ## 处理超时的房间
    timeoutRoomDict = {}
    serverGroupIDList = []
    #roomTimeout = IpyGameDataPY.GetFuncCfg("CheckRoomTimeout", 1) * 1000 # 这个时间尽量长点,目前暂时不确定玩家从准备好到进入到地图的时长
    roomTimeout = 180 * 1000 # 这个时间尽量长点,目前暂时不确定玩家从准备到进入到地图的时长
    for roomID, pkRoom in PyGameData.g_crossPKRoomDict.items():
        if pkRoom.isMapOpen or not pkRoom.readyTick:
            continue
        if tick - pkRoom.readyTick <= roomTimeout:
            continue
        pkZoneID = pkRoom.pkZoneID
        GameWorld.Log("PK房间等待玩家进来超时,没有玩家进来,关闭该房间!pkZoneID=%s,roomID=%s,openTick=%s,readyTick=%s,tick=%s"
                      % (pkZoneID, roomID, pkRoom.openTick, pkRoom.readyTick, tick))
        roomPlayerInfo = []
        for roomPlayerID in pkRoom.roomPlayerIDList:
            pkPlayer = PyGameData.g_crossPKPlayerDict.pop(roomPlayerID, None)
            if not pkPlayer:
                continue
            serverGroupID = pkPlayer.serverGroupID
            GameWorld.Log("    移除玩家,玩家需重新手动匹配,serverGroupID=%s,roomPlayerID=%s" % (serverGroupID, roomPlayerID))
            serverGroupIDList.append(serverGroupID)
            roomPlayerInfo.append([serverGroupID, roomPlayerID])
        timeoutRoomDict[roomID] = roomPlayerInfo
        PyGameData.g_crossPKRoomDict.pop(roomID)
    # 将超时房间广播到子服
    if timeoutRoomDict:
        CrossRealmMsg.SendMsgToClientServer(ShareDefine.CrossServerMsg_PKTimeoutRoomList, timeoutRoomDict, serverGroupIDList)
    return
def MapServer_CrossPKRoomOpen(msgList):
    roomID = msgList[0]
    if roomID not in PyGameData.g_crossPKRoomDict:
        GameWorld.ErrLog("MapServer_CrossPKRoomOpen => PK房间不存在!roomID=%s" % roomID)
        return
    pkRoom = PyGameData.g_crossPKRoomDict[roomID]
    pkRoom.isMapOpen = True
    GameWorld.Log("MapServer_CrossPKRoomOpen => roomID=%s" % roomID)
    return
def MapServer_MergePKOver(infoList):
    ## 收到MapServer副本跨服PK结果同步
    GameWorld.Log("收到MapServer_跨服PK战斗结果: %s" % str(infoList))
    roomID, winnerID, loserID, roundWinnerIDList, overType = infoList
    if roomID not in PyGameData.g_crossPKRoomDict:
        GameWorld.ErrLog("跨服PK房间数据不存在!roomID=%s" % roomID)
        return
    vsRoom = PyGameData.g_crossPKRoomDict.pop(roomID)
    #vsRoom = PyGameData.g_crossPKRoomDict[roomID]
    roomPlayerIDList = vsRoom.roomPlayerIDList
    if not winnerID and not loserID:
        GameWorld.ErrLog("地图没有结算跨服PK胜负玩家,随机玩家获胜!")
        if not roomPlayerIDList or len(roomPlayerIDList) != 2:
            return
        winnerID, loserID = roomPlayerIDList
    elif not loserID:
        for roomPlayerID in roomPlayerIDList:
            if roomPlayerID != winnerID:
                loserID = roomPlayerID
                break
    if winnerID not in roomPlayerIDList or loserID not in roomPlayerIDList:
        GameWorld.ErrLog("跨服PK房间及玩家不匹配,不结算!roomID=%s,winnerID=%s,loserID=%s,roomPlayerIDList=%s"
                         % (roomID, winnerID, loserID, vsRoom.roomPlayerIDList))
        return
    if winnerID not in PyGameData.g_crossPKPlayerDict:
        GameWorld.ErrLog("跨服PK房间获取不到玩家PK数据, roomID=%s,winnerID=%s" % (roomID, winnerID))
        return
    if loserID not in PyGameData.g_crossPKPlayerDict:
        GameWorld.ErrLog("跨服PK房间获取不到玩家PK数据, roomID=%s,loserID=%s" % (roomID, loserID))
        return
    winner = PyGameData.g_crossPKPlayerDict.pop(winnerID)
    loser = PyGameData.g_crossPKPlayerDict.pop(loserID)
    #winner = PyGameData.g_crossPKPlayerDict[winnerID]
    #loser = PyGameData.g_crossPKPlayerDict[loserID]
    seasonID = winner.seasonID
    cWinCount = winner.cWinCount
    winnerScore, loserScore = winner.pkScore, loser.pkScore
    winnerDanLV, loserDanLV = winner.danLV, loser.danLV
    winnerDayScore, loserDayScore = max(0, winnerScore - winner.ondayScore), max(0, loserScore - loser.ondayScore) # 今日已获得积分,正积分
    GameWorld.Log("winnerDayScore=%s,winnerScore=%s,winnerDanLV=%s,cWinCount=%s" % (winnerDayScore, winnerScore, winnerDanLV, cWinCount))
    GameWorld.Log("loserDayScore=%s,loserScore=%s,loserDanLV=%s" % (loserDayScore, loserScore, loserDanLV))
    winIpyData = IpyGameDataPY.GetIpyGameData("CrossRealmPKDan", winnerDanLV)
    loseIpyData = IpyGameDataPY.GetIpyGameData("CrossRealmPKDan", loserDanLV)
    if not winIpyData or not loseIpyData:
        GameWorld.ErrLog("跨服PK房间段位数据异常! roomID=%s,winnerDanLV=%s,loserDanLV=%s" % (roomID, winnerDanLV, loserDanLV))
    baseScoreList = IpyGameDataPY.GetFuncEvalCfg("CrossRealmPKScore", 2) # 胜负保底分
    wBaseScore = baseScoreList[0] if len(baseScoreList) > 0 else 0
    lBaseScore = baseScoreList[1] if len(baseScoreList) > 1 else 0
    wExScore = eval(IpyGameDataPY.GetFuncCompileCfg("CrossRealmPKScore", 3)) # 胜方附加分
    lExScore = 0
    winnerAddScore = wBaseScore + wExScore
    loserAddScore = lBaseScore + lExScore
    dayMaxScore = IpyGameDataPY.GetFuncCfg("CrossRealmPKScore", 1) # 每日获得积分上限,0为不限制
    if dayMaxScore:
        if winnerAddScore > 0:
            winnerAddScore = min(dayMaxScore - winnerDayScore, winnerAddScore)
        if loserAddScore > 0:
            loserAddScore = min(dayMaxScore - loserDayScore, loserAddScore)
    winner.pkScore += winnerAddScore
    loser.pkScore += loserAddScore
    winner.cWinCount += 1
    loser.cWinCount = 0
    if winIpyData and winIpyData.GetLVUpScore() and winner.pkScore >= winIpyData.GetLVUpScore():
        winner.danLV += 1
    if loseIpyData and loseIpyData.GetLVUpScore() and loser.pkScore >= loseIpyData.GetLVUpScore():
        loser.danLV += 1
    GameWorld.Log("wBaseScore=%s,wExScore=%s,winnerAddScore=%s,updScore=%s,updDanLV=%s,updCWinCount=%s" % (wBaseScore, wExScore, winnerAddScore, winner.pkScore, winner.danLV, winner.cWinCount))
    GameWorld.Log("lBaseScore=%s,lExScore=%s,loserAddScore=%s,updScore=%s,updDanLV=%s,updCWinCount=%s" % (lBaseScore, lExScore, loserAddScore, loser.pkScore, loser.danLV, loser.cWinCount))
    # 通知子服
    pkScore = updScore
    packDataMgr = PyDataManager.GetDBPlayerPackDataManager()
    tagPackObj = packDataMgr.GetPlayerPackObj(tagPlayerID)
    tagPlayerName = tagPackObj.playerName if tagPackObj else ""
    winnerID = playerID if isWinner else tagPlayerID
    timeStr = GameWorld.GetCurrentDataTimeStr()
    playerOverDict = {}
    # 通知客户端战斗结果
    for playerID in [winnerID, loserID]:
        if playerID == winnerID:
            serverGroupID, pkScore, danLV, cWinCount, addScore, tagPlayerID, tagPlayerName = \
                winner.serverGroupID, winner.pkScore, winner.danLV, winner.cWinCount, winnerAddScore, loser.playerID, loser.playerName
        else:
            serverGroupID, pkScore, danLV, cWinCount, addScore, tagPlayerID, tagPlayerName = \
                loser.serverGroupID, loser.pkScore, loser.danLV, loser.cWinCount, loserAddScore, winner.playerID, winner.playerName
        player = GameWorld.GetPlayerManager().FindPlayerByID(playerID)
        notifyState = True if player else False
        playerOverDict[playerID] = [roomID, seasonID, timeStr, overType, winnerID, roundWinnerIDList] \
                                    + [serverGroupID, pkScore, danLV, cWinCount, addScore, tagPlayerID, tagPlayerName, notifyState]
        if not player:
            continue
        overPack = ChPyNetSendPack.tagGCCrossRealmPKOverInfo()
        overPack.TimeStr = timeStr
        overPack.OverType = overType
        overPack.WinnerID = winnerID
        overPack.RoundWinnerID = roundWinnerIDList
        overPack.RoundCount = len(overPack.RoundWinnerID)
        overPack.AddScore = addScore
        overPack.Score = pkScore
        overPack.DanLV = danLV
        overPack.CWinCnt = cWinCount
        overPack.TagName = tagPlayerName
        overPack.TagNameLen = len(overPack.TagName)
        NetPackCommon.SendFakePack(player, overPack)
        GameWorld.Log("同步玩家PK结果: serverGroupID=%s,roomID=%s,addScore=%s,pkScore=%s,danLV=%s,cWinCount=%s,tagPlayerID=%s"
                      % (serverGroupID, roomID, addScore, pkScore, danLV, cWinCount, tagPlayerID), playerID)
    serverGroupIDList = [winner.serverGroupID, loser.serverGroupID]
    GameWorld.Log("同步子服战斗结果: seasonID=%s,timeStr=%s,roomID=%s,overType=%s,winnerID=%s,roundWinnerIDList=%s"
                  % (seasonID, timeStr, roomID, overType, winnerID, roundWinnerIDList))
    # 同步子服
    playerOverDict[playerID] = [zoneID, seasonID, timeStr, winnerID, pkScore, danLV, cWinCount, addScore, tagPlayerID, tagPlayerName]
    serverGroupIDList = [serverGroupID]
    CrossRealmMsg.SendMsgToClientServer(ShareDefine.CrossServerMsg_PKOverInfo, playerOverDict, serverGroupIDList)
    # 系统重新刷新匹配
    OnRefreshPKMatch(zoneID, seasonID, playerID, fightPower, serverGroupID, True)
    return
def OnRefreshPKMatch(zoneID, seasonID, playerID, fightPower, serverGroupID, isRefresh):
    # 刷新匹配数据
    # @param isRefresh: 是否强制重新刷新
    if isRefresh or playerID not in PyGameData.g_crossPKMatchDict:
        # 执行匹配逻辑
        matchIDList = __DoPKMatch(zoneID, seasonID, playerID, fightPower)
        if matchIDList: # 有新结果才替换
            PyGameData.g_crossPKMatchDict[playerID] = matchIDList
    packDataMgr = PyDataManager.GetDBPlayerPackDataManager()
    matchIDList = PyGameData.g_crossPKMatchDict.get(playerID, [])
    matchInfoDict = {}
    for matchID in matchIDList:
        packObj = packDataMgr.GetPlayerPackObj(matchID)
        if not packObj:
            continue
        matchInfoDict[matchID] = packObj.GetBaseDict()
    dataMsg = {"playerID":playerID, "matchIDList":matchIDList, "matchInfoDict":matchInfoDict}
    CrossRealmMsg.SendMsgToClientServer(ShareDefine.CrossServerMsg_PKMatchReqRet, dataMsg, [serverGroupID])
    return
def __DoPKMatch(zoneID, seasonID, playerID, fightPower):
    ## 执行匹配
    if not IsCrossRealmPKMatchState():
        GameWorld.DebugLog("非匹配阶段,不允许刷新匹配! zoneID=%s,seasonID=%s" % (zoneID, seasonID), playerID)
        return
    matchCount = IpyGameDataPY.GetFuncCfg("CrossRealmPKMatch3", 1)
    rankRange = IpyGameDataPY.GetFuncCfg("CrossRealmPKMatch3", 2) # 名次范围段
    totalRange = rankRange * matchCount # 总匹配名次范围
    matchIDList = [] # 匹配ID结果列表
    billboardType = ShareDefine.Def_CBT_CrossRealmPK
    groupValue1, groupValue2 = zoneID, seasonID
    billboardMgr = PyDataManager.GetCrossBillboardManager()
    billboardObj = billboardMgr.GetCrossBillboard(billboardType, groupValue1, groupValue2)
    billDataCount = billboardObj.GetCount()
    playerBillIndex = billboardObj.IndexOfByID(playerID)
    GameWorld.DebugLog("===执行匹配: zoneID=%s,seasonID=%s,playerID=%s,fightPower=%s,playerBillIndex=%s,matchCount=%s"
                       % (zoneID, seasonID, playerID, fightPower, playerBillIndex, matchCount), playerID)
    if playerBillIndex <= 0:
        playerBillIndex = billDataCount
        GameWorld.DebugLog("玩家未上积分榜,视为排在榜上最后一名之后! playerBillIndex=%s" % playerBillIndex, playerID)
    billStartIndex = max(0, playerBillIndex - totalRange / 2) # 以自己的排名为中心索引
    billLoopIndexList = range(billStartIndex, billStartIndex + totalRange + 1)
    if playerBillIndex in billLoopIndexList:
        billLoopIndexList.remove(playerBillIndex) # 移除自己
    loopBillPlayerIDList = []
    for index in billLoopIndexList:
        if index >= billDataCount:
            break
        billData = billboardObj.At(index)
        loopBillPlayerIDList.append(int(billData.ID))
    GameWorld.DebugLog("积分榜可匹配的排名玩家ID列表: %s,%s" % (len(loopBillPlayerIDList), loopBillPlayerIDList), playerID)
    randPackPlayerIDList = None # 可随机匹配的打包数据战力排名玩家ID列表
    haveRobot = False
    for matchIndex in range(matchCount):
        GameWorld.DebugLog("匹配第%s个: matchIndex=%s,playerBillIndex=%s,billLoopIndexList=%s" % (matchIndex + 1, matchIndex, playerBillIndex, billLoopIndexList), playerID)
        matchID = 0
        randIDList = []
        # 1. 优先赛季积分排行榜
        if billDataCount:
            __addRandMatchID(playerID, matchIndex, rankRange, randIDList, matchIDList, loopBillPlayerIDList, 1)
        # 有需要用到战力匹配的,加载一次
        if not randIDList and randPackPlayerIDList == None:
            zonePackPlayerIDList = __getZonePackPlayerIDList(zoneID, playerID)
            playerPackIndex = -1
            randPackPlayerIDList = []
            if playerID in zonePackPlayerIDList:
                playerPackIndex = zonePackPlayerIDList.index(playerID)
                startIndex = max(0, playerPackIndex - totalRange / 2) # 以自己的排名为中心索引
                randPackPlayerIDList = [int(packID) for packID in zonePackPlayerIDList[startIndex:totalRange + 1]] # 因为包含自己,所以加1
                if playerID in randPackPlayerIDList:
                    randPackPlayerIDList.remove(playerID) # 移除自己
            GameWorld.DebugLog("    赛区战力榜玩家排名: playerPackIndex=%s,%s,%s" % (playerPackIndex, len(zonePackPlayerIDList), zonePackPlayerIDList), playerID)
            GameWorld.DebugLog("    赛区战力榜随机玩家: %s,%s" % (len(randPackPlayerIDList), randPackPlayerIDList), playerID)
        # 2. 该范围段没有的话匹配打包数据战力榜
        if not randIDList and randPackPlayerIDList:
            __addRandMatchID(playerID, matchIndex, rankRange, randIDList, matchIDList, randPackPlayerIDList, 2)
        if randIDList:
            matchID = random.choice(randIDList)
        # 3. 最终还没有,直接机器人
        if not matchID:
            matchID = matchIndex + 1 # 机器人ID固定为 matchIndex + 1
            haveRobot = True
        matchIDList.append(matchID)
        GameWorld.DebugLog("        本段随机匹配结果: matchIndex=%s,matchID=%s,randIDList=%s,matchIDList=%s" % (matchIndex, matchID, randIDList, matchIDList), playerID)
    #最后一个如果不是机器人,则概率直接匹配机器人
    if not haveRobot and GameWorld.CanHappen(IpyGameDataPY.GetFuncCfg("CrossRealmPKMatch3", 3)):
        robotID = matchCount
        matchIDList[-1] = robotID
        GameWorld.DebugLog("没有匹配到机器人,概率直接匹配到机器人: robotID=%s" % robotID, playerID)
    GameWorld.DebugLog("最终匹配结果: matchIDList=%s" % str(matchIDList), playerID)
    return matchIDList
def __addRandMatchID(playerID, matchIndex, rankRange, randIDList, matchIDList, loopPlayerIDList, sign):
    ## 根据所有可循环玩家ID列表,添加对应匹配轮次可随机匹配的玩家
    packDataMgr = PyDataManager.GetDBPlayerPackDataManager()
    indexStart = matchIndex * rankRange
    indexEnd = indexStart + rankRange - 1
    loopIDCount = len(loopPlayerIDList)
    if sign == 1:
        GameWorld.DebugLog("    匹配赛区积分榜: matchIndex=%s,loopIndex=%s~%s,loopIDCount=%s" % (matchIndex, indexStart, indexEnd, loopIDCount), playerID)
    else:
        GameWorld.DebugLog("    匹配赛区战力榜: matchIndex=%s,loopIndex=%s~%s,loopIDCount=%s" % (matchIndex, indexStart, indexEnd, loopIDCount), playerID)
    for index in range(indexStart, indexEnd + 1):
        if index >= loopIDCount:
            break
        dataID = loopPlayerIDList[index]
        if not dataID or dataID == playerID or dataID in randIDList:
            GameWorld.DebugLog("        不可匹配空或自己或已添加: dataID=%s,randIDList=%s" % (dataID, randIDList), playerID)
            continue
        if dataID in matchIDList:
            GameWorld.DebugLog("        不可添加已匹配过玩家: dataID=%s,randIDList=%s,matchIDList=%s" % (dataID, randIDList, matchIDList), playerID)
            continue
        if not packDataMgr.IsPlayerIn(dataID):
            GameWorld.DebugLog("        不匹配无打包数据玩家: dataID=%s,randIDList=%s" % (dataID, randIDList), playerID)
            continue
        randIDList.append(dataID)
        GameWorld.DebugLog("        添加可以随机匹配玩家: dataID=%s,randIDList=%s" % (dataID, randIDList), playerID)
    return
def __getZonePackPlayerIDList(zoneID, playerID):
    ## 获取分区打包数据玩家ID列表
    crossZoneName = GameWorld.GetCrossZoneName()
    zoneIpyData = IpyGameDataPY.GetIpyGameData("CrossZonePK", crossZoneName, zoneID)
    if zoneIpyData:
        packDataMgr = PyDataManager.GetDBPlayerPackDataManager()
        packDataMgr.Sort()
        serverIDList = zoneIpyData.GetServerGroupIDList()
        zonePackPlayerIDList = packDataMgr.GetPlayerIDListByServerIDInfo(zoneIpyData.GetServerGroupIDList())
        GameWorld.DebugLog("    获得赛区活跃打包数据玩家: zoneID=%s,serverIDList=%s,%s,%s"
                           % (zoneID, serverIDList, len(zonePackPlayerIDList), zonePackPlayerIDList), playerID)
        return zonePackPlayerIDList
    return []
def ClientServerMsg_PKBillboard(serverGroupID, msgData):
    ## 收到子服GM同步的设置跨服PK数据
    zoneID = msgData["ZoneID"]
    seasonID = msgData["SeasonID"]
    playerInfoDict = msgData["PlayerInfo"]
    accID = playerInfoDict["accID"]
    playerID = playerInfoDict["playerID"]
    playerName = playerInfoDict["playerName"]
    playerJob = playerInfoDict["playerJob"]
    face = playerInfoDict.get("face", 0)
    facePic = playerInfoDict.get("facePic", 0)
    realmLV = playerInfoDict["realmLV"]
    pkScore = playerInfoDict["pkScore"]
    danLV = playerInfoDict["danLV"]
    cWinCount = playerInfoDict["cWinCount"]
    # 更新排行榜
    groupValue1, groupValue2 = zoneID, seasonID
    name2, type2 = "", playerJob
    value1, value2 = realmLV, danLV
    cmpValue = pkScore
    isOK = CrossBillboard.UpdCrossBillboard(ShareDefine.Def_CBT_CrossRealmPK, groupValue1, playerID, playerName, name2, type2,
                                            value1, value2, cmpValue, groupValue2=groupValue2, value3=face, value4=facePic)
    GameWorld.Log("GM设置跨服PK榜单玩家数据: isOK=%s,zoneID=%s,seasonID=%s,pkScore=%s,danLV=%s,cWinCount=%s,accID=%s"
                  % (isOK, zoneID, seasonID, pkScore, danLV, cWinCount, accID), playerID)
    return
##================================== 以下是子服逻辑 ==========================================
def OnGameServerInitOK():
    if GameWorld.IsCrossServer():
        return
    dbZoneID = PlayerDBGSEvent.GetDBGSTrig_ByKey(ShareDefine.Def_Notify_WorldKey_CrossPKZoneID)
    dbSeasonID = PlayerDBGSEvent.GetDBGSTrig_ByKey(ShareDefine.Def_Notify_WorldKey_CrossPKSeasonID)
    dbSeasonState = PlayerDBGSEvent.GetDBGSTrig_ByKey(ShareDefine.Def_Notify_WorldKey_CrossPKSeasonState)
    GameWorld.Log("OnGameServerInitOK dbZoneID=%s,dbSeasonID=%s,dbSeasonState=%s" % (dbZoneID, dbSeasonID, dbSeasonState))
    gameWorld = GameWorld.GetGameWorld()
    zoneID = gameWorld.GetDictByKey(ShareDefine.Def_Notify_WorldKey_CrossPKZoneID)
    if not zoneID:
        gameWorld.SetDict(ShareDefine.Def_Notify_WorldKey_CrossPKZoneID, dbZoneID)
        gameWorld.SetDict(ShareDefine.Def_Notify_WorldKey_CrossPKSeasonID, dbSeasonID)
        gameWorld.SetDict(ShareDefine.Def_Notify_WorldKey_CrossPKSeasonState, dbSeasonState)
        GameWorld.Log("    服务器启动取DB值设置本服赛区赛季信息: ")
    return
def OnMapServerInitOK():
    # 通知地图服务器状态
    if GameWorld.IsCrossServer():
        return
    gameWorld = GameWorld.GetGameWorld()
    zoneID = gameWorld.GetDictByKey(ShareDefine.Def_Notify_WorldKey_CrossPKZoneID)
    seasonID = gameWorld.GetDictByKey(ShareDefine.Def_Notify_WorldKey_CrossPKSeasonID)
    seasonState = gameWorld.GetDictByKey(ShareDefine.Def_Notify_WorldKey_CrossPKSeasonState)
    matchState = GameWorld.GetGameWorld().GetDictByKey(ShareDefine.Def_Notify_WorldKey_CrossDailyActionState % ShareDefine.DailyActionID_CrossReamPK)
    GameWorld.SendMapServerMsgEx(ShareDefine.Def_Notify_WorldKey_CrossPKZoneID, zoneID)
    GameWorld.SendMapServerMsgEx(ShareDefine.Def_Notify_WorldKey_CrossPKSeasonID, seasonID)
    GameWorld.SendMapServerMsgEx(ShareDefine.Def_Notify_WorldKey_CrossPKSeasonState, seasonState)
    GameWorld.SendMapServerMsgEx(ShareDefine.Def_Notify_WorldKey_CrossDailyActionState % ShareDefine.DailyActionID_CrossReamPK, matchState)
    return
def CrossServerMsg_PKSeasonInfo(seasonInfo):
    ## 收到跨服服务器同步的赛季信息
    #seasonInfo = {"ZoneID":zoneID, "SeasonID":seasonID, "SeasonState":seasonState, "MatchState":matchState}
    GameWorld.Log("收到跨服服务器同步的赛季信息...")
    if not seasonInfo:
        return
    zoneID = seasonInfo.get("ZoneID", 0)
    seasonID = seasonInfo.get("SeasonID", 0)
    seasonState = seasonInfo.get("SeasonState", 0)
    matchState = seasonInfo.get("MatchState", 0)
    GameWorld.Log("    zoneID=%s,seasonID=%s,seasonState=%s,matchState=%s" % (zoneID, seasonID, seasonState, matchState))
    if not zoneID:
        return
    if not seasonID:
        dbSeasonID = PlayerDBGSEvent.GetDBGSTrig_ByKey(ShareDefine.Def_Notify_WorldKey_CrossPKSeasonID)
        if dbSeasonID:
            GameWorld.ErrLog("    已经有分配赛季ID的暂定不能被置为0! dbSeasonID=%s" % dbSeasonID)
            return
    gameWorld = GameWorld.GetGameWorld()
    curSeasonState = gameWorld.GetDictByKey(ShareDefine.Def_Notify_WorldKey_CrossPKSeasonState)
    gameWorld.SetDict(ShareDefine.Def_Notify_WorldKey_CrossPKZoneID, zoneID)
    gameWorld.SetDict(ShareDefine.Def_Notify_WorldKey_CrossPKSeasonID, seasonID)
    gameWorld.SetDict(ShareDefine.Def_Notify_WorldKey_CrossPKSeasonState, seasonState)
    gameWorld.SetDict(ShareDefine.Def_Notify_WorldKey_CrossDailyActionState % ShareDefine.DailyActionID_CrossReamPK, matchState)
    # 强制覆盖存DB
    dbZoneID = PlayerDBGSEvent.GetDBGSTrig_ByKey(ShareDefine.Def_Notify_WorldKey_CrossPKZoneID)
    dbSeasonID = PlayerDBGSEvent.GetDBGSTrig_ByKey(ShareDefine.Def_Notify_WorldKey_CrossPKSeasonID)
    dbSeasonState = PlayerDBGSEvent.GetDBGSTrig_ByKey(ShareDefine.Def_Notify_WorldKey_CrossPKSeasonState)
    GameWorld.Log("    before dbZoneID=%s,dbSeasonID=%s,dbSeasonState=%s" % (dbZoneID, dbSeasonID, dbSeasonState))
    PlayerDBGSEvent.SetDBGSTrig_ByKey(ShareDefine.Def_Notify_WorldKey_CrossPKZoneID, zoneID)
    PlayerDBGSEvent.SetDBGSTrig_ByKey(ShareDefine.Def_Notify_WorldKey_CrossPKSeasonID, seasonID)
    PlayerDBGSEvent.SetDBGSTrig_ByKey(ShareDefine.Def_Notify_WorldKey_CrossPKSeasonState, seasonState)
    dbZoneID = PlayerDBGSEvent.GetDBGSTrig_ByKey(ShareDefine.Def_Notify_WorldKey_CrossPKZoneID)
    dbSeasonID = PlayerDBGSEvent.GetDBGSTrig_ByKey(ShareDefine.Def_Notify_WorldKey_CrossPKSeasonID)
    dbSeasonState = PlayerDBGSEvent.GetDBGSTrig_ByKey(ShareDefine.Def_Notify_WorldKey_CrossPKSeasonState)
    GameWorld.Log("    update dbZoneID=%s,dbSeasonID=%s,dbSeasonState=%s" % (dbZoneID, dbSeasonID, dbSeasonState))
    if curSeasonState == 1 and seasonState == 2:
        pass
        #PlayerControl.WorldNotify(0, "NotifySeasonOver")
    # 通知地图服务器状态
    GameWorld.SendMapServerMsgEx(ShareDefine.Def_Notify_WorldKey_CrossPKZoneID, zoneID)
    GameWorld.SendMapServerMsgEx(ShareDefine.Def_Notify_WorldKey_CrossPKSeasonID, seasonID)
    GameWorld.SendMapServerMsgEx(ShareDefine.Def_Notify_WorldKey_CrossPKSeasonState, seasonState)
    GameWorld.SendMapServerMsgEx(ShareDefine.Def_Notify_WorldKey_CrossDailyActionState % ShareDefine.DailyActionID_CrossReamPK, matchState)
    # 广播玩家赛季相关状态变更
    seasonStatePack = ChPyNetSendPack.tagGCCrossRealmPKSeasonState()
    seasonStatePack.ZoneID = zoneID
    seasonStatePack.SeasonID = seasonID
    seasonStatePack.SeasonState = seasonState
    seasonStatePack.MatchState = matchState
    seasonStatePack.CrossZoneName = GameWorld.GetCrossZoneName()
    seasonStatePack.CrossZoneNameLen = len(seasonStatePack.CrossZoneName)
    playerManager = GameWorld.GetPlayerManager()
    for i in xrange(playerManager.GetPlayerCount()):
        curPlayer = playerManager.GetPlayerByIndex(i)
        if curPlayer == None or not curPlayer.GetInitOK():
            continue
        NetPackCommon.SendFakePack(curPlayer, seasonStatePack)
    return
def CrossServerMsg_PKMatchReqRet(retInfo):
    ## 跨服PK匹配请求结果
    playerID, result = retInfo
    playerID = retInfo["playerID"]
    matchIDList = retInfo["matchIDList"]
    matchInfoDict = retInfo["matchInfoDict"]
    curPlayer = GameWorld.GetPlayerManager().FindPlayerByID(playerID)
    if not curPlayer:
        return
    
    if result == -2:
        PlayerControl.NotifyCode(curPlayer, "InCrossPKing")
        return
    if result == 1:
        NetPackCommon.SendFakePack(curPlayer, ChPyNetSendPack.tagGCCrossRealmPKStartMatch())
    matchOKPack = ChPyNetSendPack.tagGCCrossRealmPKMatchOK()
    matchOKPack.MatchPlayer = []
    for matchID in matchIDList:
        matchPlayer = ChPyNetSendPack.tagGCCrossRealmPKMatchPlayer()
        if matchID not in matchInfoDict:
            matchPlayer.PlayerID = matchID
            matchOKPack.MatchPlayer.append(matchPlayer)
            continue
        matchInfo = matchInfoDict[matchID]
        matchPlayer.PlayerID = matchInfo["playerID"]
        matchPlayer.PlayerName = matchInfo["playerName"]
        matchPlayer.NameLen = len(matchPlayer.PlayerName)
        matchPlayer.Job = matchInfo["job"]
        matchPlayer.LV = matchInfo["lv"]
        matchPlayer.RealmLV = matchInfo["realmLV"]
        matchPlayer.Face = matchInfo["face"]
        matchPlayer.FacePic = matchInfo["facePic"]
        matchPlayer.FightPower = matchInfo["fightPower"] % ChConfig.Def_PerPointValue
        matchPlayer.FightPowerEx = matchInfo["fightPower"] / ChConfig.Def_PerPointValue
        matchOKPack.MatchPlayer.append(matchPlayer)
    matchOKPack.MatchPlayerCount = len(matchOKPack.MatchPlayer)
    NetPackCommon.SendFakePack(curPlayer, matchOKPack)
    return
def CrossServerMsg_PKMatchResult(vsRoomDict):
    ## 跨服PK匹配结果
    curServerGroupID = GameWorld.GetServerGroupID()
    actionType = ShareDefine.Def_MergeAction_MergePK
    mapPosList = IpyGameDataPY.GetFuncEvalCfg("CrossRealmPKMatch", 5)
    GameWorld.Log("=== 收到PK匹配结果处理  === curServerGroupID=%s" % curServerGroupID)
    if not mapPosList:
        GameWorld.ErrLog("没有配置对战地图进入坐标!")
#跨服竞技场未通知玩家的比赛结果,注意该类只处理数据逻辑,功能相关逻辑不要写在该类,不然重读脚本不会生效
class CrossPKUnNotifyOverInfoManager(object):
    def __init__(self):
        self.__unNotifyOverInfoDict = {} # {playerID:tagDBCrossPKUnNotifyOverInfo, ...}
        return
    
    for roomID, roomInfo in vsRoomDict.items():
        mapID, playerList = roomInfo
        GameWorld.Log("    roomID=%s,playerList=%s" % (roomID, playerList))
        for i, playerInfo in enumerate(playerList):
            serverGroupID, playerID = playerInfo
            if serverGroupID != curServerGroupID:
                GameWorld.DebugLog("        不是本服玩家,不处理!playerID=%s,serverGroupID=%s" % (playerID, serverGroupID))
                continue
            player = GameWorld.GetPlayerManager().FindPlayerByID(playerID)
            if not player:
                GameWorld.DebugLog("        玩家不在线, playerID=%s" % (playerID))
                continue
            if PlayerControl.GetIsTJG(player):
                GameWorld.DebugLog("        玩家脱机中, playerID=%s" % (playerID))
                continue
            PlayerControl.SetVsRoomId(player, roomID, True)
            # 通知地图玩家匹配成功, 上传数据, 准备进入跨服服务器
            posX, posY = mapPosList[i] if len(mapPosList) > i else mapPosList[0]
            CrossRealmPlayer.SendCrossRealmReg(player, actionType, mapID, mapID, 0, posX, posY)
    return
def CrossServerMsg_PKReadyOKRoomList(readyOKRoomList):
    ## 子服接收玩家已准备好的PK房间信息, 此房间里的玩家可传送进入跨服
    def AddUnNotifyOverInfo(self, playerID, overInfoData):
        self.__unNotifyOverInfoDict[playerID] = overInfoData
        return
    
    curServerGroupID = GameWorld.GetServerGroupID()
    GameWorld.Log("===收到跨服服务器通知已准备好的对战PK房间信息处理=== curServerGroupID=%s" % curServerGroupID)
    # serverGroupID, playerName, playerJob
    def GetPlayerUnNotifyOverInfo(self, playerID): return self.__unNotifyOverInfoDict.pop(playerID, None)
    
    for roomID, readyMemberDict in readyOKRoomList:
        for playerID, playerInfo in readyMemberDict.items():
            serverGroupID = playerInfo["ServerGroupID"]
            playerName = playerInfo["Name"]
            if serverGroupID != curServerGroupID:
                GameWorld.DebugLog("    不是本服玩家,不处理!playerID=%s,serverGroupID=%s" % (playerID, serverGroupID))
                continue
            player = GameWorld.GetPlayerManager().FindPlayerByID(playerID)
            if not player:
                GameWorld.DebugLog("    玩家不在线 , playerID=%s" % (playerID))
                continue
            if PlayerControl.GetIsTJG(player):
                GameWorld.DebugLog("    玩家脱机中, playerID=%s" % (playerID))
                continue
            player.SetDict(ChConfig.Def_PlayerKey_IsLoginToMergeServer, 1)
            matchPlayer = ChPyNetSendPack.tagGCCrossRealmPKMatchPlayer()
            for readyPlayerID, readyPlayerInfo in readyMemberDict.items():
                if readyPlayerID != playerID:
                    matchPlayer.PlayerID = readyPlayerID
                    matchPlayer.PlayerName = readyPlayerInfo["Name"]
                    matchPlayer.NameLen = len(matchPlayer.PlayerName)
                    matchPlayer.Job = readyPlayerInfo["Job"]
                    matchPlayer.LV = readyPlayerInfo["LV"]
                    matchPlayer.MaxHP = readyPlayerInfo["MaxHP"]
                    break
    # 保存数据 存数据库和realtimebackup
    def GetSaveData(self):
        savaData = ""
        cntData = ""
        cnt = 0
        for overInfoData in self.__unNotifyOverInfoDict.values():
            cnt += 1
            savaData += overInfoData.getBuffer()
                
            PlayerControl.SetCrossRealmState(player, 1)
            # 通知匹配成功,可进入跨服
            matchOKPack = ChPyNetSendPack.tagGCCrossRealmPKMatchOK()
            matchOKPack.RoomID = roomID
            matchOKPack.PlayerName = playerName
            matchOKPack.NameLen = len(matchOKPack.PlayerName)
            matchOKPack.MatchPlayer = [matchPlayer]
            matchOKPack.MatchPlayerCount = len(matchOKPack.MatchPlayer)
            NetPackCommon.SendFakePack(player, matchOKPack)
            GameWorld.Log("    通知玩家进入跨服PK对战房间! roomID=%s,playerID=%s,matchPlayerID=%s" % (roomID, playerID, matchPlayer.PlayerID))
            # 到这里默认认为一定会有结果的,所以本服直接增加次数
            #player.MapServer_QueryPlayerResult(0, 0, 'MergePKAddCnt', "", 0)
    return
def CrossServerMsg_PKTimeoutRoomList(timeoutRoomDict):
    ## 子服接收已超时的PK房间信息, 此房间里的玩家重置跨服状态
        GameWorld.Log("SaveDBCrossPKUnNotifyOverInfo cnt :%s len=%s" % (cnt, len(savaData)))
        return CommFunc.WriteDWORD(cntData, cnt) + savaData
    
    curServerGroupID = GameWorld.GetServerGroupID()
    GameWorld.Log("===收到跨服服务器通知已超时的对战PK房间信息处理=== curServerGroupID=%s" % curServerGroupID)
    # 从数据库载入数据
    def LoadPyGameData(self, datas, pos, dataslen):
        cnt, pos = CommFunc.ReadDWORD(datas, pos)
        GameWorld.Log("LoadDBCrossPKUnNotifyOverInfo cnt :%s" % cnt)
        for _ in xrange(cnt):
            overInfoData = PyGameDataStruct.tagDBCrossPKUnNotifyOverInfo()
            overInfoData.clear()
            pos += overInfoData.readData(datas, pos, dataslen)
            self.__unNotifyOverInfoDict[overInfoData.PlayerID] = overInfoData
        return pos
    
    for roomID, roomPlayerInfo in timeoutRoomDict.items():
        if not roomPlayerInfo:
            continue
        serverGroupID, playerID = roomPlayerInfo
        if serverGroupID != curServerGroupID:
            GameWorld.DebugLog("    不是本服玩家,不处理!playerID=%s,serverGroupID=%s" % (playerID, serverGroupID))
            continue
        player = GameWorld.GetPlayerManager().FindPlayerByID(playerID)
        if not player:
            GameWorld.DebugLog("    玩家不在线 , playerID=%s" % (playerID))
            continue
        if PlayerControl.GetIsTJG(player):
            GameWorld.DebugLog("    玩家脱机中, playerID=%s" % (playerID))
            continue
        playerVSRoomID = player.GetVsRoomId()
        if playerVSRoomID and playerVSRoomID != roomID:
            GameWorld.DebugLog("    房间ID不同, playerID=%s" % (playerID))
            continue
        player.SetDict(ChConfig.Def_PlayerKey_IsLoginToMergeServer, 0)
        PlayerControl.SetCrossRealmState(player, 0)
    return
def CrossServerMsg_PKOverInfo(playerOverDict):
    ## 子服接收跨服PK结果信息
    
    curServerGroupID = GameWorld.GetServerGroupID()
    GameWorld.Log("===收到跨服服务器同步的跨服PK结果=== curServerGroupID=%s" % curServerGroupID)
    GameWorld.DebugLog("===收到跨服服务器同步的跨服PK结果=== curServerGroupID=%s" % curServerGroupID)
    
    for playerID, overInfo in playerOverDict.items():
        roomID, seasonID, timeStr, overType, winnerID, roundWinnerIDList, \
            serverGroupID, pkScore, danLV, cWinCount, addScore, tagPlayerID, tagPlayerName, notifyState = overInfo
        if serverGroupID != curServerGroupID:
            GameWorld.DebugLog("    不是本服玩家,不处理!playerID=%s,serverGroupID=%s" % (playerID, serverGroupID))
        zoneID, seasonID, timeStr, winnerID, pkScore, danLV, cWinCount, addScore, tagPlayerID, tagPlayerName = overInfo
        if not PlayerControl.GetDBPlayerAccIDByID(playerID):
            GameWorld.DebugLog("    不是本服玩家,不处理! playerID=%s" % (playerID))
            continue
        
        sendMapOverInfo = [roomID, seasonID, timeStr, overType, winnerID, roundWinnerIDList, pkScore, danLV, cWinCount, addScore, tagPlayerID, tagPlayerName, notifyState]
        player = GameWorld.GetPlayerManager().FindPlayerByID(playerID)
        if not player or PlayerControl.GetIsTJG(player):
            GameWorld.Log("    玩家不在线 或脱机中,先缓存,玩家上线后再同步,playerID=%s" % (playerID))
            PyGameData.g_crossPKUnNotifyOverInfo[playerID] = sendMapOverInfo
            GameWorld.DebugLog("    玩家不在线 或脱机中,先缓存,玩家上线后再同步,playerID=%s" % (playerID))
            overInfoData = PyGameDataStruct.tagDBCrossPKUnNotifyOverInfo()
            overInfoData.clear()
            overInfoData.ZoneID = zoneID
            overInfoData.SeasonID = seasonID
            overInfoData.RoomID = 0
            overInfoData.TimeStr = timeStr
            overInfoData.OverType = 0
            overInfoData.PlayerID = playerID
            overInfoData.WinnerID = winnerID
            overInfoData.RoundWinnerInfo = str([])
            overInfoData.RoundWinnerLen = len(overInfoData.RoundWinnerInfo)
            overInfoData.PKScore = pkScore
            overInfoData.DanLV = danLV
            overInfoData.CWinCount = cWinCount
            overInfoData.AddScore = addScore
            overInfoData.TagPlayerID = tagPlayerID
            overInfoData.TagPlayerName = tagPlayerName
            PyDataManager.GetCrossPKUnNotifyOverInfoManager().AddUnNotifyOverInfo(playerID, overInfoData)
            continue
        
        sysMsg = str(sendMapOverInfo)
        sysMsg = str(overInfo)
        player.MapServer_QueryPlayerResult(0, 0, "CrossPKOverInfo", sysMsg, len(sysMsg))
        GameWorld.Log("通知地图跨服PK结算: roomID=%s,seasonID=%s,timeStr=%s,overType=%s,winnerID=%s,roundWinnerIDList=%s, pkScore=%s,danLV=%s,cWinCount=%s,addScore=%s,tagPlayerID=%s,notifyState=%s,mapID=%s"
                      % (roomID, seasonID, timeStr, overType, winnerID, roundWinnerIDList, pkScore, danLV, cWinCount, addScore, tagPlayerID, notifyState, player.GetMapID()), playerID)
        GameWorld.DebugLog("通知地图跨服PK结算: zoneID=%s,seasonID=%s,timeStr=%s,winnerID=%s, pkScore=%s,danLV=%s,cWinCount=%s,addScore=%s,tagPlayerID=%s,mapID=%s"
                           % (zoneID, seasonID, timeStr, winnerID, pkScore, danLV, cWinCount, addScore, tagPlayerID, player.GetMapID()), playerID)
    return
def __OnLoginNotifyPKOverInfo(curPlayer):
    playerID = curPlayer.GetPlayerID()
    if playerID not in PyGameData.g_crossPKUnNotifyOverInfo:
    overInfoData = PyDataManager.GetCrossPKUnNotifyOverInfoManager().GetPlayerUnNotifyOverInfo(playerID)
    if not overInfoData:
        return
    overInfo = PyGameData.g_crossPKUnNotifyOverInfo.pop(playerID)
    PlayerControl.SetCrossRealmState(curPlayer, 0)
    sysMsg = str(overInfo)
    zoneID = overInfoData.ZoneID
    seasonID = overInfoData.SeasonID
    timeStr = overInfoData.TimeStr
    winnerID = overInfoData.WinnerID
    pkScore = overInfoData.PKScore
    danLV = overInfoData.DanLV
    cWinCount = overInfoData.CWinCount
    addScore = overInfoData.AddScore
    tagPlayerID = overInfoData.TagPlayerID
    tagPlayerName = overInfoData.TagPlayerName
    sysMsg = str([zoneID, seasonID, timeStr, winnerID, pkScore, danLV, cWinCount, addScore, tagPlayerID, tagPlayerName])
    curPlayer.MapServer_QueryPlayerResult(0, 0, "CrossPKOverInfo", sysMsg, len(sysMsg))
    GameWorld.Log("玩家上线通知地图未结算的跨服PK结算: mapID=%s,overInfo=%s" % (curPlayer.GetMapID(), overInfo), playerID)
    GameWorld.DebugLog("玩家上线通知地图未结算的跨服PK结算: zoneID=%s,seasonID=%s,timeStr=%s,winnerID=%s,pkScore=%s,danLV=%s,cWinCount=%s,addScore=%s,tagPlayerID=%s,mapID=%s"
                       % (zoneID, seasonID, timeStr, winnerID, pkScore, danLV, cWinCount, addScore, tagPlayerID, curPlayer.GetMapID()), playerID)
    return
def DR_CrossReamlPK(eventName, dataDict={}):
    drDataDict = {}
    drDataDict.update(dataDict)
    DataRecordPack.SendEventPack("CrossPK_%s" % eventName, drDataDict)
    return