From d5e51e22a6ba5d69da195f9621af8450af2291f1 Mon Sep 17 00:00:00 2001
From: hxp <ale99527@vip.qq.com>
Date: 星期二, 13 五月 2025 16:51:43 +0800
Subject: [PATCH] 10367 【越南】【英语】【BT】【砍树】仙盟攻城战-服务端(点赞奖励不限制是否有参赛资格;)

---
 ServerPython/CoreServerGroup/GameServer/Script/Player/PlayerFamily.py |  866 ++++++++++++++++++++++++++++++++++++++++++++++++++++-----
 1 files changed, 791 insertions(+), 75 deletions(-)

diff --git a/ServerPython/CoreServerGroup/GameServer/Script/Player/PlayerFamily.py b/ServerPython/CoreServerGroup/GameServer/Script/Player/PlayerFamily.py
index 71cff36..e779af4 100644
--- a/ServerPython/CoreServerGroup/GameServer/Script/Player/PlayerFamily.py
+++ b/ServerPython/CoreServerGroup/GameServer/Script/Player/PlayerFamily.py
@@ -29,18 +29,25 @@
 import DataRecordPack
 import PlayerFamilyBoss
 import IpyGameDataPY
+import PlayerFamilyZhenbaoge
 import PlayerFamilyRedPacket
 import GameWorldFamilyWar
 import ChPyNetSendPack
 import NetPackCommon
 import PyDataManager
 import PyGameData
+import PlayerBillboard
+import PlayerActBossTrial
 import PlayerCompensation
+import PlayerFamilyEmblem
 import PlayerFamilyParty
 import PlayerFamilySWRH
 import PlayerViewCache
 import GameWorldBoss
+import CrossRealmMsg
+import CrossFamilyGCZ
 import AuctionHouse
+import PlayerAssist
 import PlayerTalk
 import PlayerTeam
 
@@ -56,6 +63,62 @@
 ImpeachLastTime  # 弹劾需要持续的时间
 ) = range(3)
 
+class FamilyMgr():
+    
+    def __init__(self):
+        self.sortFamilyIDList = [] #本服仙盟排序顺序 [familyID, ...]
+        self.fightPowerChangeFamilyIDList = [] # 仙盟成员战力有变更的仙盟ID [familyID, ...]
+        
+        #这里仅针对增改信息,删除的另外处理,因为删除的需要确保成功删除,所以需要入库未成功删除的记录
+        #而变更同步的会定时同步,每次重连服务器也会强制同步,所以不需要有成功回复
+        self.syncCrossFamilyDict = {} # 需要同步跨服的仙盟 {familyID:[需要同步的成员ID, ...], ...}
+        return
+    
+    def OnDeleteFamilyID(self, familyID):
+        if familyID in self.sortFamilyIDList:
+            self.sortFamilyIDList.remove(familyID) # 直接从排序列表中移除, 不需要重新排序
+        self.SetSyncCrossFamilyDel(familyID) # 解散仙盟
+        return
+    
+    def GetFamilyIDRank(self, familyID):
+        if familyID not in self.sortFamilyIDList:
+            return len(self.sortFamilyIDList) + 1
+        return self.sortFamilyIDList.index(familyID) + 1
+    
+    def AddFamilyIDToFightPowerChangeList(self, familyID, playerID=0):
+        if familyID not in self.fightPowerChangeFamilyIDList:
+            self.fightPowerChangeFamilyIDList.append(familyID)
+            GameWorld.DebugLog("仙盟战力变更待处理列表: fightPowerChangeFamilyIDList=%s" % self.fightPowerChangeFamilyIDList)
+        self.SetSyncCrossFamilyUpd(familyID, playerID) # 仙盟战力变更、成员战力变更
+        return
+    
+    def SetSyncCrossFamilyUpd(self, familyID, playerID=0, syncNow=False):
+        if familyID not in self.syncCrossFamilyDict:
+            self.syncCrossFamilyDict[familyID] = []
+        if playerID:
+            needSyncMemIDList = self.syncCrossFamilyDict[familyID]
+            if playerID not in needSyncMemIDList:
+                needSyncMemIDList.append(playerID)
+        # 变更数据是否立即同步跨服,否则等待定时同步即可
+        if syncNow:
+            Sync_ClientFamilyUpdToCrossServer()
+        return
+    
+    def SetSyncCrossFamilyDel(self, familyID, playerID=0):
+        ## 设置同步跨服服务器仙盟删除
+        # @param playerID: 如果有值代表仅成员删除
+        valueSetList = [playerID]
+        gameRecMgr = PyDataManager.GetDBGameRecDataManager()
+        gameRecMgr.AddGameRecData(ShareDefine.Def_GameRecType_FamilyDelSyncCross, familyID, valueSetList)
+        Sync_ClientFamilyDelToCrossServer() # 删除的立马同步
+        return
+    
+def GetFamilyMgr():
+    mgr = PyGameData.g_familyMgr
+    if not mgr:
+        mgr = FamilyMgr()
+        PyGameData.g_familyMgr = mgr
+    return mgr
 
 ## ------------------ 仙盟 ----------------------
 ## 仙盟联赛排名
@@ -66,12 +129,19 @@
 def SetFamilyTotalFightPower(curFamily, totalFightPower):
     curFamily.SetExtra4(totalFightPower / ChConfig.Def_PerPointValue)
     curFamily.SetExtra5(totalFightPower % ChConfig.Def_PerPointValue)
+    GetFamilyMgr().SetSyncCrossFamilyUpd(curFamily.GetID()) # 仙盟战力变更
     return
 def GetFamilyTotalFightPowerByID(familyID):
     family = GameWorld.GetFamilyManager().FindFamily(familyID)
     if not family:
         return 0
     return GetFamilyTotalFightPower(family)
+# 徽章ID
+def GetFamilyEmblemID(curFamily): return curFamily.GetExtra6()
+def SetFamilyEmblemID(curFamily, emblemID):
+    curFamily.SetExtra6(emblemID)
+    GetFamilyMgr().SetSyncCrossFamilyUpd(curFamily.GetID()) # 徽章变更
+    return
 
 # 公告修改次数
 def GetFamilyBroadcastCnt(curFamily): return curFamily.GetExtra3()
@@ -88,12 +158,68 @@
 def GetFamilyMixServerDay(curFamily): return curFamily.GetExtra1()
 def SetFamilyMixServerDay(curFamily, value): return curFamily.SetExtra1(value)
 ## ------------------ 成员 ----------------------
-def GetMemberFightPower(curMember): return curMember.GetExattr3()
-def SetMemberFightPower(curMember, fightPower): return curMember.SetExattr3(fightPower)
+def GetMemberFightPower(curMember): return curMember.GetExattr3() + curMember.GetExattr5() * ChConfig.Def_PerPointValue
+def SetMemberFightPower(curMember, fightPower):
+    curMember.SetExattr5(fightPower / ChConfig.Def_PerPointValue)
+    curMember.SetExattr3(fightPower % ChConfig.Def_PerPointValue)
+    return
 
 def GetMemberJoinTime(curMember): return curMember.GetExattr4()
 def SetMemberJoinTime(curMember, joinTime): return curMember.SetExattr4(joinTime)
 #----------------------------------------------------------------------
+
+def OnGameServerInitOK():
+    ## 服务器启动成功处理
+    if GameWorld.IsCrossServer():
+        pass
+    else:
+        DoFamilySort()
+    return
+
+def OnMixServerInit():
+    ## 合服后首次启动成功处理
+    
+    if GameWorld.IsCrossServer():
+        return
+    
+    # 仙盟联赛重置
+    GameWorldFamilyWar.DoFamilyWarReset()
+    # 重置所有仙盟联赛评级
+    familyManager = GameWorld.GetFamilyManager()
+    for i in xrange(familyManager.GetCount()):
+        family = familyManager.GetAt(i)
+        SetFamilyWarRank(family, 0)
+        
+        # 仙盟榜相关榜单重新上榜
+        familyID = family.GetID()
+        familyBillInfo = GetFamilyBillboardInfo(family)
+        
+        familySubmitTotal = PlayerActBossTrial.GetFamilySubmitTotalByID(familyID)
+        PlayerBillboard.UpdateFamilyBillboard(ShareDefine.Def_BT_BossTrialSubmitFamily, familyBillInfo, familySubmitTotal)
+        
+    DoFamilySort()
+    return
+
+def OnLoadDBPlayerOK():
+    ## 服务器启动加载DB玩家成功处理
+    
+    # 检查仙盟ServerID
+    familyManager = GameWorld.GetFamilyManager()
+    for i in xrange(familyManager.GetCount()):
+        family = familyManager.GetAt(i)
+        if family.GetServerID():
+            continue
+        familyID = family.GetID()
+        # 没有则默认取盟主的
+        leaderID = family.GetLeaderID()
+        leaderAccID = PlayerControl.GetDBPlayerAccIDByID(leaderID)
+        if not leaderAccID:
+            continue
+        serverID = GameWorld.GetAccIDServerID(leaderAccID)
+        family.SetServerID(serverID)
+        GameWorld.Log("启动更新仙盟所属服务器ID: familyID=%s,serverID=%s,leaderID=%s,%s" % (familyID, serverID, leaderID, leaderAccID))
+        
+    return
 
 def RandomFakeFamily():
     '''随机3个假仙盟'''
