ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/DB/StructData/DBFamily.py
@@ -15,20 +15,25 @@
#"""Version = 2025-05-09 12:20"""
#-------------------------------------------------------------------------------
import ChPlayer
import DBStruct
import CommFunc
import GameWorld
import ShareDefine
import PlayerControl
import PlayerViewCache
import ChPyNetSendPack
import NetPackCommon
import IpyGameDataPY
import PyMongoMain
import DBDataMgr
import CrossMgr
import CrossMsg
import ChConfig
import DBComm
import time
FamilyUpperLimitCount = 2000
#FamilyUpperLimitCount = 2000
class FamilyActionData():
    
@@ -192,11 +197,15 @@
    
class FamilyMem():
    
    def __init__(self, dbData=None):
    def __init__(self, dbData=None, family=None):
        self.__dbData = DBStruct.tagDBFamilyMem() if not dbData else dbData
        self.__family = family
        return
    
    def GetFamily(self): return DBDataMgr.GetFamilyMgr().FindFamily(self.__dbData.FamilyID)
    def GetFamily(self):
        if not self.__family:
            self.__family = DBDataMgr.GetFamilyMgr().FindFamily(self.__dbData.FamilyID)
        return self.__family
    def GetPlayerID(self): return self.__dbData.PlayerID
    def GetFamilyID(self): return self.__dbData.FamilyID
    def GetJoinTime(self): return self.__dbData.JoinTime
@@ -238,7 +247,9 @@
    def GetBuffer(self): return self.__dbData.getBuffer()
    
    def RefreshMemberByID(self, playerID):
        ## 根据玩家ID更新成员数据,一般用于离线功能,如添加离线成员,直接使用查看缓存更新
        '''刷新成员信息,仙盟成员属于永久功能数据,除非仙盟解散,所以单独存一份成员基础信息,支持离线玩家,直接使用查看缓存更新
        @return: 战力是否变更
        '''
        if playerID != self.GetPlayerID():
            return
        viewCache = PlayerViewCache.FindViewCache(playerID)
@@ -254,35 +265,12 @@
        self.SetServerID(viewCache.GetServerID())
        fpChange = False
        fightPowerTotal = viewCache.GetFightPowerTotal()
        if self.GetFightPowerTotal() < fightPowerTotal:
        if self.GetFightPowerTotal() != fightPowerTotal:
            self.SetFightPowerTotal(fightPowerTotal)
            fpChange = True
            family = self.GetFamily()
            if family:
                family.SetMemFightPowerChange()
        return fpChange
    def RefreshMember(self, curPlayer):
        '''刷新成员信息,仙盟成员属于永久功能数据,除非仙盟解散,所以单独存一份成员基础信息,防止过度依赖查看缓存数据引发的问题
        @return: 战力是否变更
        '''
        if not curPlayer or curPlayer.GetPlayerID() != self.GetPlayerID():
            return
        self.SetPlayerName(curPlayer.GetPlayerName())
        self.SetLV(curPlayer.GetLV())
        self.SetJob(curPlayer.GetJob())
        self.SetRealmLV(curPlayer.GetOfficialRank())
        self.SetFace(curPlayer.GetFace())
        self.SetFacePic(curPlayer.GetFacePic())
        self.SetTitleID(PlayerControl.GetTitleID(curPlayer))
        self.SetServerID(GameWorld.GetPlayerServerID(curPlayer))
        fightPowerTotal = PlayerControl.GetFightPower(curPlayer)
        fpChange = self.GetFightPowerTotal() != fightPowerTotal
        self.SetFightPowerTotal(fightPowerTotal)
        if fpChange:
            family = self.GetFamily()
            if family:
                family.SetMemFightPowerChange()
                family.RefrshFightPowerTotal()
        return fpChange
    
class Family():
@@ -293,7 +281,6 @@
        self.__memberDict = {} # 成员字典 {playerID:FamilyMem, ...}
        self.__familyMgr = DBDataMgr.GetFamilyMgr()
        self.__actionMgr = DBDataMgr.GetFamilyActionMgr()
        self.__memFightPowerChange = None # 成员战力是否有变化,默认None,代表未处理过
        return
    
    def GetID(self): return self.__dbData.ID
@@ -318,24 +305,39 @@
        return
    def GetFightPower(self): return self.__dbData.FightPower
    def GetFightPowerEx(self): return self.__dbData.FightPowerEx
    def GetFightPowerTotal(self): return self.__dbData.FightPowerEx * ChConfig.Def_PerPointValue + self.__dbData.FightPower
    def GetFightPowerTotal(self):
        return self.__dbData.FightPowerEx * ChConfig.Def_PerPointValue + self.__dbData.FightPower
    def SetFightPowerTotal(self, fightPowerTotal):
        fpBef = self.GetFightPowerTotal()
        self.__dbData.FightPower = fightPowerTotal % ChConfig.Def_PerPointValue
        self.__dbData.FightPowerEx = fightPowerTotal / ChConfig.Def_PerPointValue
        fpAft = self.GetFightPowerTotal()
        if fpBef != fpAft:
            self.__familyMgr.OnFightPowerChange(self)
        return
    def GetEmblemID(self): return self.__dbData.EmblemID
    def SetEmblemID(self, emblemID): self.__dbData.EmblemID = emblemID
    def GetEmblemWord(self): return self.__dbData.EmblemWord
    def SetEmblemWord(self, emblemWord): self.__dbData.EmblemWord = emblemWord
    def GetExtra1(self): return self.__dbData.Extra1
    def SetExtra1(self, extra1): self.__dbData.Extra1 = extra1
    def GetExtra2(self): return self.__dbData.Extra2
    def SetExtra2(self, extra2): self.__dbData.Extra2 = extra2
    def GetExtra3(self): return self.__dbData.Extra3
    def SetExtra3(self, extra3): self.__dbData.Extra3 = extra3
    def GetExtra4(self): return self.__dbData.Extra4
    def SetExtra4(self, extra4): self.__dbData.Extra4 = extra4
    def GetExtra5(self): return self.__dbData.Extra5
    def SetExtra5(self, extra5): self.__dbData.Extra5 = extra5
    def GetBuffer(self): return self.__dbData.getBuffer()
    ## ------------------------------------------------
    def InitMemberInstance(self, dbData):
        '''初始化功能数据实例,创建或加载数据时通用,功能一般不调用
        @param dbData: 实例对应绑定的dbData
        @return: 成功返回实例对象,失败返回None
        '''
        member = FamilyMem(dbData)
        member = FamilyMem(dbData, self)
        playerID = member.GetPlayerID()
        if playerID in self.__memberDict:
            return
@@ -357,14 +359,16 @@
            member = self.InitMemberInstance(dbData)
            if not member and False:
                member = FamilyMem()
        self.__memFightPowerChange = True
        self.RefrshFightPowerTotal()
        self.__familyMgr.OnAddMember(self, playerID)
        return member
    
    def DeleteMember(self, playerID):
        delMem = self.__memberDict.pop(playerID, None)
        if delMem in self.__memberList:
            self.__memberList.remove(delMem)
        self.__memFightPowerChange = True
        self.RefrshFightPowerTotal()
        self.__familyMgr.OnDelMember(self, playerID)
        return delMem
    
    def FindMember(self, playerID):
