xdh
2019-05-09 df2af1331d39ad7da3f833931c01e2391f575988
ServerPython/CoreServerGroup/GameServer/Script/Player/PlayerFB.py
@@ -30,14 +30,60 @@
import PlayerTeam
import GameWorld
import ChConfig
import IPY_PlayerDefine
import CrossRealmPlayer
import CrossRealmMsg
import ShareDefine
import CrossBoss
import random
#---------------------------------------------------------------------
## 跨服地图动态分配的功能线路,如果有人数上限的,则同分区同地图玩法的可能同时存在多个相同功能线路的数据
class CrossFuncLineInfo():
    def __init__(self):
        self.realMapID = 0
        self.copyMapID = 0
        self.funcLineDataCache = None # 功能线路自定义缓存数据
        return
    def OnCopyMapClose(self):
        self.realMapID = 0
        self.copyMapID = 0
        return
## 跨服地图动态分配的虚拟线路信息
class CrossCopyMapInfo():
    def __init__(self, zoneID, funcLineID):
        self.zoneID = zoneID
        self.funcLineID = funcLineID
        self.openState = IPY_PlayerDefine.fbosClosed
        self.fbPlayerDict = {} # 副本中的玩家信息 {playerID:serverGroupID, ...}
        self.waitPlayerDict = {} # 等待进入的玩家信息 {playerID:[serverGroupID, tick], ...}
        self.offlinePlayerDict = {} # 掉线的玩家信息,非主动退出的 {playerID:serverGroupID, ...}
        return
    def OnRequestEnterCrossCopyMap(self, playerID, serverGroupID, tick, copyMapPlayerMax):
        # 已经在请求队列里,可进入
        if playerID in self.waitPlayerDict or not copyMapPlayerMax:
            self.waitPlayerDict[playerID] = [serverGroupID, tick]
            return True
        # 移除请求进入超时的玩家
        for waitPlayerID, playerInfo in self.waitPlayerDict.items():
            serverGroupID, requestTick = playerInfo
            if tick - requestTick > 60000: # 请求进入时间保留1分钟
                self.waitPlayerDict.pop(waitPlayerID)
        # 判断是否超过人数上限
        fbPlayerCount, waitPlayerCount = len(self.fbPlayerDict), len(self.waitPlayerDict)
        if fbPlayerCount + waitPlayerCount >= copyMapPlayerMax:
            return False
        self.waitPlayerDict[playerID] = [serverGroupID, tick]
        return True
#---------------------------------------------------------------------
def GetFBLineIpyData(mapID, lineID, isDefaultLine=True):
    mapID = GetRecordMapID(mapID)
@@ -76,187 +122,350 @@
            return dataMapID
    return mapID