@@ -132,9 +258,25 @@
             if not lackCnt:
                 break
     elif lackCnt < 0:
-        GameWorld.ErrLog('    随机假仙盟异常 已存在的随机数大于还需要的随机个数lackFakeCnt=%s,fakeIDList=%s'%(lackFakeCnt, fakeIDList))
+        #GameWorld.DebugLog('    随机假仙盟异常 已存在的随机数大于还需要的随机个数lackFakeCnt=%s,fakeIDList=%s'%(lackFakeCnt, fakeIDList))
         return []
     return fakeIDList
+
+def GetFamilyNameFakeIndex(familyName):
+    ## 获取仙盟名是否是系统随机出来的假仙盟名
+    # @return: 0-不是, >0 对应的 fakeIndex
+    fakeFamilyNameList = IpyGameDataPY.GetFuncEvalCfg('FakeFamilyName')
+    randomCnt = IpyGameDataPY.GetFuncCfg('FakeFamilyName', 2)
+    for i in xrange(randomCnt):
+        fakeID = PlayerDBGSEvent.GetDBGSTrig_ByKey(PlayerDBGSEvent.Def_FakeFamilyIndex % i)
+        if not fakeID:
+            continue
+        nameIndex = fakeID - 1
+        if nameIndex >=0 and nameIndex < len(fakeFamilyNameList):
+            fakeName = GameWorld.GbkToCode(fakeFamilyNameList[nameIndex])
+            if familyName == fakeName:
+                return fakeID
+    return 0
 
 def SyncFakeFamilyInfo(curPlayer=None):
     '''通知假仙盟信息'''
@@ -162,6 +304,28 @@
         NetPackCommon.SendFakePack(curPlayer, fakeFamilyPack)
     return
 
+def SyncCreatFamilyTimes(curPlayer=None):
+    # 通知建盟次数
+    packData = ChPyNetSendPack.tagGCServerCreatFamilyTimes()
+    packData.Clear()
+    packData.Times = PlayerDBGSEvent.GetDBGSTrig_ByKey(PlayerDBGSEvent.Def_ServerCreatFamilyTimes)
+    if not curPlayer:
+        # 全服广播在线玩家
+        playerManager = GameWorld.GetPlayerManager()
+        for i in range(0, playerManager.GetPlayerCount()):
+            curPlayer = playerManager.GetPlayerByIndex(i)
+            if curPlayer == None or not curPlayer.GetInitOK():
+                continue
+            
+            if PlayerControl.GetIsTJG(curPlayer):
+                continue
+            NetPackCommon.SendFakePack(curPlayer, packData)
+    else:
+        if PlayerControl.GetIsTJG(curPlayer):
+            return
+        NetPackCommon.SendFakePack(curPlayer, packData)
+    return
+
 #输入家族名称
 #class   IPY_CInputFamilyName
 #{
@@ -176,7 +340,7 @@
 #  @param tick 当前时间
 #  @return None
 #  @remarks 函数详细说明.
-def DoCreateFamily(curPlayer, familyName, fakeIndex, tick):
+def DoCreateFamily(curPlayer, familyName, fakeIndex, tick, emblemID=0):
     #---验证玩家属性---
     curPlayerID = curPlayer.GetPlayerID()
 
@@ -233,11 +397,16 @@
     if curFamily == None:
         GameWorld.ErrLog("家族创建数目已满, 创建家族失败", curPlayerID)
         return
-    GameWorld.Log("创建仙盟: familyID=%s,playerID=%s" % (curFamily.GetID(), curPlayerID))
+    emblemIDList = PlayerFamilyEmblem.GetDefaultFamilyEmblemIDList()
+    if not emblemID or emblemID not in emblemIDList:
+        emblemID = random.choice(emblemIDList) # 从默认徽章中随机选择一个
+    GameWorld.Log("创建仙盟: familyID=%s,playerID=%s,emblemID=%s" % (curFamily.GetID(), curPlayerID, emblemID))
     #---创建家族---
+    curFamily.SetServerID(GameWorld.GetAccIDServerID(curPlayer.GetAccID()))
     curFamily.SetCreateTime(GameWorld.GetCurrentDataTimeStr())
     curFamily.SetLV(1)
     curFamily.SetAcceptJoin(ShareDefine.FamilyAcceptJoin_Agree)     #设置收人方式为直接通过申请
+    SetFamilyEmblemID(curFamily, emblemID)
     PyDataManager.GetFamilyStoreItemManager().DelFamilyStoreItemAll(curFamily.GetID())
     
     #新创建的仙盟默认设置已处理过合服
@@ -245,11 +414,19 @@
     
     #-设置家族成员属性
     DoPlayerJionFamily(curFamily, curPlayer, IPY_GameServer.fmlLeader)
-    
+    creatFamilyTimes = PlayerDBGSEvent.GetDBGSTrig_ByKey(PlayerDBGSEvent.Def_ServerCreatFamilyTimes)
+    # 如果是手动指定仙盟名创建的,判断是否在系统随机的假仙盟里,如果是的话就当做创建系统分配的假仙盟处理
+    if familyName and not fakeIndex:
+        fakeIndex = GetFamilyNameFakeIndex(familyName)
+        GameWorld.DebugLog("    玩家手动输入创建仙盟名与系统随机的假仙盟名相同,默认当做创建假仙盟!fakeIndexID=%s" % (fakeIndex))
     #扣道具(前N个战盟并且假编号在随机编号里不要钱)
     if fakeIndex and fakeIndex in fakeIndexList:
         PlayerDBGSEvent.SetDBGSTrig_ByKey(PlayerDBGSEvent.Def_FakeFamilyIndex % fakeIndexList.index(fakeIndex), 0)
-        GameWorld.DebugLog('    创建前3个仙盟不扣钱! 假仙盟索引%s'%fakeIndexList.index(fakeIndex))
+        GameWorld.Log('    创建前n个假仙盟不扣钱! 假仙盟索引%s'%fakeIndexList.index(fakeIndex))
+        fakeAwardItemList = IpyGameDataPY.GetFuncEvalCfg("FakeFamilyName", 3)
+        PlayerCompensation.SendMailByKey("FackFamilyNotice", [curPlayerID], fakeAwardItemList)
+    elif creatFamilyTimes < IpyGameDataPY.GetFuncCfg('CreateFamilyNeedMoney', 3):
+        GameWorld.Log('    创建前n个仙盟不扣钱! creatFamilyTimes=%s' % creatFamilyTimes)
     else:
         for i, findex in enumerate(fakeIndexList):
             if findex:
@@ -259,7 +436,9 @@
         needMoney = IpyGameDataPY.GetFuncCfg('CreateFamilyNeedMoney')
         if needMoney:
             moneyType = IpyGameDataPY.GetFuncCfg('CreateFamilyNeedMoney', 2)
-            curPlayer.MapServer_PayMoney(moneyType, needMoney)
+            sendMsg = str([moneyType, needMoney])
+            curPlayer.MapServer_QueryPlayerResult(0, 0, "CreateFamilyPayMoney", sendMsg, len(sendMsg))
+            #curPlayer.MapServer_PayMoney(moneyType, needMoney)
             #玩家创建家族费用转化为家族初始资金
             #PlayerAddFamilyMoney(curPlayer, curFamily, needMoney)
   
@@ -285,13 +464,16 @@
     
     #XW_JZ_EstablishSud <n color="255,255,0">恭喜您,家族建立成功!</n>    25  -   -
     PlayerControl.NotifyCode(curPlayer, "XW_JZ_EstablishSud")
-    
+    PlayerDBGSEvent.SetDBGSTrig_ByKey(PlayerDBGSEvent.Def_ServerCreatFamilyTimes, min(creatFamilyTimes + 1, ShareDefine.Def_UpperLimit_DWord))
+    SyncCreatFamilyTimes()
     #帮会创建流向
     DataRecordPack.DR_CreateFamily(curPlayer.GetAccID(), curPlayerID, curPlayer.GetName(),
-                                   fullFamilyName, curFamily.GetID())
+                                   fullFamilyName, curFamily.GetID(), creatFamilyTimes+1)
 
-    GameWorld.Log('创建家族 : %s(%s), fakeIndex=%s' % (fullFamilyName, curFamily.GetID(), fakeIndex), curPlayerID)
+    GameWorld.Log('创建家族 : %s(%s), fakeIndex=%s, creatFamilyTimes=%s' % (fullFamilyName, curFamily.GetID(), fakeIndex, creatFamilyTimes+1), curPlayerID)
     PlayerControl.WorldNotify(0, "jiazu_liubo_671654", [curPlayer.GetName(), fullFamilyName, curFamily.GetID()])