@@ -384,12 +388,8 @@
            mem = FamilyMem()
        return mem
    
    def SetMemFightPowerChange(self): self.__memFightPowerChange = True
    def RefrshFightPowerTotal(self, checkChange=False):
    def RefrshFightPowerTotal(self):
        ## 刷新总战力
        if checkChange and self.__memFightPowerChange == False: # 默认None,首次必刷新
            #GameWorld.DebugLog("没有成员战力变化可不刷新仙盟总战力! familyID=%s" % self.GetID())
            return
        familyFightPowerTotal = 0
        for index in range(self.GetCount()):
            member = self.GetAt(index)
@@ -397,8 +397,6 @@
                continue
            familyFightPowerTotal += member.GetFightPowerTotal()
        self.SetFightPowerTotal(familyFightPowerTotal)
        #GameWorld.DebugLog("刷新仙盟总战力! familyID=%s" % self.GetID())
        self.__memFightPowerChange = False
        return familyFightPowerTotal
    
    def GetReqJoinPlayerInfo(self):
@@ -432,68 +430,121 @@
        return
    
    def OnDelete(self):
        for memID in self.__memberDict.keys():
            self.DeleteMember(memID)
        self.DelReqJoinPlayerAll()
        self.__actionMgr.ClearFamilyAction(self.GetID())
        return
    
class FamilyMgr():
#class FamilyViewBase():
#    ## 公会基础信息,本服跨服通用,一般用于本服公会需要用的基本信息,如查看,方便本服可直接取,由跨服同步
#
#    def __init__(self, familyID):
#        self._familyID = familyID
#        self._family = None
#        self._familyName = ""
#        self._serverID = 0
#        self._emblemID = 0
#        self._emblemWord = ""
#        return
#
#    def SetFamily(self, family): self._family = family
#
#    def GetID(self): return self._familyID
#
#    # 有值以设置的值为准-跨服同步过来的,否则以本服已存在的公会数据为准-即未互通时兼容取本服公会数据
#    def GetName(self): return self._familyName if self._familyName else (self._family.GetName() if self._family else "")
#    def GetServerID(self): return self._serverID if self._serverID else (self._family.GetServerID() if self._family else 0)
#    def GetEmblemID(self): return self._emblemID if self._emblemID else (self._family.GetEmblemID() if self._family else 0)
#    def GetEmblemWord(self): return self._emblemWord if self._emblemWord else (self._family.GetEmblemWord() if self._family else "")
#
#    def GetSyncData(self):
#        return [self.GetName(), self.GetServerID(), self.GetEmblemID(), self.GetEmblemWord()]
#
#    def UpdSyncData(self, syncData):
#        ## 根据跨服同步过来的更新
#        self._familyName = syncData[0] if len(syncData) > 0 else self._familyName
#        self._serverID = syncData[1] if len(syncData) > 1 else self._serverID
#        self._emblemID = syncData[2] if len(syncData) > 2 else self._emblemID
#        self._emblemWord = syncData[3] if len(syncData) > 3 else self._emblemWord
#        return
    
    def __init__(self):
        self.__familyList = [] # 仙盟对象列表,可进行排序 [Family, ...]
        self.__familyIDDict = {} # 仙盟ID对象字典,  {familyID:Family, ...}
        self.__familyNameDict = {} # 仙盟名称对象字典,  {familyName:Family, ...}
        self.__actionMgr = FamilyActionMgr()
        self.__playerReqJoinDict = None # 玩家申请加入仙盟列表 {playerID:[familyID, ...], ...}