def ClientServerMsg_EnterFB(serverGroupID, msgData):
def ClientServerMsg_EnterFB(serverGroupID, msgData, tick):
    ## 收到子服请求进入动态分配的跨服副本
    playerID = msgData["PlayerID"]
    dataMapID = msgData["DataMapID"]
    mapID = msgData["MapID"]
    funcLineID = msgData["FuncLineID"]
    
    if dataMapID == ChConfig.Def_FBMapID_CrossDemonKing:
        zoneIpyData = CrossRealmPlayer.GetCrossZoneIpyDataByServerGroupID(dataMapID, serverGroupID)
        if not zoneIpyData:
            return
        zoneID = zoneIpyData.GetZoneID()
    zoneIpyData = CrossRealmPlayer.GetCrossZoneIpyDataByServerGroupID(mapID, serverGroupID)
    if not zoneIpyData:
        return
    zoneID = zoneIpyData.GetZoneID()
    dynamicLineMaxPlayerCountDict = IpyGameDataPY.GetFuncEvalCfg("CrossDynamicLineMap", 2)
    copyMapPlayerMax = dynamicLineMaxPlayerCountDict.get(mapID, 0) # 0为不限制人数,默认不限制
    if mapID == ChConfig.Def_FBMapID_CrossDemonKing:
        bossID = msgData["BossID"]
        if not CrossBoss.GetCrossBossIsAliveOrCanReborn(zoneID, bossID):
            GameWorld.DebugLog("当前跨服妖王死亡状态,不可进入! serverGroupID=%s,funcLineID=%s,zoneID=%s,bossID=%s" % (serverGroupID, funcLineID, zoneID, bossID))
            return
        
        mapCopyLineInfo = __GetCrossDynamicLineInfo(dataMapID, funcLineID, zoneID)
        if not mapCopyLineInfo:
            return
        mapID, copyMapID, isOpenNew = mapCopyLineInfo
    elif mapID in [ChConfig.Def_FBMapID_CrossGrasslandLing, ChConfig.Def_FBMapID_CrossGrasslandXian]:
        pass
        
        # 如果是等待线路启动中的直接返回,等启动好后再通知可进入
        if __AddWaitCrossFBOpenPlayer(mapID, copyMapID, isOpenNew, playerID, serverGroupID):
            return
    else:
        return
    
    mapCopyLineInfo = __GetCrossDynamicLineInfo(playerID, serverGroupID, mapID, funcLineID, zoneID, copyMapPlayerMax, tick)
    if not mapCopyLineInfo:
        return
    realMapID, copyMapID, openState = mapCopyLineInfo
    if openState != IPY_PlayerDefine.fbosOpen:
        return
    playerIDList = [playerID]
    retInfo = [playerIDList, dataMapID, mapID, copyMapID]
    retInfo = [playerIDList, mapID, realMapID, copyMapID, funcLineID]
    CrossRealmMsg.SendMsgToClientServer(ShareDefine.CrossServerMsg_EnterFBRet, retInfo, [serverGroupID])
    return
def CrossServerMsg_EnterFBRet(msgData, tick):
    ## 收到跨服服务器动态分配的跨服副本进入信息
    
    playerIDList, dataMapID, mapID, copyMapID = msgData
    playerIDList, dataMapID, mapID, copyMapID, funcLineID = msgData
    
    if dataMapID == ChConfig.Def_FBMapID_CrossDemonKing:
        mapPosInfo = IpyGameDataPY.GetFuncEvalCfg("CrossDemonKingMap", 2)
    else:
        return
    posX, posY = mapPosInfo[:2]
    dist = mapPosInfo[2] if len(mapPosInfo) > 2 else 0
    if dist > 0:
        posX, posY = random.randint(posX - dist, posX + dist), random.randint(posY - dist, posY + dist)
    for playerID in playerIDList:
        curPlayer = GameWorld.GetPlayerManager().FindPlayerByID(playerID)
        if not curPlayer:
            continue
        CrossRealmPlayer.SendCrossRealmReg(curPlayer, dataMapID, mapID, dataMapID, copyMapID, posX, posY)
        CrossRealmPlayer.SendCrossRealmReg(curPlayer, dataMapID, mapID, dataMapID, copyMapID, lineID=funcLineID)
        
    return
def __GetCrossDynamicLineInfo(dataMapID, funcLineID, zoneID):
    ## 获取跨服分区对应动态分配的副本地图虚拟线路信息
    isOpenNew = False