+    
+    PlayerFamilyZhenbaoge.OnZhenbaogeReset(curFamily)
     return
 
 ## 获取家族全名
@@ -326,7 +508,7 @@
     #加入家族
     familyMember = curFamily.AddMember(jionPlayer)
     #刷新基本信息
-    RefreshFamilyMemberBaseMsg(familyMember, jionPlayer)
+    RefreshFamilyMemberBaseMsg(curFamily, familyMember, jionPlayer)
     
     #族长设置
     if jionFamilySetLv == IPY_GameServer.fmlLeader:
@@ -365,19 +547,21 @@
     #通知战盟红包信息
     PlayerFamilyRedPacket.NotifyRedPacketInfo(jionPlayer)
     
-    #通知战盟BOSS开启信息
-    PlayerFamilyBoss.NotifyFamilyBossFBInfo(jionPlayer)
+    #通知战盟BOSS
+    PlayerFamilyBoss.OnPlayerJionFamily(curFamily, jionPlayer)
     #通知家族仓库
     PyDataManager.GetFamilyStoreItemManager().SyncFamilyStoreItem(jionPlayer, curFamily.GetID())
     #仙盟拍品
     AuctionHouse.Sync_FamilyAuctionItemInfo(jionPlayer, curFamily.GetID())
-    SetMemberFightPower(familyMember, jionPlayer.GetFightPower())
-    AddFamilyIDToFightPowerChangeList(curFamily.GetID())
+    SetMemberFightPower(familyMember, PlayerControl.GetFightPower(jionPlayer))
+    GetFamilyMgr().AddFamilyIDToFightPowerChangeList(curFamily.GetID(), jionPlayer.GetPlayerID())
     
     #通知仙盟盛宴题目
     PlayerFamilyParty.NotifyFamilyPartyQuestion(jionPlayer)
     #通知守卫人皇信息
     PlayerFamilySWRH.NotifySWRHInfo(jionPlayer, curFamily.GetID())
+    #通知仙盟协助信息
+    PlayerAssist.SyncFamilyAssist(jionPlayer)
     #oss记录加入家族信息
     DataRecordPack.DR_PlayerJoinFamily(jionPlayer, curFamily.GetID(), curFamily.GetName(), curFamily.GetCount())
     return
@@ -387,7 +571,6 @@
     GameWorld.DebugLog('    玩家战盟名变更处理, newFamilyName=%s' % familyName, playerID)
     #不处理排行榜
     needChangeFamilyBillboardList = [
-                                     #ShareDefine.Def_BT_RechargeTeHui,  # 充值特惠活动排行榜-当前期记录
                                      ]
     billboardMgr = GameWorld.GetBillboard()
     for billboardIndex in needChangeFamilyBillboardList:
@@ -490,6 +673,7 @@
 #    tagHead        Head;
 #    char        Name[33];
 #    WORD        FakeID;
+#    BYTE        EmblemID; //选择徽章ID,解锁仙盟等级为1级的均为可选ID
 #};
 ## 查看申请帮会的成员信息
 #  @param index 玩家索引
@@ -500,8 +684,9 @@
     curPlayer = GameWorld.GetPlayerManager().GetPlayerByIndex(index)
     inputName = clientPack.Name
     fakeIndex = clientPack.FakeID
+    emblemID = clientPack.EmblemID
     #执行创建家族逻辑
-    DoCreateFamily(curPlayer, inputName, fakeIndex, tick)
+    DoCreateFamily(curPlayer, inputName, fakeIndex, tick, emblemID)
     #重置查看家族状态(仅创建家族时候重置, 其余状态由MapServer退出事件时重置)
     __ClearViewFamilyState(curPlayer)
     #玩家离开事件
@@ -561,7 +746,7 @@
 # @remarks 通知客户端服务器家族信息
 def Sync_AllFamilyInfo(curPlayer, viewPage, pageCnt=ChConfig.Def_ViewAllFamilyPageCount, sortRule=IPY_GameServer.fsrHornor):
     #familyCount = GameWorld.GetFamilyManager().GetCount()
-    familyCount = len(PyGameData.g_sortFamilyIDList)
+    familyCount = len(GetFamilyMgr().sortFamilyIDList)
     allPageCnt = GameWorld.GetIntUpper(familyCount, pageCnt)
 
     if allPageCnt != 0 and (viewPage < 0 or viewPage >= allPageCnt):
@@ -582,7 +767,7 @@
     #===============================================================================================
     return
 
-def SendFamilyFakePack(familyID, clientPack):
+def SendFamilyFakePack(familyID, clientPack, excludePlayerIDList=[]):
     ## 广播家族成员PY封包
     family = GameWorld.GetFamilyManager().FindFamily(familyID)
     if not family:
@@ -591,12 +776,16 @@
     for index in xrange(family.GetCount()):
         member = family.GetAt(index)
         memPlayer = member.GetPlayer()
-        if memPlayer:
-            NetPackCommon.SendFakePack(memPlayer, clientPack)
+        if not memPlayer:
+            continue
+        if excludePlayerIDList and memPlayer.GetPlayerID() in excludePlayerIDList:
+            continue
+        NetPackCommon.SendFakePack(memPlayer, clientPack)
     return
 
 def Sync_PyAllFamilyInfo(curPlayer, allPageCnt, viewPage, startIndex, endIndex):
-    familyCount = len(PyGameData.g_sortFamilyIDList)
+    sortFamilyIDList = GetFamilyMgr().sortFamilyIDList
+    familyCount = len(sortFamilyIDList)
     if startIndex < 0 or endIndex >= familyCount:
         return
     
@@ -607,7 +796,7 @@
     familyViewPack.CurPage = viewPage
     familyViewPack.Family = []
     for i in xrange(startIndex, endIndex + 1):
-        familyID = PyGameData.g_sortFamilyIDList[i]
+        familyID = sortFamilyIDList[i]
         family = familyMgr.FindFamily(familyID)
         if not family:
             continue
@@ -634,6 +823,7 @@
     totalFightPower = GetFamilyTotalFightPower(family)
     familyView.TotalFightPower = totalFightPower % ChConfig.Def_PerPointValue
     familyView.TotalFightPowerEx = totalFightPower / ChConfig.Def_PerPointValue
+    familyView.EmblemID = GetFamilyEmblemID(family)
     return familyView
 
 ## 玩家模糊查询家族,0F 0D封包
@@ -664,7 +854,7 @@
     familyViewPack.TotalCount = 1
     #familyViewPack.CurPage = viewPage
     familyViewPack.Family = []
-    for i, familyID in enumerate(PyGameData.g_sortFamilyIDList):
+    for i, familyID in enumerate(GetFamilyMgr().sortFamilyIDList):
         family = familyMgr.FindFamily(familyID)
         if not family:
             continue
@@ -701,7 +891,7 @@
     familyViewPack.TotalCount = 1
     #familyViewPack.CurPage = viewPage
     familyViewPack.Family = []
-    for i, familyID in enumerate(PyGameData.g_sortFamilyIDList):
+    for i, familyID in enumerate(GetFamilyMgr().sortFamilyIDList):
         family = familyMgr.FindFamily(familyID)
         if not family:
             continue
@@ -715,6 +905,76 @@
     familyViewPack.PageCount = len(familyViewPack.Family)
     NetPackCommon.SendFakePack(curPlayer, familyViewPack)
     return
