From 9d5ec7599f3abe0cebb76ce1df3c3b8c4e0aa51e Mon Sep 17 00:00:00 2001
From: hxp <ale99527@vip.qq.com>
Date: 星期二, 10 二月 2026 11:40:15 +0800
Subject: [PATCH] 66 【公会】基础主体-服务端(跨服聊天;)

---
 ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/DB/StructData/DBFamily.py | 1108 +++++++++++++++++++++++++++++++++++++++++++++++++--------
 1 files changed, 949 insertions(+), 159 deletions(-)

diff --git a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/DB/StructData/DBFamily.py b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/DB/StructData/DBFamily.py
index ee25408..312ce65 100644
--- a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/DB/StructData/DBFamily.py
+++ b/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():
     
@@ -75,10 +80,10 @@
         @param dbData: 实例对应绑定的dbData
         @return: 成功返回实例对象,失败返回None
         '''
-        dataToJson = False
-        # 如果需要 dataToJson,可根据ActionType在这里处理
+        dataToJson = True
+        # 默认使用 dataToJson,如果不需要的可根据 ActionType 在这里处理
         if dbData.ActionType in []:
-            dataToJson = True
+            dataToJson = False
         actionData = FamilyActionData(dbData, dataToJson)
         self.__actionDataList.append(actionData)
         return actionData
@@ -132,7 +137,28 @@
         elif False:
             aData = FamilyActionData()
         return aData
-        
+    
+    def GetActionDataByValue1(self, value1, isAdd=False):
+        ## 获取Action根据Value1
+        # @return: None or findActionData
+        findActionData = None
+        for actionData in self.__actionDataList:
+            if actionData.GetValue1() == value1:
+                findActionData = actionData
+                break
+        if not findActionData and isAdd:
+            findActionData = self.AddAction()
+            findActionData.SetValue1(value1)
+        return findActionData
+    
+    def DelActionDataByValue1(self, value1):
+        ## 删除Action根据Value1
+        for actionData in self.__actionDataList[::-1]:
+            if actionData.GetValue1() == value1:
+                self.__actionDataList.remove(actionData)
+                break
+        return
+    
 class FamilyActionMgr():
     
     def __init__(self):
@@ -140,6 +166,8 @@
         return
     
     def GetFamilyAction(self, familyID, actionType):
+        if not familyID:
+            return FamilyAction()
         if familyID not in self.__familyActionDict:
             self.__familyActionDict[familyID] = {}
         actionDict = self.__familyActionDict[familyID]
@@ -169,10 +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):
+        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
@@ -188,6 +221,8 @@
     def SetFace(self, face): self.__dbData.Face = face
     def GetFacePic(self): return self.__dbData.FacePic
     def SetFacePic(self, facePic): self.__dbData.FacePic = facePic
+    def GetTitleID(self): return self.__dbData.TitleID
+    def SetTitleID(self, titleID): self.__dbData.TitleID = titleID
     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
@@ -195,7 +230,7 @@
         self.__dbData.FightPower = fightPowerTotal % ChConfig.Def_PerPointValue
         self.__dbData.FightPowerEx = fightPowerTotal / ChConfig.Def_PerPointValue
         return
-    def GetServerID(self): return self.__dbData.ServerID
+    def GetServerID(self): return int(self.__dbData.ServerID)
     def SetServerID(self, serverID): self.__dbData.ServerID = serverID
     def GetOffTime(self): return self.__dbData.OffTime
     def SetOffTime(self, offTime): self.__dbData.OffTime = offTime
@@ -212,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)
@@ -224,30 +261,18 @@
         self.SetRealmLV(viewCache.GetRealmLV())
         self.SetFace(viewCache.GetFace())
         self.SetFacePic(viewCache.GetFacePic())
+        self.SetTitleID(viewCache.GetTitleID())
         self.SetServerID(viewCache.GetServerID())
+        if not self.GetOffTime(): # 有离线时间数据了,以成员自己的为准
+            self.SetOffTime(viewCache.GetOffTime())
         fpChange = False
         fightPowerTotal = viewCache.GetFightPowerTotal()
-        if self.GetFightPowerTotal() < fightPowerTotal:
+        if self.GetFightPowerTotal() != fightPowerTotal:
             self.SetFightPowerTotal(fightPowerTotal)
             fpChange = True
-        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.SetServerID(GameWorld.GetPlayerServerID(curPlayer))
-        fightPowerTotal = PlayerControl.GetFightPower(curPlayer)
-        fpChange = self.GetFightPowerTotal() != fightPowerTotal
-        self.SetFightPowerTotal(fightPowerTotal)
+            family = self.GetFamily()
+            if family:
+                family.RefrshFightPowerTotal()
         return fpChange
     
 class Family():
@@ -258,12 +283,11 @@
         self.__memberDict = {} # 成员字典 {playerID:FamilyMem, ...}
         self.__familyMgr = DBDataMgr.GetFamilyMgr()
         self.__actionMgr = DBDataMgr.GetFamilyActionMgr()
-        self.__memFightPowerChange = None # 成员战力是否有变化,默认None,代表未处理过
         return
     
     def GetID(self): return self.__dbData.ID
     def GetCreateTime(self): return self.__dbData.CreateTime
-    def GetServerID(self): return self.__dbData.ServerID
+    def GetServerID(self): return int(self.__dbData.ServerID)
     def GetName(self): return self.__dbData.Name
     def SetName(self, name): self.__dbData.Name = name
     def GetLeaderID(self): return self.__dbData.LeaderID
@@ -283,29 +307,47 @@
         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
         self.__memberDict[playerID] = member
         self.__memberList.append(member)
+        viewCache = PlayerViewCache.FindViewCache(playerID)
+        member.SetOffTime(viewCache.GetOffTime() if viewCache else int(time.time()))
+        self.__familyMgr.OnAddMember(self, playerID)
         return member
     
     def GetMemberIDList(self): return self.__memberDict.keys()
@@ -322,14 +364,15 @@
             member = self.InitMemberInstance(dbData)
             if not member and False:
                 member = FamilyMem()
-        self.__memFightPowerChange = True
+        self.RefrshFightPowerTotal()
         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):
@@ -349,24 +392,8 @@
             mem = FamilyMem()
         return mem
     
-    def RefreshFamilyMember(self, curPlayer):
-        ## 刷新在线成员信息
-        playerID = curPlayer.GetPlayerID()
-        familyID = curPlayer.GetFamilyID()
-        if self.GetID() != familyID:
-            return
-        member = self.FindMember(playerID)
-        if not member:
-            return
-        if member.RefreshMember(curPlayer):
-            self.__memFightPowerChange = True
-        return
-    
-    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)
@@ -374,8 +401,6 @@
                 continue
             familyFightPowerTotal += member.GetFightPowerTotal()
         self.SetFightPowerTotal(familyFightPowerTotal)
-        #GameWorld.DebugLog("刷新仙盟总战力! familyID=%s" % self.GetID())
-        self.__memFightPowerChange = False
         return familyFightPowerTotal
     
     def GetReqJoinPlayerInfo(self):
@@ -409,72 +434,127 @@
         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
+            family.SetName(familyName)
+            family.SetExtra1(0) # 重置改名CD
+        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
-        if familyID in self.__familyIDDict:
+        if familyID and familyID in self.__familyIDDict:
             family = self.__familyIDDict[familyID]
         elif False:
             family = Family()
@@ -488,25 +568,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
@@ -516,31 +577,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]
@@ -573,13 +799,176 @@
             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]
+        GameWorld.Log("本跨服重置公会互通分区! crossServerID=%s,zoneDict=%s" % (crossServerID, zoneDict))
+        for zoneID in zoneDict.keys():
+            self.GetZoneFamilyMgr(zoneID)
+        GameWorld.Log("GetZoneIDListThisServer=%s" % self.GetZoneIDListThisServer())
+        
+        # 重新将本服公会数据分配到所属分区
+        for family in self.__familyIDDict.values():
+            familyID = family.GetID()
+            zoneID = familyZoneDict.get(familyID, 0) # 理论上不可能再为0,因为有0时 noZoneServerIDList 验证不会通过
+            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
@@ -606,20 +995,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)
@@ -642,29 +1031,430 @@
             
             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():
+def LoadZoneCfg():
+    crossZoneCfgDict = {}
+    appID = GameWorld.GetAppID()
+    ipyDataMgr = IpyGameDataPY.IPY_Data()
+    for index in range(ipyDataMgr.GetFamilyCrossCount()):
+        ipyData = ipyDataMgr.GetFamilyCrossByIndex(index)
+        if ipyData.GetAppID() != appID:
+            continue
+        crossServerID = ipyData.GetCrossServerID()
+        zoneID = ipyData.GetZoneID()
+        if crossServerID not in crossZoneCfgDict:
+            crossZoneCfgDict[crossServerID] = {}
+        zoneDict = crossZoneCfgDict[crossServerID]
+        zoneDict[zoneID] = [] + ipyData.GetServerIDList()
+    GameWorld.Log("跨服公会分区配置加载: appID=%s,%s" % (appID, crossZoneCfgDict))
+    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()
+        Sync_CrossToServer_FamilyInfo()
+    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()
-    # 每分钟刷新下仙盟战力排序
-    isSort = False
-    for index in range(familyMgr.GetCount()):
-        family = familyMgr.GetAt(index)
-        if not family:
+    if not familyMgr.UpdCrossZoneCfgDict(updCrossZoneCfgDict):
+        return
+    Sync_CrossToServer_FamilyInfo()
+    return
+
+def Sync_CrossToServer_FamilyInfo(toServerID=0, syncZoneID=0, syncFamilyIDList=[]):
+    ## 跨服服务器同步互通公会信息给游戏服
+    # @param toServerID: 有指定游戏服连上时只发给该服,没有的话一般是分区配置变更时由跨服主动同步所有相关游戏服
+    
+    GameWorld.DebugLog("Sync_CrossToServer_FamilyInfo toServerID=%s,syncZoneID=%s,syncFamilyIDList=%s" % (toServerID, syncZoneID, syncFamilyIDList))
+    familyMgr = DBDataMgr.GetFamilyMgr()
+    crossZoneCfgDict = familyMgr.GetCrossZoneCfgDict() # 配置的互通
+    if not crossZoneCfgDict:
+        return
+    crossServerID = GameWorld.GetGameWorld().GetServerID()
+    if crossServerID not in crossZoneCfgDict:
+        return
+    zoneCfgDict = crossZoneCfgDict[crossServerID]
+    GameWorld.DebugLog("    crossServerID=%s,zoneCfgDict=%s" % (crossServerID, zoneCfgDict))
+    
+    for zoneID in familyMgr.GetZoneIDListThisServer():
+        if syncZoneID and syncZoneID != zoneID:
             continue
-        if family.RefrshFightPowerTotal(True):
-            isSort = True
-    if isSort:
-        familyMgr.Sort()
+        if zoneID not in zoneCfgDict:
+            continue
+        cfgServerIDList = zoneCfgDict[zoneID] # 配置需要互通的服务器信息,需要同步给这些服务器
+        
+        if toServerID:
+            if not GameWorld.CheckServerIDInList(toServerID, cfgServerIDList):
+                # 非指定目标服务器所属分区不同步
+                #GameWorld.DebugLog("    非指定目标服务器所属分区不同步 toServerID=%s" % (toServerID))
+                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()
+    
+    GameWorld.Log("dataslen=%s" % len(syncData))
+    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)
+    isOK = unpackRet[0]
+    if not isOK:
+        errorMsg = unpackRet[1]
+        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:
+        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)
+    GameWorld.Log("__unpackFamilyData: dataslen=%s" % dataslen)
+    
+    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 False, errorMsg
+        
+        familyServerID = dbData.ServerID
+        zoneID = familyMgr.GetZoneIDInThisServer(familyServerID)
+        if zoneID <= 0:
+            errorMsg = "同步的公会数据不属于本跨服! familyID=%s,familyServerID=%s,zoneID=%s" % (familyID, familyServerID, zoneID)
+            return False, errorMsg
+        zoneIDDict[zoneID] = familyServerID
+        
+    if len(zoneIDDict) != 1:
+        errorMsg = "同步的公会数据分区异常可能存在多个分区! zoneIDDict=%s" % zoneIDDict
+        return False, errorMsg
+    zoneID = zoneIDDict.keys()[0]
+    
+    uppackFamilyCnt = len(familyDataList)
+    familyDataCnt = cntDict.get("familyDataCnt", 0)
+    if uppackFamilyCnt != familyDataCnt:
+        errorMsg = "同步的公会个数不匹配! uppackFamilyCnt=%s != %s" % (uppackFamilyCnt, familyDataCnt)
+        return False, 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 False, 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 False, errorMsg
+    
+    return True, 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
     
\ No newline at end of file

--
Gitblit v1.8.0