def __GetCrossDynamicLineInfo(playerID, serverGroupID, mapID, funcLineID, zoneID, copyMapPlayerMax, tick):
    '''获取跨服分区对应动态分配的副本地图虚拟线路信息, 由于需要支持多地图分流,所以直接由GameServer管理分配
            每个功能线路支持按人数分流,超过最大人数后可开启一条相同功能线路的虚拟线路进行分流,所以同一分区同一地图的功能线路ID可能对应多条虚拟线路
    '''
    zoneLineKey = (zoneID, funcLineID)
    if dataMapID not in PyGameData.g_crossDynamicLineInfo:
        PyGameData.g_crossDynamicLineInfo[dataMapID] = {}
    zoneLineDict = PyGameData.g_crossDynamicLineInfo[dataMapID] # 跨服动态线路信息 {dataMapID:{(zoneID, funcLineID):[mapID, copyMapID], ...}, ...}
    if zoneLineKey in zoneLineDict:
        mapID, copyMapID = zoneLineDict[zoneLineKey]
        GameWorld.DebugLog("已存在该分区功能线路ID,不需要重新分配: zoneID=%s,funcLineID=%s,mapID=%s,copyMapID=%s" % (zoneID, funcLineID, mapID, copyMapID))
        return mapID, copyMapID, isOpenNew
    if mapID not in PyGameData.g_crossDynamicLineInfo:
        PyGameData.g_crossDynamicLineInfo[mapID] = {}
    zoneLineDict = PyGameData.g_crossDynamicLineInfo[mapID] # 跨服动态线路信息 {dataMapID:{(zoneID, funcLineID):[CrossFuncLineInfo, CrossFuncLineInfo, ...], ...}, ...}
    if zoneLineKey not in zoneLineDict:
        zoneLineDict[zoneLineKey] = []
    funcLineObjList = zoneLineDict[zoneLineKey]
    
    if dataMapID == ChConfig.Def_FBMapID_CrossDemonKing:
        mapIDList = IpyGameDataPY.GetFuncEvalCfg("CrossDemonKingMap", 1)
    newFuncLineNum = None
    newFuncLineObj = None
    for index, funcLineObj in enumerate(funcLineObjList, 1):
        realMapID, copyMapID = funcLineObj.realMapID, funcLineObj.copyMapID
        if not realMapID:
            if not newFuncLineObj:
                newFuncLineNum, newFuncLineObj = index, funcLineObj
            continue
        
    # 其他地图待扩展
    else:
        return
        key = (realMapID, copyMapID)
        if key not in PyGameData.g_crossDynamicLineCopyMapInfo:
            GameWorld.ErrLog("已经分配的虚拟线路不存在缓存对应关系里!zoneID=%s,funcLineID=%s,realMapID=%s,copyMapID=%s"
                             % (zoneID, funcLineID, realMapID, copyMapID))
            continue
        copyMapObj = PyGameData.g_crossDynamicLineCopyMapInfo[key]
        openState = copyMapObj.openState
        if openState == IPY_PlayerDefine.fbosWaitForClose:
            if not copyMapPlayerMax:
                PlayerControl.CrossNotifyCode(serverGroupID, playerID, "HazyRegionClose")
                return
            #GameWorld.DebugLog("    虚拟线路等待关闭中! index=%s,realMapID=%s,copyMapID=%s" % (index, realMapID, copyMapID))
            continue
        canEnter = copyMapObj.OnRequestEnterCrossCopyMap(playerID, serverGroupID, tick, copyMapPlayerMax)
        if canEnter:
            #GameWorld.DebugLog("可进入动态分布的虚拟线路! realMapID=%s,copyMapID=%s,openState=%s" % (realMapID, copyMapID, openState))
            #GameWorld.DebugLog("    副本中的玩家ID: %s" % copyMapObj.fbPlayerDict)
            #GameWorld.DebugLog("    等待中的玩家ID: %s" % copyMapObj.waitPlayerDict)
            return realMapID, copyMapID, openState
    dynamicLineMapDict = IpyGameDataPY.GetFuncEvalCfg("CrossDynamicLineMap", 1)
    dynamicMapIDList = dynamicLineMapDict.get(mapID, [mapID])
    
    usedMapCopyList = zoneLineDict.values() # 已经使用中的地图虚拟线路
    openMapID, openCopyMapID = 0, 0
    for mapID in mapIDList:
        maxCopyMapCount = PyGameData.g_crossMapCopyMapCountDict.get(mapID, 0)
    for realMapID in dynamicMapIDList:
        maxCopyMapCount = PyGameData.g_crossMapCopyMapCountDict.get(realMapID, 0)
        for copyMapID in xrange(maxCopyMapCount):
            if [mapID, copyMapID] not in usedMapCopyList:
                openMapID, openCopyMapID = mapID, copyMapID
            if (realMapID, copyMapID) not in PyGameData.g_crossDynamicLineCopyMapInfo:
                openMapID, openCopyMapID = realMapID, copyMapID
                break
        if openMapID:
            break
    if not openMapID:
        GameWorld.ErrLog("没有空余的虚拟线路,无法动态开启跨服副本!dataMapID=%s, funcLineID=%s, zoneID=%s, mapIDList=%s"
                         % (dataMapID, funcLineID, zoneID, mapIDList))
        GameWorld.ErrLog("没有空余的虚拟线路,无法动态开启跨服副本!mapID=%s, funcLineID=%s, zoneID=%s, dynamicMapIDList=%s"
                         % (mapID, funcLineID, zoneID, dynamicMapIDList))
        return
    isOpenNew = True
    mapID, copyMapID = openMapID, openCopyMapID
    
    zoneLineDict[zoneLineKey] = [mapID, copyMapID]
    propertyID = zoneID * 1000 + funcLineID
    GameWorld.DebugLog("不存在该分区功能线路ID,重新分配: zoneID=%s,funcLineID=%s,mapID=%s,copyMapID=%s,propertyID=%s"
                       % (zoneID, funcLineID, mapID, copyMapID, propertyID))
    if newFuncLineObj == None:
        newFuncLineObj = CrossFuncLineInfo()
        funcLineObjList.append(newFuncLineObj)
        newFuncLineNum = len(funcLineObjList)
    realMapID, copyMapID = openMapID, openCopyMapID
    newFuncLineObj.realMapID = realMapID
    newFuncLineObj.copyMapID = copyMapID
    funcLineDataCache = newFuncLineObj.funcLineDataCache
    key = (realMapID, copyMapID)
    copyMapObj = CrossCopyMapInfo(zoneID, funcLineID)
    PyGameData.g_crossDynamicLineCopyMapInfo[key] = copyMapObj
    copyMapObj.waitPlayerDict[playerID] = [serverGroupID, tick]
    openState = copyMapObj.openState
    propertyID = int("%d%03d%d" % (zoneID, funcLineID, newFuncLineNum))
    GameWorld.DebugLog("不存在该分区功能线路ID,重新分配: zoneID=%s,funcLineID=%s,realMapID=%s,copyMapID=%s,propertyID=%s"
                       % (zoneID, funcLineID, realMapID, copyMapID, propertyID))
    
    # 通知地图开启新的地图虚拟分线
    msgInfo = str([copyMapID, propertyID])
    GameWorld.GetPlayerManager().MapServer_QueryPlayer(0, 0, 0, mapID, "OpenFB", msgInfo, len(msgInfo))
    return mapID, copyMapID, isOpenNew
    msgInfo = str([copyMapID, propertyID, funcLineDataCache])
    GameWorld.GetPlayerManager().MapServer_QueryPlayer(0, 0, 0, realMapID, "OpenFB", msgInfo, len(msgInfo))
    return realMapID, copyMapID, openState