+
+#// A4 13 修改家族徽章 #tagCGChangeFamilyEmblem
+#
+#struct    tagCGChangeFamilyEmblem
+#{
+#    tagHead        Head;
+#    BYTE        EmblemID;    // 更换的徽章ID
+#};
+def OnChangeFamilyEmblem(index, clientData, tick):
+    curPlayer = GameWorld.GetPlayerManager().GetPlayerByIndex(index)
+    changeEmblemID = clientData.EmblemID
+    PlayerFamilyEmblem.OnChangeFamilyEmblem(curPlayer, changeEmblemID)
+    return
+
+#// A4 14 查看目标仙盟 #tagCGViewTagFamily
+#
+#struct    tagCGViewTagFamily
+#{
+#    tagHead        Head;
+#    DWORD        TagFamilyID;    // 目标仙盟ID,支持查看跨服仙盟
+#};
+def OnViewTagFamily(index, clientData, tick):
+    curPlayer = GameWorld.GetPlayerManager().GetPlayerByIndex(index)
+    playerID = curPlayer.GetPlayerID()
+    tagFamilyID = clientData.TagFamilyID
+    clientPack = __GetTagFamilyInfoPack(tagFamilyID)
+    if not clientPack:
+        # 发送跨服服务器查询,可能是跨服仙盟
+        dataMsg = {"playerID":playerID, "tagFamilyID":tagFamilyID}
+        CrossRealmMsg.SendMsgToCrossServer(ShareDefine.ClientServerMsg_QueryCrossFamily, dataMsg)
+        return
+    NetPackCommon.SendFakePack(curPlayer, clientPack)
+    return
+
+def __GetTagFamilyInfoPack(tagFamilyID):
+    ## 获取查看目标仙盟同步封包
+    familyManager = GameWorld.GetFamilyManager()
+    family = familyManager.FindFamily(tagFamilyID)
+    if not family:
+        return
+    clientPack = ChPyNetSendPack.tagGCTagFamilyInfo()
+    clientPack.FamilyID = tagFamilyID
+    clientPack.FamilyName = family.GetName()
+    clientPack.FamilyLV = family.GetLV()
+    clientPack.Broadcast = family.GetBroadcast()
+    clientPack.BroadcastLen = len(clientPack.Broadcast)
+    clientPack.Member = []
+    for index in range(family.GetCount()):
+        curMember = family.GetAt(index)
+        mem = ChPyNetSendPack.tagGCTagFamilyMember()
+        mem.PlayerID = curMember.GetPlayerID()
+        mem.Name = curMember.GetName()
+        mem.NameLen = len(mem.Name)
+        mem.FamilyLV = curMember.GetFamilyLV()
+        mem.LV = curMember.GetLV()
+        mem.Job = curMember.GetJob()
+        mem.Face = curMember.GetFace()
+        mem.FacePic = curMember.GetFacePic()
+        mem.OfficialRank = curMember.GetOfficialRank()
+        fightPower = GetMemberFightPower(curMember)
+        mem.FightPower = fightPower % ChConfig.Def_PerPointValue
+        mem.FightPowerEx = fightPower / ChConfig.Def_PerPointValue
+        clientPack.Member.append(mem)
+    clientPack.MemberCount = len(clientPack.Member)
+    familyFightPower = GetFamilyTotalFightPower(family)
+    clientPack.FightPower = familyFightPower % ChConfig.Def_PerPointValue
+    clientPack.FightPowerEx = familyFightPower / ChConfig.Def_PerPointValue
+    clientPack.EmblemID = GetFamilyEmblemID(family)
+    clientPack.ServerID = family.GetServerID()
+    return clientPack
 
 #class   IPY_CFamilyChangeBroadcast
 #{
@@ -774,6 +1034,7 @@
             continue
         curPlayer.ChatMi(notifyPlayer, 1, pack.GetMsg(), 0, PlayerTalk.GetTalkExtraValue(curPlayer))
         PyDataManager.GetContactsManager().AddContactsBoth(curPlayer.GetID(), notifyPlayer.GetID())
+    GetFamilyMgr().SetSyncCrossFamilyUpd(curFamily.GetID()) # 公告变更
     return
 
 ## 检测目标玩家是否可以加入家族
@@ -1426,7 +1687,9 @@
     if PlayerFamilyBoss.IsInAllFamilyBoss():
         PlayerControl.NotifyCode(curPlayer, "LeagueBOSSExitError1")
         return
-    
+    if AuctionHouse.IsFamilyMemberBiddingAuctionItem(curFamily.GetID(), tagMemberID):
+        PlayerControl.NotifyCode(curPlayer, "Paimai7")
+        return
     tagPlayerName = curTagMember.GetName()  # 被踢玩家名
     tagPlayerID = curTagMember.GetPlayerID()  # 被踢玩家ID
     tagFamilyLV = curTagMember.GetFamilyLV()  # 被踢玩家职位
@@ -1437,10 +1700,10 @@
     PlayerFamilyAction.AddFamilyActionNote(tagPlayerName, curFamily.GetID(), ShareDefine.Def_ActionType_FamilyEvent,
                                            [ShareDefine.Def_FamilyActionEvent_MemberChange, ShareDefine.Def_FamilyMemberChange_KickOut], tick)
     #删除玩家
-    curFamily.DeleteMember(tagPlayerID)
-    __DoPlayerLeaveFamilyByID(curFamily, tagPlayerID)
-    
+    curFamily.DeleteMember(tagPlayerID)    
     tagPlayer = playerManager.FindPlayerByID(tagMemberID)
+    
+    __DoPlayerLeaveFamilyByID(curFamily, tagPlayerID, tagPlayer)
     #玩家在线, 设置这个玩家的属性
     PlayerForceLeaveFamily(tagPlayer, tick)
     
@@ -1508,6 +1771,9 @@
     if PlayerFamilyBoss.IsInAllFamilyBoss():
         PlayerControl.NotifyCode(curPlayer, "LeagueBOSSExitError1")
         return
+    if AuctionHouse.IsFamilyMemberBiddingAuctionItem(curFamily.GetID(), curMember.GetPlayerID()):
+        PlayerControl.NotifyCode(curPlayer, "Paimai8")
+        return
     #判断退出时间间隔
     curTime = int(time.time())
     lastLeaveFamilyTime = PlayerControl.GetLeaveFamilyTime(curPlayer)
@@ -1534,7 +1800,7 @@
     curFamily.DeleteMember(curMember.GetPlayerID())
     #玩家在线, 设置这个玩家的属性
     PlayerForceLeaveFamily(curPlayer, tick)  
-    __DoPlayerLeaveFamilyByID(curFamily, curPlayerID)
+    __DoPlayerLeaveFamilyByID(curFamily, curPlayerID, curPlayer)
 
     DataRecordPack.DR_PlayerLeaveFamily(curPlayer, curFamily.GetID(), curFamily.GetName(), curFamily.GetCount(),
                                         familyLV, curPlayer.GetPlayerID(), curPlayer.GetName(), familyLV, updTime)
@@ -1552,14 +1818,17 @@
 #  @param curFamily 离开的家族
 #  @param leavePlayerID 离开的玩家ID
 #  @return None
-def __DoPlayerLeaveFamilyByID(curFamily, leavePlayerID):
+def __DoPlayerLeaveFamilyByID(curFamily, leavePlayerID, tagPlayer=None):
+    PlayerCompensation.SendMailByKey("LeaveFamilyNotice", [leavePlayerID], [])
     PlayerFamilyAction.DelFamilyOfficerModelEquip(curFamily.GetID(), leavePlayerID)
     # 玩家战盟名变更处理
     __OnFamilyNameChange(leavePlayerID, '')
-    AddFamilyIDToFightPowerChangeList(curFamily.GetID())
+    GetFamilyMgr().AddFamilyIDToFightPowerChangeList(curFamily.GetID(), leavePlayerID)
     PlayerViewCache.OnPlayerFamilyChange(leavePlayerID, 0, "")
+    PlayerAssist.OnPlayerLeaveFamily(curFamily.GetID(), leavePlayerID, tagPlayer)
     if leavePlayerID in PyGameData.g_autoViceleaderDict.get(curFamily.GetID(),[]):
         PyGameData.g_autoViceleaderDict[curFamily.GetID()].remove(leavePlayerID)
+    GetFamilyMgr().SetSyncCrossFamilyDel(curFamily.GetID(), leavePlayerID) # 成员离开、踢出
     return
 
 #//////////////////////////////////////////////////////////////
@@ -1678,8 +1947,10 @@
                                           addFamilyMoney, curFamily.GetFamilyActiveValue(), addFamilyActiveValue)
     
     #通知客户端
-    #curFamily.Broadcast_FamilyChange()
-    curPlayer.Sync_FamilyInfo()
+    if addFamilyHornor:
+        curFamily.Broadcast_FamilyChange()
+    else:
+        curPlayer.Sync_FamilyInfo()
     
     #金钱变更时才通知
     if addFamilyMoney != 0:
@@ -1702,7 +1973,7 @@
     
     SetMemberFightPower(curMember, fightPower)
     GameWorld.DebugLog("仙盟成员战力变更 familyID=%s,fightPower=%s" % (familyID, fightPower), playerID)
-    AddFamilyIDToFightPowerChangeList(familyID)
+    GetFamilyMgr().AddFamilyIDToFightPowerChangeList(familyID, playerID)
     return
 
 ## A4 07 升级家族#tagCGFamilyLVUp
@@ -1811,6 +2082,7 @@
     #世界服务器家族重新排序
     #GameWorld.GetFamilyManager().SortByLV()
     DoFamilySort() # 升级直接强排一次
+    GetFamilyMgr().SetSyncCrossFamilyUpd(curFamily.GetID(), syncNow=True) # 仙盟等级变更
     return True
 
 #---------------------------------------------------------------------
@@ -1859,6 +2131,8 @@
 #  @remarks 函数详细说明.
 def OnPlayerChangeMap(curPlayer, tick):
     #同步给玩家, 最新的家族信息(家族等级刷新)
+    if GameWorld.IsCrossServer():
+        return
     curPlayer.MapServer_FamilyRefresh()
     return
 
@@ -1870,6 +2144,7 @@
 #  @remarks 函数详细说明.
 def PlayerLoginRefreshFamily(curPlayer, tick):
     SyncFakeFamilyInfo(curPlayer)