class ZoneFamilyMgr():
    ## 跨服公会互通分区,本服的也使用,默认分区0
    ## 【注意】跨服分区只是在原公会数据的基础上进行汇总归纳分区,即使分区异常也不要影响公会数据,可重复进行修改配置重新分区
    def __init__(self, zoneID=0):
        self.__familyMgr = DBDataMgr.GetFamilyMgr()
        self.__zoneID = zoneID # 本服默认分区0,即未互通的服务器
        self.__familyList = [] # 公会对象列表,可进行排序 [Family, ...]
        self.__familyIDDict = {} # 公会ID对象字典,  {familyID:Family, ...}
        self.__familyNameDict = {} # 公会名称对象字典,  {familyName:Family, ...}
        self.__zoneServerIDList = [] # 本分区实际已经互通的公会服务器ID列表 [serverID, ...]
        self.__familyIDRankDict = {} # 公会名次字典 {familyID:rank, ...}
        self.__needSort = True
        ## 注意!注意!注意!暂时只存分区的公会列表管理,其他分区相关的缓存数据暂不要放这里
        ## 因为分区配置重读变更后会被重置可能导致丢失,如需要存储其他数据再另行处理
        return
    
    def InitFamilyInstance(self, dbData):
        '''初始化功能数据实例,创建或加载数据时通用,功能一般不调用
        @param dbData: 实例对应绑定的dbData
        @return: 成功返回实例对象,失败返回None
    def GetZoneID(self): return self.__zoneID
    def SetSort(self): self.__needSort = True # 设置需要排序
    def Sort(self, checkSort=False):
        ''' 默认排序倒序,按  仙盟总战力 -> 仙盟等级
        @param checkSort: 是否检查需要排序,如果需要排序才会排序,设置True时则强制排序不检查
        '''
        family = Family(dbData)
        if checkSort and not self.__needSort:
            return
        self.__familyList.sort(key=lambda f: (f.GetFightPowerTotal(), f.GetLV()), reverse=True)
        self.__familyIDRankDict = {}
        self.__needSort = False
        return
    def AddFamilyToZone(self, family):
        ## 将某个公会分配给该分区
        if not family:
            return
        familyID = family.GetID()
        if familyID in self.__familyIDDict:
            return
        self.__familyList.append(family)
        self.__familyIDDict[familyID] = family
        self.__familyNameDict[family.GetName()] = family
        return family
    def Sort(self):
        ''' 默认排序倒序,按  仙盟总战力 -> 仙盟等级
        '''
        self.__familyList.sort(key=lambda f: (f.GetFightPowerTotal(), f.GetLV()), reverse=True)
        familyServerID = family.GetServerID()
        familyName = family.GetName()
        # 规定相同分区名字唯一,如重名,强制修改为唯一的;合服数据合并、首次互通传数据等均支持自动改名
        if familyName in self.__familyNameDict:
            for doCnt in range(1, 1000):
                fixName = "%s%s" % (familyName, doCnt) # 强制加个编号
                if fixName not in self.__familyNameDict:
                    familyName = fixName
                    break
        self.__familyNameDict[familyName] = family
        if family not in self.__familyList:
            self.__familyList.append(family)
            self.__familyIDRankDict = {} # 新公会加入时重置
        if familyServerID not in self.__zoneServerIDList:
            self.__zoneServerIDList.append(familyServerID)
        self.__needSort = True
        self.__familyMgr.OnAddToZone(family, self.__zoneID)
        return
    
    def AddFamily(self, familyName, serverID, familyID=None):
        ## 创建新仙盟
        newFamily = None
        if familyID == None:
            familyID = PyMongoMain.GetUserCtrlDB().GetNewFamilyID()
            if familyID <= 0:
                GameWorld.ErrLog("创建仙盟时生成新ID异常!")
                return newFamily
        if familyID in self.__familyIDDict:
            GameWorld.ErrLog("创建仙盟时ID已存在! familyID=%s" % familyID)
            return newFamily
        if familyName in self.__familyNameDict:
            GameWorld.ErrLog("创建仙盟时名称已存在! familyName=%s" % familyName)
            return newFamily
        if len(self.__familyList) >= FamilyUpperLimitCount:
            GameWorld.ErrLog("单服限制创建仙盟数已达上限!")
            return newFamily
        dbData = DBStruct.tagDBFamily()
        dbData.ID = familyID
        dbData.Name = familyName
        dbData.ServerID = serverID
        dbData.CreateTime = int(time.time())
        newFamily = self.InitFamilyInstance(dbData)
        if not newFamily and False: # 不执行,为了代码提示
            newFamily = Family()
        return newFamily
    def DelZoneFamily(self, familyID):
        family = self.__familyIDDict.pop(familyID, None)
        if not family:
            return
        if family in self.__familyList:
            self.__familyList.remove(family)
        self.__familyNameDict.pop(family.GetName(), None)
        self.__familyIDRankDict.pop(familyID, None)
        self.__needSort = True
        return
    def GetZoneServerIDList(self): return self.__zoneServerIDList
    
    def FindFamily(self, familyID):
        family = None
@@ -511,25 +562,6 @@
            family = Family()
        return family
    
    def DelFamily(self, familyID):
        family = self.FindFamily(familyID)
        if family:
            self.__familyNameDict.pop(family.GetName(), None)
            if family in self.__familyList:
                self.__familyList.remove(family)
            family.OnDelete()
        self.__familyIDDict.pop(familyID, None)
        if familyID >= ShareDefine.RealFamilyIDStart:
            PyMongoMain.GetUserCtrlDB().FreeFamilyID(familyID) # 归还仙盟ID,重复使用
        return family
    def DelAllFamily(self):
        for index in range(self.GetCount())[::-1]:
            family = self.GetAt(index)
            familyID = family.GetID()
            self.DelFamily(familyID)
        return
    def GetCount(self): return len(self.__familyList)
    def GetAt(self, index):
        family = None
@@ -539,31 +571,196 @@
            family = Family()
        return family
    
    def GetPlayerFamilyID(self, playerID):
        ## 获取玩家当前所属的仙盟ID
        for index in range(self.GetCount()):
            family = self.GetAt(index)
            if family.FindMember(playerID):
                return family.GetID()
        return 0
    def GetFamilyRank(self, familyID):
        ## 获取公会ID所在排名
        # @return: 0-未上榜; >0-名次
        if not familyID:
            return 0
        if not self.__familyIDRankDict:
            self.__familyIDRankDict = {}
            for rank, family in enumerate(self.__familyList, 1):
                self.__familyIDRankDict[family.GetID()] = rank
        if familyID not in self.__familyIDRankDict:
            return 0
        return self.__familyIDRankDict[familyID]
class FamilyMgr():
    ''' 所有分区的公会总管理,本服、跨服通用,zoneID为0时默认是本服的非跨服互通公会
    '''
    def __init__(self):
        ## ----------------------- 公会数据服务器的信息,如【未互通游戏服】 或 【跨服】 ------------------------
        # 本服务器数据,可能是游戏服本服 或 跨服互通数据
        self.__familyIDDict = {} # 公会ID对象字典,  {familyID:Family, ...}
        self.__playerFamilyIDDict = {} # 玩家所属公会ID字典 {playerID:familyID, ...}
        self.__playerReqJoinDict = {} # 玩家申请加入仙盟列表 {playerID:[familyID, ...], ...}
        self.__actionMgr = FamilyActionMgr()
        # 本服数据分区管理,游戏服尚未未分区的默认使用分区0,因为只管理本服公会数据,故不用再按crossServerID区分,默认都属于本服的serverID
        # 分区可能随时被重置重新分配,故如有需要持久化的逻辑数据需要另行处理,或在重置后复制所需持久化的数据,暂时不用,之后有需要再处理
        self.__zoneFamilyMgrDict = {} # 分区对应公会分区管理器 {zoneID:ZoneFamilyMgr(), ...}
        self.__familyIDZoneIDDict = {} # 公会ID对应所属分区 {familyID:zoneID, ...}
        ## ------------------------ 【所有服务器】会有的信息,由跨服中心或跨服同步 ------------------------
        # 分区配置说明:
        # 1. 启动时由跨服中心初步验证配置合理性,如服务器范围有交叉、拆分等,验证不通过不会通知,且邮件通知运维
        # 2. 跨服中心验证通过后广播给所有跨服服务器,跨服服务器进一步验证是否有未分配的公会,有则验证不通过,且邮件通知运维
        # 3. 跨服中心、跨服均验证通过后,会按配置的分区初始化各分区公会管理,然后同步给各自的分区游戏服
        # 4. 热更分区配置时,只更新跨服中心即可,由跨服中心发起验证,同样执行流程 1~3,验证不通过的不会更新配置,保留原分区配置
        # 5. 热更分区验证通过后,会重新生成公会分区,热更仅对不需要不改变公会数据所在服务器时有效
        # 6. 如需改变已互通公会数据所在跨服,需停服维护先进行跨服数据合并后,重启服务器
        # 【注】游戏服首次跨服,根据收到的互通配置进行验证是否开始跨服,开始后同步数据给所属跨服,可重复同步直到成功,否则还是保持使用本服公会数据
        self.__crossZoneCfgDict = {} # 跨服互通分区配置 {crossServerID:{zoneID:[互通服务器ID范围列表], ...}, ...},由跨服中心同步
        ## ------------------------ 【游戏服】专有信息,一般由所属互通跨服数据服同步 ----------------------
        # 互通公会基本信息 - 查看玩家页面需要看到的所属公会最简单的信息,一般只有游戏服用到
        #self.__familyViewBaseDict = {} # 公会ID对应基础查看信息 {familyID:FamilyViewBase, ...}
        self.__curZoneServerIDList = [] # 当前游戏服主服所属互通分区实际已经互通的服务器ID列表,同步给前端的
        self.__curCrossServerID = 0 # 当前游戏服主服公会所属跨服ID
        self.__curZoneID = 0 # 当前游戏服主服所属互通分区ID
        ## 【不允许不同互通分区的合服】
        return
    def InitFamilyInstance(self, dbData):
        '''初始化功能数据实例,创建或加载数据时通用,功能一般不调用
        @param dbData: 实例对应绑定的dbData
        @return: 成功返回实例对象,失败返回None
        '''
        family = Family(dbData)
        familyID = family.GetID()
        if familyID in self.__familyIDDict:
            return self.__familyIDDict[familyID]
        self.__familyIDDict[familyID] = family
        return family
    def AddFamily(self, familyName, serverID, familyID=None):
        ## 创建新仙盟
        newFamily = None
        if not familyName or not serverID:
            GameWorld.ErrLog("创建公会参数异常,无法创建! serverID=%s" % serverID)
            return newFamily
        zoneID = self.GetZoneIDInThisServer(serverID)
        if zoneID < 0:
            GameWorld.ErrLog("服务器ID创建的公会不属于本服管理,无法创建! serverID=%s" % serverID)
            return newFamily
        if familyID == None:
            familyID = PyMongoMain.GetUserCtrlDB().GetNewFamilyID()
            if familyID <= 0:
                GameWorld.ErrLog("创建公会时生成新ID异常!")
                return newFamily
        if familyID in self.__familyIDDict:
            GameWorld.ErrLog("创建公会时ID已存在! familyID=%s" % familyID)
            return newFamily
        # 名字通过分区管理验证并自动修改为不重名
        #if familyName in self.__familyNameDict:
        #    GameWorld.ErrLog("创建仙盟时名称已存在! familyName=%s" % familyName)
        #    return newFamily
        #if len(self.__familyList) >= FamilyUpperLimitCount:
        #    GameWorld.ErrLog("单服限制创建仙盟数已达上限!")
        #    return newFamily
        dbData = DBStruct.tagDBFamily()
        dbData.ID = familyID
        dbData.Name = familyName
        dbData.ServerID = serverID
        dbData.CreateTime = int(time.time())
        newFamily = self.InitFamilyInstance(dbData)
        if not newFamily and False: # 不执行,为了代码提示
            newFamily = Family()
        # 添加到所属分区
        zoneMgr = self.GetZoneFamilyMgr(zoneID)
        zoneMgr.AddFamilyToZone(newFamily)
        return newFamily
    def OnFightPowerChange(self, family):
        zoneMgr = self.GetZoneFamilyMgrByFamilyID(family.GetID())
        if not zoneMgr:
            return
        zoneMgr.SetSort()
        return
    def OnAddToZone(self, family, zoneID):
        familyID = family.GetID()
        self.__familyIDZoneIDDict[familyID] = zoneID
        return
    def OnAddMember(self, family, playerID):
        self.__playerFamilyIDDict[playerID] = family.GetID()
        return
    def OnDelMember(self, family, playerID):
        self.__playerFamilyIDDict.pop(playerID, None)
        return
    def GetFamilyIDList(self): return self.__familyIDDict.keys()
    def FindFamily(self, familyID):
        family = None
        if familyID and familyID in self.__familyIDDict:
            family = self.__familyIDDict[familyID]
        elif False:
            family = Family()
        return family
    def DelFamily(self, familyID, isFreeID=True):
        family = self.FindFamily(familyID)
        self.__familyIDDict.pop(familyID, None)
        # 同步从分区中删除
        if familyID in self.__familyIDZoneIDDict:
            zoneID = self.__familyIDZoneIDDict.get(familyID)
            zoneMgr = self.GetZoneFamilyMgr(zoneID)
            zoneMgr.DelZoneFamily(familyID)
        # 最后删除自身
        if family:
            family.OnDelete()
        if isFreeID and familyID >= ShareDefine.RealFamilyIDStart:
            PyMongoMain.GetUserCtrlDB().FreeFamilyID(familyID) # 归还仙盟ID,重复使用
        return family
    def DelAllFamily(self):
        for familyID in self.__familyIDDict.keys():
            self.DelFamily(familyID)
        return
    
    def GetFamilyActionMgr(self): return self.__actionMgr
    
    def __afterLoadDBFamilyData(self):
        self.__playerFamilyIDDict = {}
        self.__playerReqJoinDict = {}
        for family in self.__familyIDDict.values():
            familyID = family.GetID()
            family.RefrshFightPowerTotal()
            for index in range(family.GetCount()):
                member = family.GetAt(index)
                self.__playerFamilyIDDict[member.GetPlayerID()] = familyID
            reqPlayerIDDict = family.GetReqJoinPlayerInfo()
            #key强制转为int,线上的是字符串,做数据兼容
            for reqIDStr, v in reqPlayerIDDict.items():
                reqID = int(reqIDStr)
                reqPlayerIDDict.pop(reqIDStr, None)
                reqPlayerIDDict[reqID] = v
                if reqID not in self.__playerReqJoinDict:
                    self.__playerReqJoinDict[reqID] = []
                reqFamilyIDList = self.__playerReqJoinDict[reqID]
                if familyID not in reqFamilyIDList:
                    reqFamilyIDList.append(familyID)
        return
    def GetPlayerFamilyID(self, playerID):
        ## 获取玩家ID当前所在的公会ID
        return self.__playerFamilyIDDict.get(playerID, 0)
    def GetPlayerReqJoinFamilyIDList(self, playerID):
        ## 获取玩家申请加入的仙盟ID列表
        if self.__playerReqJoinDict == None:
            self.__playerReqJoinDict = {}
            for index in xrange(self.GetCount()):
                family = self.GetAt(index)
                familyID = family.GetID()
                reqPlayerIDDict = family.GetReqJoinPlayerInfo()
                for reqID in reqPlayerIDDict.keys():
                    if reqID not in self.__playerReqJoinDict:
                        self.__playerReqJoinDict[reqID] = []
                    reqFamilyIDList = self.__playerReqJoinDict[reqID]
                    if familyID not in reqFamilyIDList:
                        reqFamilyIDList.append(familyID)
        if playerID not in self.__playerReqJoinDict:
            self.__playerReqJoinDict[playerID] = []
        return self.__playerReqJoinDict[playerID]
@@ -596,13 +793,168 @@
            family.DelReqJoinPlayerID(playerID)
        return
    
    def GetCrossZoneCfgDict(self): return self.__crossZoneCfgDict # {crossServerID:{zoneID:[互通服务器ID范围列表], ...}, ...}
    def SetCrossZoneCfgDict(self, crossZoneCfgDict): self.__crossZoneCfgDict = crossZoneCfgDict # 直接设置的不做更新检测逻辑,一般游戏服用
    def UpdCrossZoneCfgDict(self, updCrossZoneCfgDict):
        ## 更新分区配置,重置分区,重新分配
        ## @return: 本跨服是否成功更新分区,验证不通过的话不会重置,保留原分区
        GameWorld.Log("跨服公会互通配置更新! updCrossZoneCfgDict=%s" % updCrossZoneCfgDict)
        if not CheckCrossZoneCfg(self.__crossZoneCfgDict, updCrossZoneCfgDict):
            return
        crossServerID = GameWorld.GetGameWorld().GetServerID()
        if crossServerID not in updCrossZoneCfgDict:
            GameWorld.Log("本跨服未分配分区的只更新配置即可!")
            self.__crossZoneCfgDict = updCrossZoneCfgDict
            return
        zoneDict = updCrossZoneCfgDict[crossServerID]
        # 存在没有分配的公会,不应该处理,可能分区配置与实际数据不一致,一般发生在调整分区时,邮件通知运维
        familyZoneDict = {}
        noZoneServerIDList = [] # 没有所属分区的服务器ID列表
        for family in self.__familyIDDict.values():
            familyID = family.GetID()
            familyServerID = family.GetServerID()
            zoneID = 0
            for zID, serverIDRangeList in zoneDict.items():
                if GameWorld.CheckServerIDInList(familyServerID, serverIDRangeList):
                    zoneID = zID
                    break
            if not zoneID:
                if familyServerID not in noZoneServerIDList:
                    noZoneServerIDList.append(familyServerID)
            else:
                familyZoneDict[familyID] = zoneID
        if noZoneServerIDList:
            GameWorld.SendGameErrorEx("FamilyCrossZoneCfgError", "noZoneServerIDList=%s" % noZoneServerIDList)
            return
        # 更新配置、重置、重新分区
        self.__crossZoneCfgDict = updCrossZoneCfgDict
        self.__zoneFamilyMgrDict = {}
        self.__familyIDZoneIDDict = {}
        zoneDict = self.__crossZoneCfgDict[crossServerID]
        for family in self.__familyIDDict.values():
            familyID = family.GetID()
            zoneID = familyZoneDict.get(familyID, 0) # 理论上不可能再为0
            zoneMgr = self.GetZoneFamilyMgr(zoneID)
            zoneMgr.AddFamilyToZone(family)
        return True
    def __setFamilyToDefaultZone(self):
        ## 将本服公会全部归到默认分区0,仅游戏服使用
        self.__zoneFamilyMgrDict = {}
        self.__familyIDZoneIDDict = {}
        zoneID = 0
        zoneMgr = self.GetZoneFamilyMgr(zoneID)
        for family in self.__familyIDDict.values():
            zoneMgr.AddFamilyToZone(family)
        return
    def GetZoneIDInThisServer(self, serverID=None):
        '''获取服务器ID在本公会数据服务器中所属的分区ID,
        @return: zoneID -2-或传输数据中;-1-分区不在本服务器;0-未互通默认分区0;>0-在本服数据中所在分区ID
        '''
        curServerID = GameWorld.GetGameWorld().GetServerID()
        if not serverID:
            serverID = curServerID
        zoneID = -1
        serverType = GameWorld.GetServerType()
        if serverType == ShareDefine.serverType_Main:
            if IsFamilyCrossInTransData():
                zoneID = -2
            elif not IsFamilyCross():
                zoneID = 0 # 本服未跨服互通的默认分区0
        elif GameWorld.IsCrossServer() and curServerID in self.__crossZoneCfgDict:
            # 跨服服务器不适用 __zoneFamilyMgr 判断是因为如果出现某个服务器ID的所有公会都被删除了,重启服务器的时候从分区管理中可能找不到该服务器ID
            # 所以使用已验证过的互通配置即可,互通配置已经验证过合理性,可以直接用
            zoneDict = self.__crossZoneCfgDict[curServerID]
            for zID, serverIDRangeList in zoneDict.items():
                if GameWorld.CheckServerIDInList(serverID, serverIDRangeList):
                    zoneID = zID
                    break
        return zoneID
    def GetZoneIDListThisServer(self): return self.__zoneFamilyMgrDict.keys() # 在本服数据中已存在的分区
    def GetFamilyZoneID(self, familyID): return self.__familyIDZoneIDDict.get(familyID, -1) # -1-未找到所属分区;>=0-所属分区
    def GetZoneFamilyMgrByFamilyID(self, familyID):
        ## 获取公会ID在本服数据所属分区管理器
        # @return: 可能返回None - 公会数据不在本服
        zoneID = -1
        if familyID in self.__familyIDZoneIDDict:
            zoneID = self.__familyIDZoneIDDict[familyID]
        zoneMgr = None
        if zoneID >= 0:
            zoneMgr = self.GetZoneFamilyMgr(zoneID)
        return zoneMgr
    def GetZoneFamilyMgr(self, zoneID=0):
        ## 获取某个分区的公会管理器
        # @param zoneID: 未互通的服务器默认使用0,使用方法一致
        zoneMgr = None
        if zoneID in self.__zoneFamilyMgrDict:
            zoneMgr = self.__zoneFamilyMgrDict[zoneID]
        else:
            zoneMgr = ZoneFamilyMgr(zoneID)
            self.__zoneFamilyMgrDict[zoneID] = zoneMgr
        return zoneMgr
#    def GetFamilyViewBase(self, familyID):
#        ## 互通公会基本信息,本服跨服通用,实际的公会完整数据可能不在本服
#        vBase = None
#        if familyID in self.__familyViewBaseDict:
#            vBase = self.__familyViewBaseDict[familyID]
#        else:
#            vBase = FamilyViewBase(familyID)
#            self.__familyViewBaseDict[familyID] = vBase
#        vBase.SetFamily(self.FindFamily(familyID))
#        return vBase
    def GetCurCrossServerID(self):
        ## 游戏服获取所属的跨服互通服务器ID,一个游戏服主服只允许一个互通分区,不同分区的不能合服
        # @return: -2-传输数据中;-1-非游戏服或没有正确的跨服ID;0-未互通;>0-互通的目标跨服ID
        serverType = GameWorld.GetServerType()
        if serverType != ShareDefine.serverType_Main:
            return -1
        if IsFamilyCrossInTransData():
            return -2
        # 未互通
        if not IsFamilyCross():
            return 0
        if self.__curCrossServerID:
            return self.__curCrossServerID
        curServerID = GameWorld.GetGameWorld().GetServerID()
        for crossServerID, zoneDict in self.__crossZoneCfgDict.items():
            for serverIDRangeList in zoneDict.values():
                if GameWorld.CheckServerIDInList(curServerID, serverIDRangeList):
                    return crossServerID
        return -1
    def SetCurCrossServerID(self, curCrossServerID): self.__curCrossServerID = curCrossServerID # 直接设置的游戏服专用
    def GetCurZoneID(self): # 本游戏服主服所属互通分区ID
        return 0 if not IsFamilyCross() else self.__curZoneID
    def SetCurZoneID(self, curZoneID): self.__curZoneID = curZoneID # 直接设置的游戏服专用
    def GetCurZoneServerIDList(self): return self.__curZoneServerIDList # 本游戏服主服实际已互通的服务器ID列表
    def SetCurZoneServerIDList(self, curZoneServerIDList): self.__curZoneServerIDList = curZoneServerIDList # 直接设置的游戏服专用
    # 保存数据 存数据库和realtimebackup
    def GetSaveData(self):
    def GetSaveData(self, cntDict=None):
        
        familyDataCnt, familySavaData = 0, ""
        membreDataCnt, memberSavaData = 0, ""
        actionDataCnt, actionSavaData = 0, ""
        for family in self.__familyList:
        for family in self.__familyIDDict.values():
            familyID = family.GetID()
            familySavaData += family.GetBuffer()
            familyDataCnt += 1
@@ -629,20 +981,20 @@
        
        saveData += CommFunc.WriteDWORD("", actionDataCnt) + actionSavaData
        GameWorld.Log("Save DBFamilyAction count :%s len=%s" % (actionDataCnt, len(actionSavaData)))
        if isinstance(cntDict, dict):
            cntDict.update({"familyDataCnt":familyDataCnt, "membreDataCnt":membreDataCnt, "actionDataCnt":actionDataCnt})
        return saveData
    
    # 从数据库载入数据
    def LoadPyGameData(self, datas, pos, dataslen):
        # 仙盟
    def LoadPyGameData(self, datas, pos, dataslen):
        # 公会
        cnt, pos = CommFunc.ReadDWORD(datas, pos)
        GameWorld.Log("Load DBFamily count :%s" % cnt)
        for _ in xrange(cnt):
            dbData = DBStruct.tagDBFamily()
            pos += dbData.readData(datas, pos, dataslen)
            self.InitFamilyInstance(dbData)
        self.Sort()
        # 成员
        cnt, pos = CommFunc.ReadDWORD(datas, pos)
        GameWorld.Log("Load DBFamilyMem count :%s" % cnt)
@@ -665,29 +1017,425 @@
            
            familyID = dbData.FamilyID
            actionType = dbData.ActionType
            family = self.FindFamily(familyID)
            if not family:
                continue
            action = self.__actionMgr.GetFamilyAction(familyID, actionType)
            action.InitActionInstance(dbData)
            
        self.__afterLoadDBFamilyData()
        PyMongoMain.GetUserCtrlDB().OnFamilyIDInit(self.__familyIDDict.keys())
        serverType = GameWorld.GetServerType()
        if serverType == ShareDefine.serverType_CrossCenter:
            self.UpdCrossZoneCfgDict(LoadZoneCfg())
        elif serverType == ShareDefine.serverType_Main:
            self.__setFamilyToDefaultZone() # 游戏服本服的数据无论有没有跨服了都同意设置到默认分区
        return pos
    
def OnMinute():
    familyMgr = DBDataMgr.GetFamilyMgr()
    # 每分钟刷新下仙盟战力排序
    isSort = False
    for index in range(familyMgr.GetCount()):
        family = familyMgr.GetAt(index)
        if not family:
def LoadZoneCfg(self):
    crossZoneCfgDict = {}
    appID = GameWorld.GetAppID()
    ipyDataMgr = IpyGameDataPY.IPY_Data()
    for index in range(ipyDataMgr.GetFamilyCrossCount()):
        ipyData = ipyDataMgr.GetFamilyCrossByIndex(index)
        if ipyData.GetAppID() != appID:
            continue
        if family.RefrshFightPowerTotal(True):
            isSort = True
    if isSort:
        familyMgr.Sort()
        crossServerID = ipyData.GetCrossServerID()
        zoneID = ipyData.GetZoneID()
        if crossServerID not in crossZoneCfgDict:
            crossZoneCfgDict[crossServerID] = {}
        zoneDict = crossZoneCfgDict[crossServerID]
        zoneDict[zoneID] = [] + ipyData.GetServerIDList()
    return crossZoneCfgDict
def CheckCrossZoneCfg(curCrossZoneCfgDict, updCrossZoneCfgDict):
    # 检查待更新的分区配置是否符合规则,不符合的话保留原配置,不更新,待扩展
    if curCrossZoneCfgDict == updCrossZoneCfgDict:
        GameWorld.Log("跨服公会互通分区配置不变不处理")
        return
    # 验证配置,是否有交叉、拆分,并邮件通知运维,待扩展,先开发功能
    #GameWorld.SendGameErrorEx("FamilyCrossZoneCfgError", "noZoneServerIDList=%s" % noZoneServerIDList)
    return True
def OnReloadConfig():
    '''重读配置验证修改后的配置是否符合规定,不符合的话,还是使用旧配置的范围,并邮件通知运维
    公会分区配置同步规则: 由跨服中心加载配置  -> 验证通过后发给所有跨服 -> 跨服验证通过后发给所管辖的分区所有游戏服务器
    '''
    if GameWorld.IsCrossCenter():
        crossZoneCfgDict = LoadZoneCfg()
        if not crossZoneCfgDict or not DBDataMgr.GetFamilyMgr().UpdCrossZoneCfgDict(crossZoneCfgDict):
            return
        Sync_CenterToCross_FamilyCrossCfg()
        
    return
def Sync_CenterToCross_FamilyInfo(serverType, serverID):
    ## 跨服中心同步给跨服服务器
    if serverType == ShareDefine.serverType_Cross:
        Sync_CenterToCross_FamilyCrossCfg(serverID)
    return
def Sync_CenterToCross_FamilyCrossCfg(serverID=0):
    familyMgr = DBDataMgr.GetFamilyMgr()
    crossZoneCfgDict = familyMgr.GetCrossZoneCfgDict()
    if not crossZoneCfgDict:
        GameWorld.Log("没有公会互通配置或没有验证正确的互通分区配置不通知!")
        return
    dataMsg = {"crossZoneCfgDict":crossZoneCfgDict}
    serverIDList = [serverID] if serverID else crossZoneCfgDict.keys()
    CrossMsg.SendToServer(ShareDefine.CC2C_FamilyCrossCfg, dataMsg, serverIDList, ShareDefine.dirType_Cross)
    return
def CC2C_FamilyCrossCfg(dataMsg, fromServerID, serverType):
    # 跨服中心同步的分区配置,仅跨服服务器处理即可,游戏服以所属跨服处理后的最终数据为准
    if serverType != ShareDefine.serverType_CrossCenter:
        return
    updCrossZoneCfgDict = dataMsg["crossZoneCfgDict"]
    if not updCrossZoneCfgDict:
        return
    if GameWorld.GetServerType() != ShareDefine.serverType_Cross:
        return
    familyMgr = DBDataMgr.GetFamilyMgr()
    if not familyMgr.UpdCrossZoneCfgDict(updCrossZoneCfgDict):
        return
    Sync_CrossToServer_FamilyInfo()
    return
def Sync_CrossToServer_FamilyInfo(toServerID=0, syncZoneID=0, syncFamilyIDList=[]):
    ## 跨服服务器同步互通公会信息给游戏服
    # @param toServerID: 有指定游戏服连上时只发给该服,没有的话一般是分区配置变更时由跨服主动同步所有相关游戏服
    familyMgr = DBDataMgr.GetFamilyMgr()
    crossZoneCfgDict = familyMgr.GetCrossZoneCfgDict() # 配置的互通
    if not crossZoneCfgDict:
        return
    crossServerID = GameWorld.GetGameWorld().GetServerID()
    if crossServerID not in crossZoneCfgDict:
        return
    zoneCfgDict = crossZoneCfgDict[crossServerID]
    for zoneID in familyMgr.GetZoneIDListThisServer():
        if syncZoneID and syncZoneID != zoneID:
            continue
        if zoneID not in zoneCfgDict:
            continue
        cfgServerIDList = zoneCfgDict[zoneID] # 配置需要互通的服务器信息,需要同步给这些服务器
        if toServerID:
            if not GameWorld.CheckServerIDInList(toServerID, cfgServerIDList):
                # 非指定目标服务器所属分区不同步
                continue
            toServerIDList = [toServerID]
        else:
            toServerIDList = cfgServerIDList
        zoneMgr = familyMgr.GetZoneFamilyMgr(zoneID)
        zoneServerIDList = zoneMgr.GetZoneServerIDList()
#        viewBaseDict = {}
#        for index in range(zoneMgr.GetCount()):
#            family = zoneMgr.GetAt(index)
#            familyID = family.GetID()
#            if syncFamilyIDList and familyID not in syncFamilyIDList:
#                continue
#            viewBase = familyMgr.GetFamilyViewBase(familyID)
#            viewBaseDict[familyID] = viewBase.GetSyncData()
        # 只通知给已经互通的相关服务器即可
        # 关于查看玩家
        # 1. 查看玩家直接到玩家所在服务器查询即可,查看玩家需要公会的一些基本信息,如 公会名、徽章、旗帜、互通跨服ID 等
        # 2. 玩家/查看玩家信息只需记录所属公会ID即可,公会相关基础信息通过 FamilyViewBase 获取,由互通所在跨服同步过来
        # 关于查看公会
        # 1. 从查看玩家中查看公会,发送公会ID、所属互通跨服ID,直接去目标服务器查询即可
        # 2. 从跨服活动中查看公会,活动相关公会记录所属互通跨服ID,同样直接去目标服务器查询即可
        dataMsg = {"crossZoneCfgDict":crossZoneCfgDict, "zoneID":zoneID, "zoneServerIDList":zoneServerIDList}
        CrossMsg.SendToClientServer(ShareDefine.C2S_FamilyCrossInfo, dataMsg, toServerIDList)
    return
def C2S_FamilyCrossInfo(dataMsg, fromServerID):
    ## 游戏服收到互通跨服同步的公会互通信息,按分区同步
    familyMgr = DBDataMgr.GetFamilyMgr()
    familyMgr.SetCurCrossServerID(fromServerID) # 直接设置,哪个服同步过来的就是所属跨服ID
    familyMgr.SetCurZoneID(dataMsg["zoneID"])
#    ## 本服更新跨服互通公会的基本信息
#    if "viewBaseDict" in dataMsg:
#        viewBaseDict = dataMsg["viewBaseDict"]
#        for familyID, syncData in viewBaseDict.items():
#            viewBase = familyMgr.GetFamilyViewBase(familyID)
#            viewBase.UpdSyncData(syncData)
    # 互通配置
    if "crossZoneCfgDict" in dataMsg:
        # 游戏服不用验证了,直接设置,跨服中心、跟跨服数据服已经验证过了,只要验证互通条件传输数据逻辑即可
        familyMgr.SetCrossZoneCfgDict(dataMsg["crossZoneCfgDict"])
        CheckCrossFamilyTransData(fromServerID)
    # 实际已互通分区
    if "zoneServerIDList" in dataMsg:
        curZoneServerIDList = familyMgr.GetCurZoneServerIDList()
        updZoneServerIDList = dataMsg["zoneServerIDList"]
        familyMgr.SetCurZoneServerIDList(updZoneServerIDList)
        if curZoneServerIDList != updZoneServerIDList:
            Sync_FamilyCrossInfo()
    return
def IsFamilyCross():
    ## 本服公会是否已跨服
    return DBDataMgr.GetEventTrigMgr().GetValue(ShareDefine.Def_FamilyCrossState) == 1
def IsFamilyCrossInTransData():
    ## 本服公会首次跨服互通同步数据中
    return DBDataMgr.GetEventTrigMgr().GetValue(ShareDefine.Def_FamilyTransDataTime) > 0
def CheckCrossFamilyTransData(connServerID=0, ignoreCD=False):
    ## 检查跨服公会传输数据,服务器启动时、onday时检查,或GM等指定强制调用
    if IsFamilyCross():
        GameWorld.DebugLog("本服公会已经跨服了!")
        return
    NeedServerDay = IpyGameDataPY.GetFuncCfg("FamilyCross", 1)
    serverDay = DBDataMgr.GetEventTrigMgr().GetValue(ShareDefine.Def_ServerDay) + 1
    if serverDay < NeedServerDay:
        GameWorld.DebugLog("本服公会互通所需开服天不足! serverDay=%s < %s" % (serverDay, NeedServerDay))
        return
    # 如果合服也以主服为准,所有合服的服务器要求本身必须也已经是跨服公会的才能合服
    familyMgr = DBDataMgr.GetFamilyMgr()
    crossZoneCfgDict = familyMgr.GetCrossZoneCfgDict()
    crossServerID = 0
    serverID = GameWorld.GetGameWorld().GetServerID()
    for cID, zoneDict in crossZoneCfgDict.items():
        for zoneID, serverIDRangeList in zoneDict.items():
            if GameWorld.CheckServerIDInList(serverID, serverIDRangeList):
                crossServerID = cID
                GameWorld.Log("本服公会所属跨服ID! serverID=%s,crossServerID=%s,zoneID=%s,serverIDRangeList=%s" % (serverID, crossServerID, zoneID, serverIDRangeList))
                break
        if crossServerID:
            break
    if not crossServerID:
        GameWorld.Log("本服公会未分配互通分区! serverID=%s" % (serverID))
        return
    if connServerID:
        if connServerID != crossServerID:
            GameWorld.Log("本服公会互通非目标跨服ID不处理! serverID=%s,crossServerID=%s != %s" % (serverID, crossServerID, connServerID))
            return
    else:
        ssServer = CrossMgr.GetSSServerMgr().GetSSServer(crossServerID)
        connState = ssServer.GetConnState()
        if connState != ShareDefine.ssConn_Normal:
            GameWorld.Log("本服公会互通目标跨服ID非连接状态! serverID=%s,crossServerID=%s,connState=%s" % (serverID, crossServerID, connState))
            return
    transDataTime = DBDataMgr.GetEventTrigMgr().GetValue(ShareDefine.Def_FamilyTransDataTime)
    if transDataTime and not ignoreCD:
        curTime = int(time.time())
        if curTime - transDataTime < 30 * 60: # 30分钟内不重复传输
            GameWorld.Log("本服公会互通传输数据中! serverID=%s,crossServerID=%s,transDataTime=%s" % (serverID, crossServerID, GameWorld.ChangeTimeNumToStr(transDataTime)))
            return
    GameWorld.Log("本服公会开启互通开始传输公会相关数据! serverID=%s,crossServerID=%s" % (serverID, crossServerID))
    DBDataMgr.GetEventTrigMgr().SetValue(ShareDefine.Def_FamilyTransDataTime, int(time.time()))
    cntDict = {}
    familyMgr = DBDataMgr.GetFamilyMgr()
    syncData = familyMgr.GetSaveData(cntDict)
    familyIDList = familyMgr.GetFamilyIDList()
    CrossMsg.SendToCrossServer(ShareDefine.S2C_FamilyData, {"syncData":syncData, "familyIDList":familyIDList, "cntDict":cntDict}, [crossServerID])
    return
def S2C_FamilyData(dataMsg, fromServerID):
    syncData = dataMsg["syncData"]
    familyIDList = dataMsg["familyIDList"]
    cntDict = dataMsg["cntDict"]
    GameWorld.Log("收到游戏服同步的互通公会数据! fromServerID=%s,cntDict=%s,familyIDList=%s" % (fromServerID, cntDict, familyIDList))
    unpackRet = __unpackFamilyData(syncData, familyIDList, cntDict)
    if not unpackRet:
        errorMsg = "unknown"
    else:
        errorMsg = unpackRet[0]
    if errorMsg:
        GameWorld.SendGameErrorEx("S2C_FamilyDataError", "互通公会数据同步失败! fromServerID=%s,errorMsg=%s" % (fromServerID, errorMsg))
        CrossMsg.SendToClientServer(ShareDefine.C2S_FamilyDataRet, {"isOK":False}, [fromServerID])
        return
    zoneID, familyDataList, memberDataList, actionDataList = unpackRet[1:]
    # 插入新数据,重名的会在加入分区后自动改名
    familyMgr = DBDataMgr.GetFamilyMgr()
    zoneMgr = familyMgr.GetZoneFamilyMgr(zoneID)
    actionMgr = familyMgr.GetFamilyActionMgr()
    syncFamilyIDList = []
    for dbData in familyDataList:
        dbData = DBStruct.tagDBFamily()
        familyID = dbData.ID
        familyMgr.DelFamily(familyID, False) # 每次都强制先删除,支持重复同步
        zoneMgr.AddFamilyToZone(familyMgr.InitFamilyInstance(dbData))
        syncFamilyIDList.append(familyID)
    # 成员
    for dbData in memberDataList:
        familyID = dbData.FamilyID
        family = familyMgr.FindFamily(familyID)
        if not family:
            continue
        family.InitMemberInstance(dbData)
    # 行为
    for dbData in actionDataList:
        familyID = dbData.FamilyID
        familyID = dbData.FamilyID
        actionType = dbData.ActionType
        family = familyMgr.FindFamily(familyID)
        if not family:
            continue
        action = actionMgr.GetFamilyAction(familyID, actionType)
        action.InitActionInstance(dbData)
    # 同步给相同互通分区的服务器
    Sync_CrossToServer_FamilyInfo(syncZoneID=zoneID, syncFamilyIDList=syncFamilyIDList)
    # 最后回复同步结果
    CrossMsg.SendToClientServer(ShareDefine.C2S_FamilyDataRet, {"isOK":True}, [fromServerID])
    return
def __unpackFamilyData(syncData, familyIDList, cntDict):
    ## 解包,验证数据
    # @param cntDict: {"familyDataCnt":familyDataCnt, "membreDataCnt":membreDataCnt, "actionDataCnt":actionDataCnt}
    errorMsg = ""
    datas, pos, dataslen = syncData, 0, len(syncData)
    zoneIDDict = {}
    familyMgr = DBDataMgr.GetFamilyMgr()
    # 公会
    familyDataList = []
    cnt, pos = CommFunc.ReadDWORD(datas, pos)
    GameWorld.Log("Read DBFamily count :%s" % cnt)
    for _ in xrange(cnt):
        dbData = DBStruct.tagDBFamily()
        pos += dbData.readData(datas, pos, dataslen)
        familyDataList.append(dbData)
        familyID = dbData.ID
        if familyID not in familyIDList:
            errorMsg = "同步的数据公会ID不匹配! familyID=%s not in %s" % (familyID, familyIDList)
            return errorMsg
        familyServerID = dbData.ServerID
        zoneID = familyMgr.GetZoneIDInThisServer(familyServerID)
        if zoneID <= 0:
            errorMsg = "同步的公会数据不属于本跨服! familyID=%s,familyServerID=%s,zoneID=%s" % (familyID, familyServerID, zoneID)
            return errorMsg
        zoneIDDict[zoneID] = familyServerID
    if len(zoneIDDict) != 1:
        errorMsg = "同步的公会数据分区异常可能存在多个分区! zoneIDDict=%s" % zoneIDDict
        return errorMsg
    zoneID = zoneIDDict.keys()[0]
    uppackFamilyCnt = len(familyDataList)
    familyDataCnt = cntDict.get("familyDataCnt", 0)
    if uppackFamilyCnt != familyDataCnt:
        errorMsg = "同步的公会个数不匹配! uppackFamilyCnt=%s != %s" % (uppackFamilyCnt, familyDataCnt)
        return errorMsg
    # 成员
    memberDataList = []
    cnt, pos = CommFunc.ReadDWORD(datas, pos)
    GameWorld.Log("Read DBFamilyMem count :%s" % cnt)
    for _ in xrange(cnt):
        dbData = DBStruct.tagDBFamilyMem()
        pos += dbData.readData(datas, pos, dataslen)
        memberDataList.append(dbData)
    uppackMemberCnt = len(memberDataList)
    membreDataCnt = cntDict.get("membreDataCnt", 0)
    if uppackMemberCnt != membreDataCnt:
        errorMsg = "同步的成员个数不匹配! uppackMemberCnt=%s != %s" % (uppackMemberCnt, membreDataCnt)
        return errorMsg
    # 行为
    actionDataList = []
    cnt, pos = CommFunc.ReadDWORD(datas, pos)
    GameWorld.Log("Read DBFamilyAction count :%s" % cnt)
    for _ in xrange(cnt):
        dbData = DBStruct.tagDBFamilyAction()
        pos += dbData.readData(datas, pos, dataslen)
        actionDataList.append(dbData)
    uppackActionCnt = len(actionDataList)
    actionDataCnt = cntDict.get("actionDataCnt", 0)
    if uppackMemberCnt != membreDataCnt:
        errorMsg = "同步的行为个数不匹配! uppackActionCnt=%s != %s" % (uppackActionCnt, actionDataCnt)
        return errorMsg
    return errorMsg, zoneID, familyDataList, memberDataList, actionDataList
def C2S_FamilyDataRet(dataMsg, fromServerID):
    isOK = dataMsg["isOK"]
    GameWorld.Log("收到互通公会数据同步结果! fromServerID=%s,isOK=%s" % (fromServerID, isOK))
    # 重置传输数据状态
    DBDataMgr.GetEventTrigMgr().SetValue(ShareDefine.Def_FamilyTransDataTime, 0)
    if not isOK:
        # 失败,暂时还是使用本服公会,等Onday再触发,或排查问题后维护,后续再优化多次同步方案
        # 运维邮件已在跨服发送,不服可不处理
        return
    GameWorld.Log("互通公会数据同步成功,本服正式开启跨服公会互通! fromServerID=%s" % (fromServerID))
    DBDataMgr.GetEventTrigMgr().SetValue(ShareDefine.Def_FamilyCrossState, 1)
    ChPlayer.SyncOnlinePlayerToCross(fromServerID)
    return
def OnMinute():
    if GameWorld.IsMainServer():
        if IsFamilyCross():
            # 游戏服公会已跨服不处理
            return
    familyMgr = DBDataMgr.GetFamilyMgr()
    # 每分钟刷新下仙盟战力排序
    for zoneID in familyMgr.GetZoneIDListThisServer():
        zoneMgr = familyMgr.GetZoneFamilyMgr(zoneID)
        zoneMgr.Sort(True)
    return
def Sync_FamilyCrossInfo(curPlayer=None):
    ## 同步本服公会互通信息
    if not IsFamilyCross():
        return
    familyMgr = DBDataMgr.GetFamilyMgr()
    clientPack = ChPyNetSendPack.tagSCFamilyCrossInfo()
    clientPack.ZoneID = familyMgr.GetCurZoneID()
    clientPack.ServerIDList = familyMgr.GetCurZoneServerIDList()
    clientPack.ServerCnt = len(clientPack.ServerIDList)
    if curPlayer:
        NetPackCommon.SendFakePack(curPlayer, clientPack)
    else:
        NetPackCommon.SendFackPackOnline(clientPack)
    return