def __AddWaitCrossFBOpenPlayer(mapID, copyMapID, isOpenNew, playerID, serverGroupID):
    ## 添加跨服玩家进入等待动态副本虚拟线路开启队列
    if mapID not in PyGameData.g_crossDynamicLineOpeningInfo:
        PyGameData.g_crossDynamicLineOpeningInfo[mapID] = {}
    openingMapCopyIDDict = PyGameData.g_crossDynamicLineOpeningInfo[mapID] # 跨服动态线路正在开启中的线路信息 {mapID:{copyMapID:{playerID:serverGroupID, ...}, ...}, ...}
    if isOpenNew or copyMapID in openingMapCopyIDDict:
        if copyMapID not in openingMapCopyIDDict:
            openingMapCopyIDDict[copyMapID] = {}
        waitingPlayerDict = openingMapCopyIDDict[copyMapID]
        waitingPlayerDict[playerID] = serverGroupID
        GameWorld.Log("添加玩家进入等待跨服动态副本虚拟线路开启队列: mapID=%s,copyMapID=%s,isOpenNew=%s,playerID=%s,serverGroupID=%s"
                      % (mapID, copyMapID, isOpenNew, playerID, serverGroupID))
        GameWorld.Log("    PyGameData.g_crossDynamicLineOpeningInfo=%s" % PyGameData.g_crossDynamicLineOpeningInfo)
        return True
    return False