+    SyncCreatFamilyTimes(curPlayer)
     familyID = curPlayer.GetFamilyID()
     curFamily = None
     
@@ -1923,8 +2198,10 @@
     
     if not PlayerControl.GetIsTJG(curPlayer):
         #上线重置离线时间为0, 非脱机挂才设置
-        curMember.SetExattr2(0)
+        curMember.SetExattr2(0) # 在线0,脱机1,>1离线时间
         curPlayer.Sync_FamilyInfo()
+    else:
+        curMember.SetExattr2(1) # 脱机1
     
     curPlayer.MapServer_FamilyRefresh()
     curMember = GetPlayerFamilyMember(curPlayer)
@@ -2005,13 +2282,16 @@
 #  @param curPlayer 真实玩家
 #  @return None
 #  @remarks 刷新家族成员基本信息
-def RefreshFamilyMemberBaseMsg(curMember, curPlayer):
+def RefreshFamilyMemberBaseMsg(curFamily, curMember, curPlayer):
     curMember.SetName(curPlayer.GetName())
     curMember.SetLV(curPlayer.GetLV())
     curMember.SetReincarnationLv(curPlayer.GetReincarnationLv())
     curMember.SetJob(curPlayer.GetJob())
     curMember.SetOperateInfo(curPlayer.GetOperateInfo())
     curMember.SetOfficialRank(curPlayer.GetOfficialRank())
+    curMember.SetFace(curPlayer.GetFace())
+    curMember.SetFacePic(curPlayer.GetFacePic())
+    GetFamilyMgr().SetSyncCrossFamilyUpd(curFamily.GetID(), curPlayer.GetPlayerID()) # 成员基础信息刷新,含加入仙盟刷新
     return
 #---------------------------------------------------------------------
 ## 玩家刷新
@@ -2026,9 +2306,9 @@
     if curMember == None:
         return
     
-    RefreshFamilyMemberBaseMsg(curMember, curPlayer)
-    #家族长境界
     family = curPlayer.GetFamily()
+    RefreshFamilyMemberBaseMsg(family, curMember, curPlayer)
+    #家族长境界
     if family.GetLeaderID() == curPlayer.GetID():
         family.SetLeaderOfficialRank(curPlayer.GetOfficialRank())
     return
@@ -2144,8 +2424,7 @@
     
     #重新排序家族
     #GameWorld.GetFamilyManager().SortByLV()
-    if familyID in PyGameData.g_sortFamilyIDList:
-        PyGameData.g_sortFamilyIDList.remove(familyID) # 直接从排序列表中移除, 不需要重新排序
+    GetFamilyMgr().OnDeleteFamilyID(familyID)
         
     #家族科技删除, 改为地图直接处理, 暂屏蔽
     #PlayerFamilyTech.DelFamilyTechData(familyID)
@@ -2174,6 +2453,7 @@
     #设置族长权限
     ChangeFamilyMemberLv(familyMember, IPY_GameServer.fmlLeader)
     GameWorldFamilyWar.OnChangeFamilyLeader(curFamily.GetID(), familyMember.GetPlayerID())
+    GetFamilyMgr().SetSyncCrossFamilyUpd(curFamily.GetID(), syncNow=True) # 盟主变更
     return
 #---------------------------------------------------------------------
 ##更改家族成员等级.
@@ -2205,6 +2485,8 @@
     # 变为普通成员,删除模型装备信息
     elif changeFamilyLV == IPY_GameServer.fmlMember:
         PlayerFamilyAction.DelFamilyOfficerModelEquip(familyMember.GetFamilyID(), familyMember.GetPlayerID())
+        
+    GetFamilyMgr().SetSyncCrossFamilyUpd(familyMember.GetFamilyID(), familyMember.GetPlayerID(), syncNow=True) # 成员职位变更
     return
 
 #---------------------------------------------------------------------
@@ -2383,6 +2665,7 @@
     #通知家族刷新
     curFamily.Broadcast_FamilyChange()
     
+    GetFamilyMgr().SetSyncCrossFamilyUpd(curFamilyID, syncNow=True) # 仙盟改名
     playerManager = GameWorld.GetPlayerManager()
     
     #仙盟联赛
@@ -2563,6 +2846,8 @@
 # @return 返回值无意义
 # @remarks 家族过天
 def FamilyOnDay(tick):
+    if GameWorld.IsCrossServer():
+        return
     #---设置所有玩家可以再次加入家族---
     GameWorld.GetPlayerManager().ClearForbiddenEnterFamily()
     #---扣除地图上所有家族的维护费---
@@ -2620,6 +2905,17 @@
     __SetFamilyActivityDayStateValue(0)
     return
 
+def FamilyOnDayEx(tick):
+    if GameWorld.IsCrossServer():
+        return
+    familyManager = GameWorld.GetFamilyManager()
+    for i in range(0, familyManager.GetCount()):
+        family = familyManager.GetAt(i)
+        #仙盟boss
+        PlayerFamilyBoss.FamilyBossFBOnDayEx(family)
+        #珍宝阁
+        PlayerFamilyZhenbaoge.OnDayEx(family)
+    return
 
 #---------------------------------------------------------------------
 ##家族过周
@@ -2627,7 +2923,8 @@
 # @return 返回值无意义
 # @remarks 家族过周
 def FamilyOnWeek(tick):
-    
+    if GameWorld.IsCrossServer():
+        return
     #---计算上周家族活跃度---
     familyManager = GameWorld.GetFamilyManager()
     for i in range(0, familyManager.GetCount()):
@@ -2654,11 +2951,13 @@
         DataRecordPack.DR_FamilyActiveValueByOnWeek(familyID, family.GetName(), familyActiveValue)
         
         #清除家族boss副本信息
-        PlayerFamilyBoss.FamilyBossFBOnWeek(familyID)
+        PlayerFamilyBoss.FamilyBossFBOnWeek(family)
         
     return
 
 def FamilyOnHour():
+    if GameWorld.IsCrossServer():
+        return
     familyManager = GameWorld.GetFamilyManager()
     for i in xrange(familyManager.GetCount()):
         family = familyManager.GetAt(i)
@@ -2728,12 +3027,35 @@
             return cmp(GetMemberJoinTime(member1), GetMemberJoinTime(member2))
     return ret
 
+def SortCrossFamily(serverIDList, getCnt=0, page=1):
+    ''' 跨服仙盟排序, 排序规则: 总战力  > 等级 > ID
+    @param serverIDList: 仙盟所属区服ID范围列表
+    @param getCnt: 返回排序靠前x个仙盟,0则全部返回
+    @param page: 第几页
+    '''
+    familyList = []
+    familyManager = GameWorld.GetFamilyManager()
+    for i in xrange(familyManager.GetCount()):
+        family = familyManager.GetAt(i)
+        serverID = family.GetServerID()
+        if not GameWorld.CheckServerIDInList(serverID, serverIDList):
+            continue
+        familyList.append(family)
+    familyList.sort(key=lambda f: (GetFamilyTotalFightPower(f), f.GetLV(), f.GetID()), reverse=True)
+    totalCnt = len(familyList)
+    if getCnt:
+        startIndex = (page - 1) * getCnt
+        return familyList[startIndex:startIndex + getCnt], totalCnt
+    return familyList, totalCnt
+    
 #---------------------------------------------------------------------
 ##通知地图服务器, 玩家家族属性刷新
 # @param curFamily 家族实例
 # @return 返回值无意义
 # @remarks IPY_MFamilyRefresh
 def SendPack_MapServer_PlayerFamilyRefresh(curFamily):
+    if GameWorld.IsCrossServer():
+        return
 #===============================================================================
 #    当家族以下权限变更时要通知地图服务器 IPY_MFamilyRefresh
 #    GetFamilyLV
@@ -2772,7 +3094,7 @@
         GameWorld.ErrLog("key = %s not in tagFamily.txt" % familyLv)
         return 0
     keyStr = ChConfig.FamilySettingDict[index]
-    return getattr(curFamilyLvSetting, keyStr)
+    return getattr(curFamilyLvSetting, "Get%s" % keyStr)()
 
 
 #===============================================================================
@@ -2852,7 +3174,7 @@
         GameWorld.Log("GetLeaderOfflineTime->FindMember, None;%s" % leaderID)
         return 0
     offLineTimeNum = curMember.GetExattr2()
-    if not offLineTimeNum:
+    if not offLineTimeNum or offLineTimeNum == 1:
         return 0
     offLineTime = GameWorld.ChangeTimeNumToStr(offLineTimeNum)
     return GameWorld.GetPastHour(offLineTime)
@@ -2863,7 +3185,7 @@
     for i in range(0, family.GetCount()):
         member = family.GetAt(i) 
         offLineTimeNum = member.GetExattr2()
-        if not offLineTimeNum:
+        if not offLineTimeNum or offLineTimeNum == 1:
             #有人在线直接返回
             return 0
         if not offLineTime:
@@ -2898,7 +3220,7 @@
         elif toMember2.GetExattr1() < member.GetExattr1():
             toMember2 = member
 
-        offLineHour = GameWorld.GetPastHour(GameWorld.ChangeTimeNumToStr(member.GetExattr2())) if member.GetExattr2() else 0
+        offLineHour = GameWorld.GetPastHour(GameWorld.ChangeTimeNumToStr(member.GetExattr2())) if member.GetExattr2() > 1 else 0
         #GameWorld.DebugLog('memberID=%s 离线%s小时,历史贡献度%s'%(member.GetPlayerID(), offLineHour, member.GetExattr1()))
         if offLineHour < 48:
             if not toMember1:
@@ -3266,17 +3588,6 @@
     PlayerFamilyAction.ViewFamilyRequestInfo(curPlayer)
     return
 
-
-## 开启家族boss副本
-#  @param index 玩家索引
-#  @param clientData 封包数据结构体
-#  @param tick 时间戳
-#  @return None
-def OpenFamilyBossFB(index, clientData, tick):
-    curPlayer = GameWorld.GetPlayerManager().GetPlayerByIndex(index)
-    fbMapID = clientData.MapID
-    PlayerFamilyBoss.OpenFamilyBossFB(curPlayer, tick)
-    return
 #===============================================================================
 # //A4 06 变更家族成员加入审核方式#tagCGChangeFamilyAcceptJoinType
 #struct tagCGChangeFamilyAcceptJoinType
@@ -3388,21 +3699,30 @@
             break
     return leaderLV
 
-def AddFamilyIDToFightPowerChangeList(familyID):
-    if familyID not in PyGameData.g_fightPowerChangeFamilyIDList:
-        PyGameData.g_fightPowerChangeFamilyIDList.append(familyID)
-        GameWorld.DebugLog("仙盟战力变更待处理列表: fightPowerChangeFamilyIDList=%s" % PyGameData.g_fightPowerChangeFamilyIDList)
+def OnMinuteProcess(curMinute):
+    if GameWorld.IsCrossServer():
+        return
+    
+    #每5分钟触发一次仙盟更新
+    if curMinute % 5 != 0:
+        return
+    
+    UpdFamilyTotalFightPower()
+    PlayerFamilyRedPacket.CheckDelRedpacketData()
+    PlayerFamilyEmblem.CheckExpireEmblem()
+    Sync_ClientFamilyUpdToCrossServer()
     return
 
 def UpdFamilyTotalFightPower():
     ## 更新仙盟总战力
-    if not PyGameData.g_fightPowerChangeFamilyIDList:
+    mgr = GetFamilyMgr()
+    if not mgr.fightPowerChangeFamilyIDList:
         #GameWorld.DebugLog("不需要更新仙盟总战力!")
         return
         
-    GameWorld.DebugLog("更新仙盟总战力 fightPowerChangeFamilyIDList=%s" % PyGameData.g_fightPowerChangeFamilyIDList)
+    GameWorld.DebugLog("更新仙盟总战力 fightPowerChangeFamilyIDList=%s" % mgr.fightPowerChangeFamilyIDList)
     familyManager = GameWorld.GetFamilyManager()
-    for familyID in PyGameData.g_fightPowerChangeFamilyIDList:    
+    for familyID in mgr.fightPowerChangeFamilyIDList:    
         family = familyManager.FindFamily(familyID)
         if not family:
             continue
@@ -3414,19 +3734,17 @@
         SetFamilyTotalFightPower(family, totalFightPower)
         GameWorld.DebugLog("    familyID=%s,totalFightPower=%s" % (familyID, totalFightPower))
         
-    PyGameData.g_fightPowerChangeFamilyIDList = []
+    mgr.fightPowerChangeFamilyIDList = []
     
     DoFamilySort(False) # 此处必须为False
     return True
 
-def GetSortFamilyIDList(): return PyGameData.g_sortFamilyIDList
+def GetSortFamilyIDList(): return GetFamilyMgr().sortFamilyIDList
 def GetFamilyIDRank(familyID):
     '''获取仙盟的排名, 注意与联赛排名区分
     每个仙盟一定有排名,但是不一定有联赛排名,联赛排名只是决定仙盟最终排名的一个比较因素
     '''