def GetCrossDynamicLineZoneID(mapID, realMapID, copyMapID):
    ## 获取跨服动态分配的虚拟线路对应分区ID
    zoneLineDict = PyGameData.g_crossDynamicLineInfo.get(mapID, {})
    for key, funcLineObjList in zoneLineDict.items():
        for funcLineObj in funcLineObjList:
            if funcLineObj.realMapID == realMapID and funcLineObj.copyMapID == copyMapID:
                zoneID = key[0]
                return zoneID
    return 0
def OnCrossDynamicLineOpen(mapID, copyMapID):
def OnCrossDynamicLineStateChange(msgList):
    realMapID, copyMapID, state = msgList[:3]
    if state == IPY_PlayerDefine.fbosWaitForClose:
        funcLineDataCache = msgList[3]
        OnCrossDynamicLineWaitForClose(realMapID, copyMapID, funcLineDataCache)
    elif state == IPY_PlayerDefine.fbosClosed:
        OnCrossDynamicLineClose(realMapID, copyMapID)
    elif state == IPY_PlayerDefine.fbosOpen:
        OnCrossDynamicLineOpen(realMapID, copyMapID)
    return
def OnCrossDynamicLineOpen(realMapID, copyMapID):
    ## 动态分配线路的地图虚拟线路启动成功
    
    if mapID not in PyGameData.g_crossDynamicLineOpeningInfo:
    key = (realMapID, copyMapID)
    if key not in PyGameData.g_crossDynamicLineCopyMapInfo:
        return
    openingCopyMapDict = PyGameData.g_crossDynamicLineOpeningInfo[mapID]
    waitingPlayerDict = openingCopyMapDict.pop(copyMapID, {})
    if not waitingPlayerDict:
        return
    copyMapObj = PyGameData.g_crossDynamicLineCopyMapInfo[key]
    copyMapObj.openState = IPY_PlayerDefine.fbosOpen
    funcLineID = copyMapObj.funcLineID
    
    # 通知子服等待中的玩家可以进入副本
    serverPlayerIDListDict = {}
    for playerID, serverGroupID in waitingPlayerDict.items():
    for playerID, playerInfo in copyMapObj.waitPlayerDict.items():
        serverGroupID = playerInfo[0]
        if serverGroupID not in serverPlayerIDListDict:
            serverPlayerIDListDict[serverGroupID] = []
        playerIDList = serverPlayerIDListDict[serverGroupID]
        playerIDList.append(playerID)
        
    dataMapID = GetRecordMapID(mapID)
    GameWorld.Log("动态分配虚拟线路启动成功,通知子服等待玩家可进入: dataMapID=%s,mapID=%s,copyMapID=%s,serverPlayerIDListDict=%s"
                  % (dataMapID, mapID, copyMapID, serverPlayerIDListDict))
    mapID = GetRecordMapID(realMapID)
    GameWorld.Log("动态分配虚拟线路启动成功,通知子服等待玩家可进入: mapID=%s,realMapID=%s,copyMapID=%s,serverPlayerIDListDict=%s"
                  % (mapID, realMapID, copyMapID, serverPlayerIDListDict))
    for serverGroupID, playerIDList in serverPlayerIDListDict.items():
        retInfo = [playerIDList, dataMapID, mapID, copyMapID]
        retInfo = [playerIDList, mapID, realMapID, copyMapID, funcLineID]
        CrossRealmMsg.SendMsgToClientServer(ShareDefine.CrossServerMsg_EnterFBRet, retInfo, [serverGroupID])
        
    GameWorld.DebugLog("    PyGameData.g_crossDynamicLineInfo=%s" % PyGameData.g_crossDynamicLineInfo)
    GameWorld.DebugLog("    PyGameData.g_crossDynamicLineOpeningInfo=%s" % PyGameData.g_crossDynamicLineOpeningInfo)
    #GameWorld.DebugLog("    PyGameData.g_crossDynamicLineInfo=%s" % PyGameData.g_crossDynamicLineInfo)
    #GameWorld.DebugLog("    PyGameData.g_crossDynamicLineCopyMapInfo=%s" % PyGameData.g_crossDynamicLineCopyMapInfo)
    return
def OnCrossDynamicLineWaitForClose(realMapID, copyMapID, funcLineDataCache):
    ## 动态分配线路的地图虚拟线路关闭
    mapID = GetRecordMapID(realMapID)
    GameWorld.Log("动态分配虚拟线路等待关闭 mapID=%s,realMapID=%s,copyMapID=%s,funcLineDataCache=%s" % (mapID, realMapID, copyMapID, funcLineDataCache))
    zoneLineDict = PyGameData.g_crossDynamicLineInfo.get(mapID, {})
    for key, funcLineObjList in zoneLineDict.items():
        for funcLineObj in funcLineObjList:
            if funcLineObj.realMapID == realMapID and funcLineObj.copyMapID == copyMapID:
                funcLineObj.funcLineDataCache = funcLineDataCache
                zoneID, funcLineID = key
                GameWorld.Log("    分区对应功能线路虚拟分线等待关闭: zoneID=%s,mapID=%s,funcLineID=%s" % (zoneID, mapID, funcLineID))
                break
    key = (realMapID, copyMapID)
    if key in PyGameData.g_crossDynamicLineCopyMapInfo:
        copyMapObj = PyGameData.g_crossDynamicLineCopyMapInfo[key]
        copyMapObj.openState = IPY_PlayerDefine.fbosWaitForClose
    #GameWorld.DebugLog("    PyGameData.g_crossDynamicLineInfo=%s" % PyGameData.g_crossDynamicLineInfo)
    #GameWorld.DebugLog("    PyGameData.g_crossDynamicLineCopyMapInfo=%s" % PyGameData.g_crossDynamicLineCopyMapInfo)
    return
def OnCrossDynamicLineClose(mapID, copyMapID):
    ## 动态分配线路的地图虚拟线路关闭
    dataMapID = GetRecordMapID(mapID)
    GameWorld.Log("动态分配虚拟线路关闭 dataMapID=%s,mapID=%s,copyMapID=%s" % (dataMapID, mapID, copyMapID))
    zoneLineDict = PyGameData.g_crossDynamicLineInfo.get(dataMapID, {}) 
    for key, mapCopyInfo in zoneLineDict.items():
        if mapCopyInfo[0] == mapID and mapCopyInfo[1] == copyMapID:
            zoneLineDict.pop(key)
            break
    openingCopyMapDict = PyGameData.g_crossDynamicLineOpeningInfo.get(mapID, {})
    openingCopyMapDict.pop(copyMapID, {})
    for key, funcLineObjList in zoneLineDict.items():
        for funcLineObj in funcLineObjList:
            if funcLineObj.realMapID == mapID and funcLineObj.copyMapID == copyMapID:
                funcLineObj.OnCopyMapClose()
                zoneID, funcLineID = key
                GameWorld.Log("    分区对应功能线路虚拟分线关闭: zoneID=%s,dataMapID%s,funcLineID=%s" % (zoneID, dataMapID, funcLineID))
                break
    
    GameWorld.DebugLog("    PyGameData.g_crossDynamicLineInfo=%s" % PyGameData.g_crossDynamicLineInfo)
    GameWorld.DebugLog("    PyGameData.g_crossDynamicLineOpeningInfo=%s" % PyGameData.g_crossDynamicLineOpeningInfo)
    key = (mapID, copyMapID)
    copyMapObj = PyGameData.g_crossDynamicLineCopyMapInfo.pop(key, None)
    if not copyMapObj:
        return
    #GameWorld.DebugLog("    PyGameData.g_crossDynamicLineInfo=%s" % PyGameData.g_crossDynamicLineInfo)
    #GameWorld.DebugLog("    PyGameData.g_crossDynamicLineCopyMapInfo=%s" % PyGameData.g_crossDynamicLineCopyMapInfo)
    playerCount = 0
    zoneID = copyMapObj.zoneID
    funcLineID = copyMapObj.funcLineID
    playerCountInfo = [playerCount]
    SyncClientServerCrossFBFuncLinePlayerCount(zoneID, mapID, funcLineID, playerCountInfo)
    #如果虚拟分线关闭时,有掉线的玩家,则通知子服重置这些玩家的跨服状态
    for playerID, serverGroupID in copyMapObj.offlinePlayerDict.items():
        CrossRealmMsg.SendMsgToClientServer(ShareDefine.CrossServerMsg_ExitCrossServer, playerID, [serverGroupID])
    return