-    if familyID not in PyGameData.g_sortFamilyIDList:
-        return len(PyGameData.g_sortFamilyIDList) + 1
-    return PyGameData.g_sortFamilyIDList.index(familyID) + 1
+    return GetFamilyMgr().GetFamilyIDRank(familyID)
 
 def DoFamilySort(isUpdTotalFightPower=True):
     ''' 仙盟排序, 排序规则: 联赛评级 > 总战力 > 等级 > 创建时间
@@ -3444,12 +3762,13 @@
         familyList.append(family)
     familyList.sort(cmp=CmpFamilySort)
     
-    PyGameData.g_sortFamilyIDList = []
+    mgr = GetFamilyMgr()
+    mgr.sortFamilyIDList = []
     for i, family in enumerate(familyList, 1):
         GameWorld.DebugLog("    i=%s,warRank=%s,fightPower=%s,LV=%s,CreateTime=%s,familyID=%s" 
                            % (i, GetFamilyWarRank(family), GetFamilyTotalFightPower(family), family.GetLV(), family.GetCreateTime(), family.GetID()))
-        PyGameData.g_sortFamilyIDList.append(family.GetID())
-    GameWorld.DebugLog("    sortFamilyIDList=%s" % PyGameData.g_sortFamilyIDList)
+        mgr.sortFamilyIDList.append(family.GetID())
+    GameWorld.DebugLog("    sortFamilyIDList=%s" % mgr.sortFamilyIDList)
     return
 
 def CmpFamilySort(family1, family2):
@@ -3498,7 +3817,8 @@
     requestPlayerName = curPlayer.GetName()
     playerLV = curPlayer.GetLV()
     #玩家Id, 等级,职业,战斗力
-    actionDataList = [curPlayer.GetID(), playerLV, curPlayer.GetJob(), curPlayer.GetFightPower()]
+    fightPower = PlayerControl.GetFightPower(curPlayer)
+    actionDataList = [curPlayer.GetID(), playerLV, curPlayer.GetJob(), fightPower % ChConfig.Def_PerPointValue, fightPower / ChConfig.Def_PerPointValue]
     allFamilyActionManager = GameWorld.GetFamilyActionManager()
     familyManager = GameWorld.GetFamilyManager()
     indexList = range(familyManager.GetCount())
@@ -3553,3 +3873,399 @@
         
     PlayerControl.NotifyCode(curPlayer, "jiazu_pan_500807")
     return
+
+##--------------------------------------- 仙盟传功 --------------------------------------------------
+def MapServer_FamilyChuangong(curPlayer, msgList):
+    msgType, msgData = msgList
+    
+    if msgType == "Invite":
+        tagPlayerID = msgData[0]
+        __DoChuangong_Invite(curPlayer, tagPlayerID)
+        return
+    
+    if msgType == "Response":
+        tagPlayerID, isOK = msgData
+        __DoChuangong_Response(curPlayer, tagPlayerID, isOK)
+        return
+    
+    if msgType == "ThanksGift":
+        __DoChuangong_ThanksGift(curPlayer, msgData)
+    return
+
+def __CheckChuangongPlayer(curPlayer, tagPlayerID):
+    curFamily = curPlayer.GetFamily()
+    if not curFamily:
+        return
+    tagMember = curFamily.FindMember(tagPlayerID)
+    if not tagMember:
+        GameWorld.DebugLog("非盟友无法传功! tagPlayerID=%s" % tagPlayerID, curPlayer.GetPlayerID())
+        return
+    tagPlayer = tagMember.GetPlayer()
+    if not tagPlayer:
+        PlayerControl.NotifyCode(curPlayer, "FairyFeastPlayerOffline")
+        return
+    return tagPlayer
+
+def __DoChuangong_Invite(curPlayer, tagPlayerID):
+    ## 邀请
+    playerID = curPlayer.GetPlayerID()
+    curFamily = curPlayer.GetFamily()
+    if not curFamily:
+        return
+    tagMember = curFamily.FindMember(tagPlayerID)
+    if not tagMember:
+        GameWorld.DebugLog("非盟友无法传功! tagPlayerID=%s" % tagPlayerID, curPlayer.GetPlayerID())
+        return
+    tagPlayer = tagMember.GetPlayer()
+    if not tagPlayer:
+        clientPack = ChPyNetSendPack.tagGCChuangongStart()
+        clientPack.Clear()
+        clientPack.PlayerID = tagMember.GetPlayerID()
+        clientPack.Name = tagMember.GetName()
+        clientPack.NameLen = len(clientPack.Name)
+        clientPack.LV = tagMember.GetLV()
+        clientPack.Job = tagMember.GetJob()
+        clientPack.RealmLV = tagMember.GetOfficialRank()
+        NetPackCommon.SendFakePack(curPlayer, clientPack)
+        PyGameData.g_chuangongTagPlayerDict[playerID] = tagPlayerID
+        GameWorld.DebugLog("对方离线,则自己直接开始传功: tagPlayerID=%s, %s" % (tagPlayerID, PyGameData.g_chuangongPlayerDict), playerID)
+        return
+    invitePlayerIDList = PyGameData.g_chuangongPlayerDict.get(playerID, [])
+    if tagPlayerID not in invitePlayerIDList:
+        invitePlayerIDList.append(tagPlayerID)
+        PyGameData.g_chuangongPlayerDict[playerID] = invitePlayerIDList
+    clientPack = ChPyNetSendPack.tagGCChuangongInviteInfo()
+    clientPack.Clear()
+    clientPack.PlayerID = curPlayer.GetPlayerID()
+    clientPack.Name = curPlayer.GetName()
+    clientPack.NameLen = len(clientPack.Name)
+    clientPack.LV = curPlayer.GetLV()
+    clientPack.Job = curPlayer.GetJob()
+    clientPack.RealmLV = curPlayer.GetOfficialRank()
+    clientPack.Face = curPlayer.GetFace()
+    clientPack.FacePic = curPlayer.GetFacePic()
+    NetPackCommon.SendFakePack(tagPlayer, clientPack)
+    GameWorld.DebugLog("邀请传功: tagPlayerID=%s, %s" % (tagPlayerID, PyGameData.g_chuangongPlayerDict), playerID)
+    return
+
+def __DoChuangong_Response(curPlayer, tagPlayerID, isOK):
+    ## 相应
+    playerID = curPlayer.GetPlayerID()
+    tagPlayer = __CheckChuangongPlayer(curPlayer, tagPlayerID)
+    if not tagPlayer:
+        return
+    invitePlayerIDList = PyGameData.g_chuangongPlayerDict.get(tagPlayerID, [])
+    if not isOK:
+        if playerID in invitePlayerIDList:
+            invitePlayerIDList.remove(playerID)
+            PyGameData.g_chuangongPlayerDict[tagPlayerID] = invitePlayerIDList
+        GameWorld.DebugLog("拒绝传功: tagPlayerID=%s, %s" % (tagPlayerID, PyGameData.g_chuangongPlayerDict), playerID)
+        return
+    if not invitePlayerIDList:
+        PlayerControl.NotifyCode(curPlayer, "TagHadFinishChuangong")
+        return    
+    if playerID not in invitePlayerIDList:
+        GameWorld.DebugLog("不在对方邀请列表了,无法传功: tagPlayerID=%s, %s" % (tagPlayerID, invitePlayerIDList), playerID)
+        return
+    PyGameData.g_chuangongPlayerDict.pop(tagPlayerID)
+    
+    # 通知双方开始传功
+    __NotifyChuangongStart(curPlayer, tagPlayer)
+    __NotifyChuangongStart(tagPlayer, curPlayer)
+    return
+
+def __NotifyChuangongStart(curPlayer, tagPlayer):
+    clientPack = ChPyNetSendPack.tagGCChuangongStart()
+    clientPack.Clear()
+    clientPack.PlayerID = tagPlayer.GetPlayerID()
+    clientPack.Name = tagPlayer.GetName()
+    clientPack.NameLen = len(clientPack.Name)
+    clientPack.LV = tagPlayer.GetLV()
+    clientPack.Job = tagPlayer.GetJob()
+    clientPack.RealmLV = tagPlayer.GetOfficialRank()
+    NetPackCommon.SendFakePack(curPlayer, clientPack)
+    PyGameData.g_chuangongTagPlayerDict[curPlayer.GetPlayerID()] = tagPlayer.GetPlayerID()
+    return
+
+def __DoChuangong_ThanksGift(curPlayer, msgData):
+    itemID = msgData[0]
+    playerID = curPlayer.GetPlayerID()
+    tagPlayerID = PyGameData.g_chuangongTagPlayerDict.pop(playerID, 0)
+    GameWorld.DebugLog("__DoChuangong_ThanksGift tagPlayerID=%s" % tagPlayerID, playerID)
+    if not tagPlayerID:
+        return
+    curFamily = curPlayer.GetFamily()
+    if not curFamily:
+        return
+    tagMember = curFamily.FindMember(tagPlayerID)
+    if not tagMember:
+        return
+    assistPlayerDict = {tagPlayerID:{"PlayerName":tagMember.GetName(), "Job":tagMember.GetJob(), 
+                                     "LV":tagMember.GetLV(), "RealmLV":tagMember.GetOfficialRank(),
+                                     "Face":tagMember.GetFace(), "FacePic":tagMember.GetFacePic()}}
+    GameWorld.DebugLog("    assistPlayerDict %s" % assistPlayerDict, playerID)
+    PlayerAssist.AddNewAssistThanksEx(curPlayer, itemID, assistPlayerDict)
+    return
+
+##--------------------------------------------------------------------------------------------------
+
+def CrossServerMsg_FamilyDelRet(msgData):
+    ## 跨服仙盟删除结果,有收到结果即代表成功
+    familyID = msgData["familyID"]
+    playerID = msgData.get("playerID", 0)
+    gameRecMgr = PyDataManager.GetDBGameRecDataManager()
+    if playerID:
+        gameRecMgr.DelGameRecDataByTypeValue(ShareDefine.Def_GameRecType_FamilyDelSyncCross, [playerID], familyID)
+    else:
+        gameRecMgr.DelGameRecDataByTypeID(ShareDefine.Def_GameRecType_FamilyDelSyncCross, familyID)
+    return
+
+def GetFamilyBillboardInfo(curFamily):
+    ## 获取仙盟榜单信息 区服ID、徽章、仙盟名、盟主名、仙盟总战力、仙盟等级
+    familyID = curFamily.GetID()
+    name = curFamily.GetName()
+    id2 = curFamily.GetLeaderID()
+    name2 = curFamily.GetLeaderName()
+    fightPower = GetFamilyTotalFightPower(curFamily)
+    value1 = fightPower / ChConfig.Def_PerPointValue
+    value2 = fightPower % ChConfig.Def_PerPointValue
+    value3 = GetFamilyEmblemID(curFamily)
+    value4 = curFamily.GetLV()
+    value5 = curFamily.GetServerID()
+    return {"id":familyID, "name":name, "id2":id2, "name2":name2, "value1":value1, "value2":value2, 
+            "value3":value3, "value4":value4, "value5":value5}
+
+def Sync_ClientFamilyUpdToCrossServer():
+    ## 定时同步仙盟变更数据到跨服服务器
+    
+    Sync_ClientFamilyDelToCrossServer() # 防止未删除成功,这里补通知
+    
+    mgr = GetFamilyMgr()
+    if not mgr.syncCrossFamilyDict:
+        return
+    
+    familyManager = GameWorld.GetFamilyManager()
+    for familyID, updMemIDList in mgr.syncCrossFamilyDict.items():
+        family = familyManager.FindFamily(familyID)
+        if not family:
+            continue
+        Send_ClientServerMsg_SyncFamilyInfo("FamilyUpd", GetSyncCrossServerFamilyInfo(family, updMemIDList))
+        
+    mgr.syncCrossFamilyDict = {}
+    return
+
+def Sync_ClientFamilyAllToCrossServer():
+    ## 同步子服所有仙盟信息到跨服
+    
+    if GameWorld.GetGameWorld().GetDictByKey(ChConfig.Def_WorldKey_SyncFamilyAllToCross):
+        return
+    GameWorld.GetGameWorld().SetDict(ChConfig.Def_WorldKey_SyncFamilyAllToCross, 1)
+    GameWorld.Log("开始同步本服所有仙盟到跨服服务器!")
+    
+    Sync_ClientFamilyDelToCrossServer() # 防止未删除成功,这里补通知
+    
+    familyManager = GameWorld.GetFamilyManager()
+    for i in xrange(familyManager.GetCount()):
+        family = familyManager.GetAt(i)
+        if not family:
+            continue
+        Send_ClientServerMsg_SyncFamilyInfo("FamilyUpd", GetSyncCrossServerFamilyInfo(family))
+    return
+
+def Sync_ClientFamilyDelToCrossServer():
+    ## 同步仙盟删除数据到跨服服务器
+    gameRecMgr = PyDataManager.GetDBGameRecDataManager()
+    recDataDict = gameRecMgr.GetGameRecDataDict(ShareDefine.Def_GameRecType_FamilyDelSyncCross)
+    for familyID, recDataList in recDataDict.items():
+        for recData in recDataList:
+            playerID = recData.GetValue1()
+            Send_ClientServerMsg_SyncFamilyInfo("FamilyDel", {"familyID":familyID, "playerID":playerID})
+    return
+
+def GetCrossFamilyBaseInfo(family):
+    return {"ID": family.GetID(), "Name": family.GetName(), "LV":family.GetLV(), "FightPower":GetFamilyTotalFightPower(family), 
+            "LeaderID": family.GetLeaderID(), "LeaderName": family.GetLeaderName(), "EmblemID":GetFamilyEmblemID(family), 
+            "ServerID":family.GetServerID(), "Broadcast":family.GetBroadcast()}
+    
+def GetCrossFamilyMemInfo(member):
+    return {"Name":member.GetName(), "LV":member.GetLV(), "Job":member.GetJob(), "OfficialRank":member.GetOfficialRank(), 
+            "Face":member.GetFace(), "FacePic":member.GetFacePic(), "FamilyLV":member.GetFamilyLV(), "FightPower":GetMemberFightPower(member)}
+    
+def GetSyncCrossServerFamilyInfo(family, memIDList=None):
+    ## 获取仙盟跨服所需信息
+    # @param memIDList: 需要获取成员信息ID列表,不传则取所有成员
+    
+    crossFamilyInfo = GetCrossFamilyBaseInfo(family)
+    
+    memAll = True if memIDList == None else False
+    memInfo = {}
+    for m in xrange(family.GetCount()):
+        member = family.GetAt(m)
+        memID = member.GetPlayerID()
+        if not memID:
+            continue
+        if memIDList and memID not in memIDList:
+            continue
+        memInfo[memID] = GetCrossFamilyMemInfo(member)
+        
+    crossFamilyInfo.update({"memInfo":memInfo, "memAll":memAll})
+    return crossFamilyInfo
+
+def Send_ClientServerMsg_SyncFamilyInfo(syncType, syncInfo):
+    dataMsg = {"syncType":syncType, "syncInfo":syncInfo}
+    CrossRealmMsg.SendMsgToCrossServer(ShareDefine.ClientServerMsg_SyncFamilyInfo, dataMsg)
+    return
+
+def ClientServerMsg_SyncFamilyInfo(serverGroupID, msgData):
+    ## 收到子服 - 仙盟信息
+    
+    syncType = msgData["syncType"]
+    syncInfo = msgData["syncInfo"]
+    
+    if syncType == "FamilyUpd":
+        __CrossServer_FamilyUpd(syncInfo)
+        
+    elif syncType == "FamilyDel":
+        __CrossServer_FamilyDel(serverGroupID, syncInfo)
+        
+    return
+
+def __CrossServer_FamilyDel(serverGroupID, syncInfo):
+    ## 跨服服务器删除子服仙盟、成员
+    familyID = syncInfo["familyID"]
+    playerID = syncInfo.get("playerID", 0)
+    if playerID:
+        curFamily = GameWorld.GetFamilyManager().FindFamily(familyID)
+        if curFamily:
+            curFamily.DeleteMember(playerID)
+    else:
+        #删除家族 
+        GameWorld.GetFamilyManager().DelFamily(familyID)
+        #删除家族行为数据
+        PlayerFamilyAction.ClearFamilyAction(familyID)
+        
+    # 直接回复就是删除成功
+    CrossRealmMsg.SendMsgToClientServer(ShareDefine.CrossServerMsg_FamilyDelRet, syncInfo, [serverGroupID])
+    return
+
+def __CrossServer_FamilyUpd(familyInfo):
+    ## 跨服服务器更新子服仙盟 - 这里只覆盖更新,不考虑删除的情况
+    
+    familyID = familyInfo["ID"]
+    familyName = familyInfo["Name"]
+    curFamily = FindAndFixCrossFamilyByIDName(familyID, familyName)
+    if not curFamily:
+        GameWorld.ErrLog("跨服更新仙盟失败! 仙盟不存在或者创建失败! familyID=%s" % familyID)
+        return
+    
+    #curFamily.SetName(familyInfo["Name"]) # 这里不再更新名称
+    curFamily.SetLV(familyInfo["LV"])
+    SetFamilyTotalFightPower(curFamily, familyInfo["FightPower"])
+    curFamily.SetLeaderID(familyInfo["LeaderID"])
+    curFamily.SetLeaderName(familyInfo["LeaderName"])
+    SetFamilyEmblemID(curFamily, familyInfo["EmblemID"])
+    curFamily.SetServerID(familyInfo["ServerID"])
+    curFamily.SetBroadcast(familyInfo["Broadcast"])
+    
+    updMemIDList = []
+    memAll = familyInfo.get("memAll", False)
+    memInfoDict = familyInfo.get("memInfo", {})
+    if memInfoDict:
+        for m in range(curFamily.GetCount())[::-1]:
+            member = curFamily.GetAt(m)
+            memID = member.GetPlayerID()
+            if not memID:
+                continue
+            if memID not in memInfoDict:
+                if memAll:
+                    curFamily.DeleteMember(memID)
+                continue
+            memInfo = memInfoDict.pop(memID)
+            __updCrossFamilyMemberInfo(curFamily, member, memInfo)
+            updMemIDList.append(memID)
+            
+        # 剩下的就是新增的成员
+        for memID, memInfo in memInfoDict.items():
+            member = curFamily.AddMemberEx(memID)
+            if not member:
+                continue
+            __updCrossFamilyMemberInfo(curFamily, member, memInfo)
+            updMemIDList.append(memID)
+            
+    # 相关活动数据更新
+    CrossFamilyGCZ.OnCrossJoinFamilyMemberUpd(curFamily, updMemIDList)
+    return
+
+def FindAndFixCrossFamilyByIDName(familyID, familyName):
+    ## 按仙盟名及ID查找跨服仙盟,重名时系统会自动修改仙盟名
+    
+    familyManager = GameWorld.GetFamilyManager()
+    curFamily = familyManager.FindFamily(familyID)
+    if not curFamily:
+        for i in range(100):
+            crossFamilyName = familyName if i == 0 else ("%s_%s" % (familyName, i))
+            curFamily = familyManager.AddFamilyEx(crossFamilyName, familyID)
+            if curFamily:
+                GameWorld.DebugLog("跨服添加新仙盟! familyID=%s, i=%s" % (familyID, i))
+                break
+        return curFamily
+    
+    # 验证当前名字是否还是唯一的,因为延迟同步的原因,仙盟可能改名时在本服唯一,但是在跨服不唯一
+    nameFamily = familyManager.FindFamilyByName(familyName)
+    if not nameFamily:
+        GameWorld.DebugLog("新同步的名字已经不存在仙盟了,可直接替换新仙盟名! familyID=%s" % familyID)
+        curFamily.SetName(familyName)
+        return curFamily
+    
+    if nameFamily.GetID() == familyID:
+        #GameWorld.DebugLog("还是自己原来的仙盟名,直接返回! familyID=%s" % familyID)
+        return curFamily
+    
+    # 尝试修改仙盟名,如果没有唯一名,则不修改保留最后一次同步的仙盟名,至少最后一次的名字还是唯一的
+    for i in range(1, 100):
+        fixFamilyName = "%s_%s" % (familyName, i)
+        family = familyManager.FindFamilyByName(fixFamilyName)
+        if family:
+            if family.GetID() == familyID:
+                #GameWorld.DebugLog("保留原系统修改的仙盟名! familyID=%s" % familyID)
+                break
+            # 已存在该仙盟名,且不是自己的仙盟
+            continue
+        curFamily.SetName(fixFamilyName)
+        GameWorld.Log("跨服强制修改仙盟名: familyID=%s,%s,%s" % (familyID, i, fixFamilyName))
+        break
+    
+    return curFamily
+
+def __updCrossFamilyMemberInfo(curFamily, member, memInfo):
+    member.SetName(memInfo.get("Name", ""))
+    member.SetLV(memInfo.get("LV", 1))
+    member.SetJob(memInfo.get("Job", 1))
+    member.SetOfficialRank(memInfo.get("OfficialRank", 1))
+    member.SetFace(memInfo.get("Face", 0))
+    member.SetFacePic(memInfo.get("FacePic", 0))
+    SetMemberFightPower(member, memInfo.get("FightPower", 0))
+    memFamilyLV = memInfo.get("FamilyLV", 0)
+    member.SetFamilyLV(memFamilyLV)
+    if memFamilyLV == IPY_GameServer.fmlLeader:
+        curFamily.SetLeaderID(member.GetPlayerID())
+        curFamily.SetLeaderName(member.GetName())
+        curFamily.SetLeaderOfficialRank(member.GetOfficialRank())
+        
+    return
+
+def ClientServerMsg_QueryCrossFamily(serverGroupID, msgData):
+    ## 子服查询跨服仙盟
+    playerID = msgData["playerID"]
+    tagFamilyID = msgData.get("tagFamilyID", 0)
+    if tagFamilyID:
+        clientPack = __GetTagFamilyInfoPack(tagFamilyID)
+        if clientPack:
+            NetPackCommon.SendFakePackByCross({playerID:serverGroupID}, clientPack)
+        else:
+            PlayerControl.NotifyCodeCross(serverGroupID, playerID, "TagFamilyNotExist")
+        return
+    
+    # 可扩展查看跨服仙盟列表信息...
+    
+    return

--
Gitblit v1.8.0