def OnCrossDynamicMapReset(mapID, copyMapCount):
def OnCrossDynamicMapReset(msgList):
    ## 动态分配线路的地图重置
    realMapID, copyMapCount = msgList
    mapID = GetRecordMapID(realMapID)
    GameWorld.Log("动态分配虚拟线路地图重置 mapID=%s,realMapID=%s,copyMapCount=%s" % (mapID, realMapID, copyMapCount))
    PyGameData.g_crossMapCopyMapCountDict[realMapID] = copyMapCount
    
    dataMapID = GetRecordMapID(mapID)
    GameWorld.Log("动态分配虚拟线路地图重置 dataMapID=%s,mapID=%s,copyMapCount=%s" % (dataMapID, mapID, copyMapCount))
    PyGameData.g_crossMapCopyMapCountDict[mapID] = copyMapCount
    zoneLineDict = PyGameData.g_crossDynamicLineInfo.get(dataMapID, {})
    for key, mapCopyInfo in zoneLineDict.items():
        if mapCopyInfo[0] == mapID:
            zoneLineDict.pop(key)
    zoneLineDict = PyGameData.g_crossDynamicLineInfo.get(mapID, {})
    for key, funcLineObjList in zoneLineDict.items():
        for funcLineObj in funcLineObjList:
            if funcLineObj.realMapID == realMapID:
                funcLineObj.OnCopyMapClose()
            
    PyGameData.g_crossDynamicLineOpeningInfo.pop(mapID, None)
    for key in PyGameData.g_crossDynamicLineCopyMapInfo.keys():
        if key[0] == realMapID:
            PyGameData.g_crossDynamicLineCopyMapInfo.pop(key)
    #GameWorld.DebugLog("    PyGameData.g_crossDynamicLineInfo=%s" % PyGameData.g_crossDynamicLineInfo)
    #GameWorld.DebugLog("    PyGameData.g_crossDynamicLineCopyMapInfo=%s" % PyGameData.g_crossDynamicLineCopyMapInfo)
    return
def PlayerLoginLoadCrossMapOK(curPlayer):
    ## 玩家登录跨服地图
    mapID, copyMapID = curPlayer.GetRealMapID(), curPlayer.GetFBID()
    key = (mapID, copyMapID)
    if key not in PyGameData.g_crossDynamicLineCopyMapInfo:
        return
    copyMapObj = PyGameData.g_crossDynamicLineCopyMapInfo[key]
    playerID = curPlayer.GetPlayerID()
    serverGroupID = PlayerControl.GetPlayerServerGroupID(curPlayer)
    copyMapObj.waitPlayerDict.pop(playerID, None)
    copyMapObj.offlinePlayerDict.pop(playerID, None)
    copyMapObj.fbPlayerDict[playerID] = serverGroupID
    #GameWorld.DebugLog("玩家登录动态分配的跨服地图: GetMapID=%s,GetRealMapID=%s,GetFBID()=%s,serverGroupID=%s"
    #                   % (curPlayer.GetMapID(), mapID, copyMapID, serverGroupID), playerID)
    #GameWorld.DebugLog("    副本中的玩家ID: %s" % copyMapObj.fbPlayerDict)
    #GameWorld.DebugLog("    等待中的玩家ID: %s" % copyMapObj.waitPlayerDict)
    #GameWorld.DebugLog("    离线中的玩家ID: %s" % copyMapObj.offlinePlayerDict)
    playerCount = len(copyMapObj.fbPlayerDict) # 等待进入的暂时不算
    zoneID = copyMapObj.zoneID
    funcLineID = copyMapObj.funcLineID
    playerCountInfo = [playerCount]
    SyncClientServerCrossFBFuncLinePlayerCount(zoneID, mapID, funcLineID, playerCountInfo)
    return
def SyncClientServerCrossFBFuncLinePlayerCount(zoneID, mapID, funcLineID, playerCountInfo):
    ## 同步子服跨服副本功能线路人数
    ## 注意: 此人数不是一个精确人数值,只是一个大概人数值,不用很精确,暂时只玩家进入时同步人数信息,玩家退出暂不处理
    mapID = GetRecordMapID(mapID)
    if mapID not in ChConfig.Def_NeedCountFBFuncLinePlayerCrossMap:
        return
    zoneIpyData = CrossRealmPlayer.GetCrossZoneIpyDataByZoneID(mapID, zoneID)
    if not zoneIpyData:
        return
    serverGroupIDList = zoneIpyData.GetServerGroupIDList()
    playerCountInfo = [mapID, funcLineID, playerCountInfo]
    CrossRealmMsg.SendMsgToClientServer(ShareDefine.CrossServerMsg_FBPlayerCount, playerCountInfo, serverGroupIDList)
    return
def OnPlayerDisconnectCrossServer(curPlayer):
    ## 玩家离开跨服服务器
    mapID, copyMapID = curPlayer.GetRealMapID(), curPlayer.GetFBID()
    key = (mapID, copyMapID)
    if key not in PyGameData.g_crossDynamicLineCopyMapInfo:
        return
    copyMapObj = PyGameData.g_crossDynamicLineCopyMapInfo[key]
    playerID = curPlayer.GetPlayerID()
    copyMapObj.waitPlayerDict.pop(playerID, None)
    copyMapObj.fbPlayerDict.pop(playerID, None)
    crossMapID = PlayerControl.GetCrossMapID(curPlayer)
    # 不是主动退出的
    if crossMapID:
        copyMapObj.offlinePlayerDict[playerID] = PlayerControl.GetPlayerServerGroupID(curPlayer)
    #GameWorld.DebugLog("玩家退出动态分配的跨服地图: GetMapID=%s,GetRealMapID=%s,GetFBID()=%s,crossMapID=%s"
    #                   % (curPlayer.GetMapID(), mapID, copyMapID, crossMapID), playerID)
    #GameWorld.DebugLog("    副本中的玩家ID: %s" % copyMapObj.fbPlayerDict)
    #GameWorld.DebugLog("    等待中的玩家ID: %s" % copyMapObj.waitPlayerDict)
    #GameWorld.DebugLog("    离线中的玩家ID: %s" % copyMapObj.offlinePlayerDict)
    return
def CrossServerMsg_FBPlayerCount(msgData):
    ## 收到跨服服务器同步的副本功能线路人数信息
    mapID, funcLineID, playerCountInfo = msgData
    if mapID not in PyGameData.g_crossFBFuncLinePlayerCountInfo:
        PyGameData.g_crossFBFuncLinePlayerCountInfo[mapID] = {}
    fbLinePlayerInfoDict = PyGameData.g_crossFBFuncLinePlayerCountInfo[mapID]
    fbLinePlayerInfoDict[funcLineID] = playerCountInfo
    return
##--------------------------------------------------------------------------------------------------
## 请求进入副本分线
#  @param curPlayer: 请求玩家
#  @param queryCallName: 请求回调名