ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/PlayerFamily.py
@@ -28,12 +28,13 @@
import IPY_PlayerDefine
import IpyGameDataPY
import IPY_GameWorld
import ItemControler
import GameFuncComm
import PlayerMail
import PlayerTask
import DBDataMgr
import CrossPlayer
import DirtyList
import ObjPool
import DBDataMgr
import DBFamily
import CrossMsg
import random
import time
@@ -42,24 +43,7 @@
Def_CreatFamily_MaxStr = 33
#仙盟变更类型
FamilyChangeTypes = (
FamilyChangeType_None,
FamilyChangeType_MemJoin, # 成员加入 1
FamilyChangeType_MemLeave, # 成员退出 2
FamilyChangeType_JoinSet, # 收人设置修改 3
FamilyChangeType_Broadcast, # 公告修改 4
FamilyChangeType_EChange, # 徽章修改 5
FamilyChangeType_LeaderChange, # 盟主变更 6
FamilyChangeType_MemFmlvChange, # 成员职位变更 7
FamilyChangeType_MemLogin, # 成员上线 8
FamilyChangeType_MemLogout, # 成员离线9
FamilyChangeType_FamilyLVExp, # 仙盟等级经验变更 10
FamilyChangeType_MemContrib, # 成员贡献变更 11
FamilyChangeType_OnDay, # 过天 12
) = range(13)
#仙盟权限
#公会权限
(
FamilyPowerID_Call,         #招人 1
FamilyPowerID_ChangeFmlv,   #变更职位 2
@@ -67,56 +51,133 @@
FamilyPowerID_Kick,         #踢人 4
) = range(1, 1 + 4)
#仙盟职位对应人数设置属性名
#公会职位对应人数设置属性名
Def_FmlSetAttrName = {
                      IPY_PlayerDefine.fmlMember:"MemberMax",
                      IPY_PlayerDefine.fmlCounsellor:"EliteMax",
                      IPY_PlayerDefine.fmlViceLeader:"DeputyLeaderMax",
                      }
def FamilyOnDay():
def GetRenameTime(family): return family.GetExtra1()
def SetRenameTime(family, renameTime): return family.SetExtra1(renameTime)
def OnMinute():
    #战力刷新在DBFamily.OnMinute
    if GameWorld.IsCrossServer():
        pass
    elif not GameWorld.IsMainServer() or DBFamily.IsFamilyCross():
        return
    PlayerFamilyEmblem.CheckExpireEmblem()
    return
def FamilyCrossCenterOnHour():
    if GameWorld.IsCrossServer():
        __doFamilyOnHour()
    return
def FamilyOnHour():
    if not GameWorld.IsMainServer() or DBFamily.IsFamilyCross():
        # 非游戏服或本服已跨服互通了不处理
        return
    __doFamilyOnHour()
    return
def __doFamilyOnHour():
    familyManager = DBDataMgr.GetFamilyMgr()
    for zoneID in familyManager.GetZoneIDListThisServer():
        zoneMgr = familyManager.GetZoneFamilyMgr(zoneID)
        for i in range(0, zoneMgr.GetCount()):
            family = zoneMgr.GetAt(i)
            #自动传位
            __AutoChangeLeader(family)
    return
def FamilyCrossCenterOnDay():
    if GameWorld.IsCrossServer():
        __doFamilyOnDay()
    return
def FamilyOnDay():
    ## 本服时间自己触发的onday逻辑
    if not GameWorld.IsMainServer() or DBFamily.IsFamilyCross():
        # 非游戏服或本服已跨服互通了不处理
        return
    
    __doFamilyOnDay()
    DBFamily.CheckCrossFamilyTransData()
    return
def __doFamilyOnDay():
    familyManager = DBDataMgr.GetFamilyMgr()
    for i in range(0, familyManager.GetCount()):
        family = familyManager.GetAt(i)
        familyID = family.GetID()
        PlayerFamilyZhenbaoge.OnDay(family)
        PlayerFamilyTaofa.OnDay(family)
        for index in xrange(family.GetCount()):
            member = family.GetAt(index)
            # 重置成员日信息
            member.SetContribDay(0)
            member.SetDonateCntDay(0)
    for zoneID in familyManager.GetZoneIDListThisServer():
        zoneMgr = familyManager.GetZoneFamilyMgr(zoneID)
        for i in range(0, zoneMgr.GetCount()):
            family = zoneMgr.GetAt(i)
            familyID = family.GetID()
            
        Broadcast_FamilyChange(familyID, FamilyChangeType_OnDay)
            PlayerFamilyZhenbaoge.OnDay(family)
            PlayerFamilyTaofa.OnDay(family)
            for index in xrange(family.GetCount()):
                member = family.GetAt(index)
                # 重置成员日信息
                member.SetContribDay(0)
                member.SetDonateCntDay(0)
            Broadcast_FamilyInfo(familyID)
    return
def PlayerCrossCenterOnDay(curPlayer):
    ## 处理跨服中心同步的onday玩家事件
    if not DBFamily.IsFamilyCross():
        GameWorld.DebugLog("公会还属于本服,不处理成员跨服过天", curPlayer.GetPlayerID())
        return
    __doPlayerOnDay(curPlayer)
    return
def PlayerOnDay(curPlayer):
    if not GameFuncComm.GetFuncCanUse(curPlayer, ShareDefine.GameFuncID_Family):
    ## 本服时间的onday玩家事件
    if DBFamily.IsFamilyCross():
        GameWorld.DebugLog("公会已经跨服了,不处理成员本服过天", curPlayer.GetPlayerID())
        return
    ResetDailyDonateCnt(curPlayer)
    __doPlayerOnDay(curPlayer)
    return
def __doPlayerOnDay(curPlayer):
    PlayerFamilyZhenbaoge.PlayerOnDay(curPlayer)
    PlayerFamilyTaofa.PlayerOnDay(curPlayer)
    Do_MapServer_PlayerOnDay(curPlayer)
    return
def OnPlayerLogin(curPlayer, tick):
    if not GameFuncComm.GetFuncCanUse(curPlayer, ShareDefine.GameFuncID_Family):
    Do_MapServer_PlayerLogin(curPlayer)
    if DBFamily.IsFamilyCross():
        #公会已跨服不处理,由所属跨服服务器处理成员登录逻辑
        return
    PlayerLoginRefreshFamily(curPlayer, tick)
    Sync_RequestAddFamilyInfo(curPlayer, False)
    SyncDonateCntInfo(curPlayer)
    PlayerFamilyZhenbaoge.OnPlayerLogin(curPlayer)
    PlayerFamilyTaofa.OnPlayerLogin(curPlayer)
    crossPlayer = CrossPlayer.GetCrossPlayerMgr().FindCrossPlayer(curPlayer.GetPlayerID())
    OnCrossPlayerLogin(crossPlayer)
    return
def OnCrossPlayerLogin(crossPlayer):
    ## 玩家上线,游戏服跨服通用,流程上当做以前GameServer处理公会一样,处理后再通知地图(现在的游戏服)
    PlayerLoginRefreshFamily(crossPlayer)
    Sync_RequestAddFamilyInfo(crossPlayer, False)
    #PlayerFamilyTaofa.OnPlayerLogin(curPlayer) 讨伐待修改
    return
def OnPlayerLogout(curPlayer):
    playerID = curPlayer.GetPlayerID()
    familyID = curPlayer.GetFamilyID()
    if DBFamily.IsFamilyCross():
        #公会已跨服不处理,由所属跨服服务器处理成员登录逻辑
        return
    crossPlayer = CrossPlayer.GetCrossPlayerMgr().FindCrossPlayer(curPlayer.GetPlayerID())
    OnCrossPlayerLogout(crossPlayer)
    return
def OnCrossPlayerLogout(crossPlayer):
    ## 玩家下线,游戏服跨服通用,流程上当做以前GameServer处理公会一样,处理后再通知地图(现在的游戏服)
    playerID = crossPlayer.GetPlayerID()
    familyID = crossPlayer.GetFamilyID()
    if not familyID:
        return
    familyMgr = DBDataMgr.GetFamilyMgr()
@@ -127,24 +188,62 @@
    if not curMember:
        return
    curMember.SetOffTime(int(time.time()))
    #XW_JZ_LeaguerLeaveline  <n color="0,190,255">{%S1%}</n><n color="255,255,0">下线了!</n>    25  -   -
    #NotifyAllFamilyMemberMsg(familyID, "XW_JZ_LeaguerLeaveline", [curPlayer.GetPlayerName()])
    Broadcast_FamilyChange(familyID, FamilyChangeType_MemLogout, excludeIDList=[playerID])
    # 通知成员下线
    Broadcast_FamilyInfo(familyID, changeMemIDList=[playerID])
    return
def OnWeekEx(curPlayer):
    #重置
def SendToFamilyMapPlayer(crossPlayer, doType, doData):
    playerID = crossPlayer.GetPlayerID()
    dataMsg = {"doType":doType, "doData":doData}
    if GameWorld.IsCrossServer():
        CrossMsg.SendToClientServer(ShareDefine.C2S_FamilyMapPlayer, dataMsg, [crossPlayer.GetMainServerID()], playerID)
    else:
        C2S_FamilyMapPlayer(dataMsg, playerID)
    return
def PlayerLoginRefreshFamily(curPlayer, tick):
def MapServer_FamilyRefresh(crossPlayer, familyID, isVoluntarily=0):
    ''' 相当于GameServer调用 curPlayer.MapServer_FamilyRefresh()
    '''
    playerID = crossPlayer.GetPlayerID()
    FmLV = 0 # ְλ
    FamilyLV = 0 # 公会等级
    FamilyName = ""
    EmblemID, EmblemWord = 0, ""
    if familyID:
        familyMgr = DBDataMgr.GetFamilyMgr()
        curFamily = familyMgr.FindFamily(familyID)
        if curFamily:
            FamilyLV = curFamily.GetLV()
            FamilyName = curFamily.GetName()
            EmblemID = curFamily.GetEmblemID()
            EmblemWord = curFamily.GetEmblemWord()
            member = curFamily.FindMember(playerID)
            if member:
                FmLV = member.GetFmLV()
        else:
            familyID = 0
    crossPlayer.SetFamilyID(familyID)
    doData = {"FamilyID":familyID}
    if familyID:
        doData.update({"FmLV":FmLV, "FamilyLV":FamilyLV, "FamilyName":FamilyName, "EmblemID":EmblemID, "EmblemWord":EmblemWord})
    if isVoluntarily:
        doData["isVoluntarily"] = 1
    SendToFamilyMapPlayer(crossPlayer, "FamilyRefresh", doData)
    return
def PlayerLoginRefreshFamily(crossPlayer):
    ## 玩家登录时刷新家族
    
    playerID = curPlayer.GetPlayerID()
    playerID = crossPlayer.GetPlayerID()
    familyMgr = DBDataMgr.GetFamilyMgr()
    refreshFamilyID = familyMgr.GetPlayerFamilyID(playerID)
    MapServer_FamilyRefresh(curPlayer, refreshFamilyID)
    familyID = curPlayer.GetFamilyID()
    GameWorld.DebugLog("PlayerLoginRefreshFamily playerID=%s,refreshFamilyID=%s" % (playerID, refreshFamilyID))
    crossPlayer.SetFamilyID(refreshFamilyID)
    MapServer_FamilyRefresh(crossPlayer, refreshFamilyID)
    familyID = refreshFamilyID
    if not familyID:
        return
    
@@ -155,55 +254,163 @@
    if not curMember:
        return
    curMember.SetOffTime(0) # 在线0,脱机1,>1离线时间
    curMember.RefreshMember(curPlayer)
    Sync_FamilyInfo(curPlayer)
    SendFamilyActionInfo(curPlayer, familyID, ShareDefine.Def_ActionType_FamilyData)
    curMember.RefreshMemberByID(playerID)
    Sync_FamilyInfo(crossPlayer) # 给自己同步完整的
    # 广播成员在线
    Broadcast_FamilyInfo(familyID, changeMemIDList=[playerID], excludeIDList=[playerID])
    
    #通知招人
    if GetFamilyMemberHasPow(curMember, FamilyPowerID_Call):
        NetPackCommon.SendFakePack(curPlayer, GetPack_FamilyReqJoinInfo(familyID))
    #XW_JZ_LeaguerOnline <n color="0,190,255">{%S1%}</n><n color="255,255,0">上线了!</n>    25  -   -
    #NotifyAllFamilyMemberMsg(familyID, "XW_JZ_LeaguerOnline", [curPlayer.GetName()], [playerID])
    Broadcast_FamilyChange(familyID, FamilyChangeType_MemLogin, excludeIDList=[playerID])
        CrossPlayer.SendFakePack(crossPlayer, GetPack_FamilyReqJoinInfo(familyID))
    # 盟主上线处理
    if curMember.GetFmLV() == IPY_PlayerDefine.fmlLeader:
        OnFamilyLeaderLogin(curPlayer)
    #if curMember.GetFmLV() == IPY_PlayerDefine.fmlLeader:
    #    OnFamilyLeaderLogin(curPlayer)
    return
def OnFamilyLeaderLogin(curPlayer):
    ## 盟主登录额外处理
    return
def OnMinute():
    #战力刷新在DBFamily.OnMinute
    PlayerFamilyEmblem.CheckExpireEmblem()
    return
def RefreshFamilyMember(curPlayer):
def RefreshFamilyMember(crossPlayer):
    ## 玩家成员相关属性变更时同步更新家族成员信息
    familyID = curPlayer.GetFamilyID()
    if GameWorld.IsCrossServer():
        pass
    elif not GameWorld.IsMainServer() or DBFamily.IsFamilyCross():
        return
    familyID = crossPlayer.GetFamilyID()
    if not familyID:
        return
    familyMgr = DBDataMgr.GetFamilyMgr()
    family = familyMgr.FindFamily(familyID)
    if not family:
        return
    playerID = curPlayer.GetPlayerID()
    playerID = crossPlayer.GetPlayerID()
    member = family.FindMember(playerID)
    if not member:
        return
    member.RefreshMember(curPlayer)
    member.RefreshMemberByID(playerID)
    return
def GetRenameTime(dataAction): return dataAction.GetValue1()
def SetRenameTime(dataAction, setTime): dataAction.SetValue1(setTime)
def GetFamilyDataAction(familyID):
    ## 家族额外数据存储的行为数据,可以视为Family公共数据的一个扩展
    action = DBDataMgr.GetFamilyActionMgr().GetFamilyAction(familyID, ShareDefine.Def_ActionType_FamilyData)
    return action.GetOneAction(True)
def FamilyPyPackForwarding(curPlayer, clientData, tick, funcName, needResult=False, reqCD=0.5, reqDataEx=None):
    ## 玩家请求公会功能包转发处理
    # @needResult: 转发跨服处理完毕是否需要回复状态,一般有消耗货币、物品相关的都要回复,且设置一个较长请求cd,防止重复处理
    # @param reqCD: 转发跨服请求cd,秒,支持小数
    playerID = curPlayer.GetPlayerID()
    crossServerID = DBDataMgr.GetFamilyMgr().GetCurCrossServerID()
    if crossServerID == -2:
        PlayerControl.NotifyCode(curPlayer, "FamilyInTransData")
        return
    if crossServerID < 0:
        GameWorld.ErrLog("公会功能异常! crossServerID=%s" % crossServerID)
        return
    # 本服处理
    if crossServerID == 0 or crossServerID == GameWorld.GetGameWorld().GetServerID():
        isOK = CallPyPackFunc(playerID, clientData, funcName, reqDataEx=reqDataEx)
        if needResult:
            __doFamilyPyPackRet(curPlayer, clientData, funcName, isOK)
        return
    # 转发请求CD验证
    if reqCD:
        reqTick = curPlayer.GetDictByKey("FamilyPyPackForwardingTick")
        if reqTick and (tick - reqTick) < reqCD * 1000:
            PlayerControl.NotifyCode(curPlayer, "RequestLater")
            return
        curPlayer.SetDict("FamilyPyPackForwardingTick", tick)
    # 剩下的就是大于0
    dataMsg = {"funcName":funcName}
    if clientData:
        dataMsg["packBuff"] = clientData.GetBuffer()
    if reqDataEx:
        dataMsg["reqDataEx"] = reqDataEx
    if needResult:
        dataMsg["needResult"] = 1
    CrossMsg.SendToCrossServer(ShareDefine.S2C_FamilyPyPack, dataMsg, [crossServerID], playerID)
    return
def S2C_FamilyPyPack(dataMsg, fromServerID, playerID):
    funcName = dataMsg["funcName"]
    packBuff = dataMsg.get("packBuff")
    reqDataEx = dataMsg.get("reqDataEx")
    clientData = None
    if packBuff:
        clientData = NetPackCommon.ReadRecPyPackData(packBuff)
        if not clientData:
            return
    isOK = CallPyPackFunc(playerID, clientData, funcName, fromServerID, reqDataEx)
    # 处理结束回复,无论成功与否
    if "needResult" in dataMsg:
        dataMsg.pop("needResult")
        dataMsg["isOK"] = isOK
        CrossMsg.SendToClientServer(ShareDefine.C2S_FamilyPyPackRet, dataMsg, [fromServerID], playerID)
    return
def C2S_FamilyPyPackRet(dataMsg, playerID):
    curPlayer = GameWorld.GetPlayerManager().FindPlayerByID(playerID)
    if not curPlayer:
        return
    curPlayer.SetDict("FamilyPyPackForwardingTick", 0)
    funcName = dataMsg["funcName"]
    packBuff = dataMsg.get("packBuff")
    clientData = None
    if packBuff:
        clientData = NetPackCommon.ReadRecPyPackData(packBuff)
        if not clientData:
            return
    isOK = dataMsg.get("isOK")
    __doFamilyPyPackRet(curPlayer, clientData, funcName, isOK)
    return
def CallPyPackFunc(playerID, clientData, funcName, fromServerID=0, reqDataEx=None):
    crossPlayer = CrossPlayer.GetCrossPlayerMgr().FindCrossPlayer(playerID)
    if not crossPlayer:
        GameWorld.ErrLog("找不到该CrossPlayer! playerID=%s" % playerID, playerID)
        return
    callFunc = GetCallFunc(funcName)
    if not callFunc:
        GameWorld.ErrLog("公会封包功能函数名不存在! %s" % funcName)
        return
    # 执行函数
    tick = GameWorld.GetGameWorld().GetTick()
    isOK = callFunc(crossPlayer, clientData, tick, fromServerID, reqDataEx)
    return isOK
def __doFamilyPyPackRet(curPlayer, clientData, funcName, isOK):
    ## 处理后额外处理,如成就、任务等
    funcName = "%s_Ret" % funcName
    callFunc = GetCallFunc(funcName)
    if not callFunc:
        # 结果额外处理函数允许不一定需要,根据功能自行决定
        return
    callFunc(curPlayer, clientData, isOK)
    return
def GetCallFunc(funcName):
    callFunc = None
    if "." in funcName:
        # 分割字符串,最后一个部分是函数名
        parts = funcName.split('.')
        if len(parts) == 2:
            moduleName, func_name = parts
            # 导入模块
            module = __import__(moduleName)
            # 获取函数
            if hasattr(module, func_name):
                func = getattr(module, func_name)
                if callable(func):
                    callFunc = func
    else:
        gDict = globals()
        if funcName in gDict and callable(gDict[funcName]):
            # 获取函数对象
            callFunc = gDict[funcName]
    return callFunc
#// A6 04 创建家族 #tagCMCreateFamily
#
@@ -211,40 +418,53 @@
#{
#    tagHead        Head;
#    char        Name[33];
#    WORD        EmblemID; //选择徽章ID,解锁仙盟等级为1级的均为可选ID
#    WORD        EmblemID; //选择徽章ID,解锁公会等级为1级的均为可选ID
#    char        EmblemWord[3];    //徽章文字
#};
def OnCreateFamily(index, clientPack, tick):
def OnCreateFamily(index, clientData, tick):
    curPlayer = GameWorld.GetPlayerManager().GetPlayerByIndex(index)
    inputName = clientPack.Name
    emblemID = clientPack.EmblemID
    emblemWord = clientPack.EmblemWord
    inputName = clientData.Name
    
    if not GameFuncComm.GetFuncCanUse(curPlayer, ShareDefine.GameFuncID_Family):
    GameWorld.DebugLog("OnCreateFamily: %s" % GameWorld.CodeToGbk(inputName))
    # 通用的前置验证,直接游戏服处理即可
    if not CheckInputFamilyName(curPlayer, inputName):
        return
    needMoney = IpyGameDataPY.GetFuncCfg("CreateFamily", 1)
    moneyType = IpyGameDataPY.GetFuncCfg("CreateFamily", 2)
    if moneyType and needMoney:
        if not PlayerControl.HaveMoney(curPlayer, moneyType, needMoney):
            return
        
    playerID = curPlayer.GetPlayerID()
    playerFamilyID = curPlayer.GetFamilyID()
    FamilyPyPackForwarding(curPlayer, clientData, tick, "__OnCreateFamily", True, 20)
    return
def __OnCreateFamily(crossPlayer, clientData, tick, fromServerID=0, reqDataEx=None):
    inputName = clientData.Name
    emblemID = clientData.EmblemID
    emblemWord = clientData.EmblemWord
    GameWorld.DebugLog("__OnCreateFamily: %s" % GameWorld.CodeToGbk(inputName))
    fullFamilyName = inputName
    playerID = crossPlayer.GetPlayerID()
    playerFamilyID = crossPlayer.GetFamilyID()
    familyMgr = DBDataMgr.GetFamilyMgr()
    if playerFamilyID:
        curFamily = familyMgr.FindFamily(playerFamilyID)
        if curFamily:
            if curFamily.FindMember(playerID):
                #玩家已经有家族, 创建失败
                PlayerControl.NotifyCode(curPlayer, "GeRen_chenxin_85890")
                CrossPlayer.NotifyCode(crossPlayer, "GeRen_chenxin_85890")
                return
            
    fullFamilyName = CheckInputFamilyName(curPlayer, inputName)
    if not fullFamilyName:
    # 验证重名
    if CheckFamilyNameExists(crossPlayer, fullFamilyName, fromServerID):
        return
    
    needMoney = IpyGameDataPY.GetFuncCfg("CreateFamily", 1)
    moneyType = IpyGameDataPY.GetFuncCfg("CreateFamily", 2)
    if moneyType and needMoney:
        if not PlayerControl.PayMoney(curPlayer, moneyType, needMoney, "CreateFamily"):
            return
    serverID = GameWorld.GetPlayerServerID(curPlayer)
    serverID = crossPlayer.GetServerID()
    curFamily = familyMgr.AddFamily(fullFamilyName, serverID)
    if curFamily == None:
        GameWorld.ErrLog("创建家族失败", playerID)
@@ -254,29 +474,45 @@
    emblemIDList = PlayerFamilyEmblem.GetDefaultFamilyEmblemIDList()
    if not emblemID or emblemID not in emblemIDList:
        emblemID = random.choice(emblemIDList) # 从默认徽章中随机选择一个
    GameWorld.Log("创建仙盟: familyID=%s,playerID=%s,emblemID=%s" % (newFamilyID, playerID, emblemID))
    GameWorld.Log("创建公会: familyID=%s,playerID=%s,emblemID=%s,serverID=%s" % (newFamilyID, playerID, emblemID, serverID))
    curFamily.SetEmblemID(emblemID)
    curFamily.SetEmblemWord(emblemWord)
    
    # 扣除消耗
    needMoney = IpyGameDataPY.GetFuncCfg("CreateFamily", 1)
    moneyType = IpyGameDataPY.GetFuncCfg("CreateFamily", 2)
    if moneyType and needMoney:
        CrossPlayer.CostPlayerResources(crossPlayer, "CreateFamily", costMoneyDict={moneyType:needMoney})
    #-设置家族成员属性
    DoPlayerJionFamily(curFamily, playerID, curPlayer, IPY_PlayerDefine.fmlLeader)
    familyMgr.Sort()
    DoPlayerJionFamily(curFamily, playerID, crossPlayer, IPY_PlayerDefine.fmlLeader)
    zoneMgr = familyMgr.GetZoneFamilyMgrByFamilyID(newFamilyID)
    zoneMgr and zoneMgr.Sort()
    
    #XW_JZ_EstablishSud <n color="255,255,0">恭喜您,家族建立成功!</n>    25  -   -
    PlayerControl.NotifyCode(curPlayer, "XW_JZ_EstablishSud")
    PlayerControl.WorldNotify(0, "jiazu_liubo_671654", [curPlayer.GetName(), fullFamilyName, newFamilyID])
    #CrossPlayer.NotifyCode(crossPlayer, "XW_JZ_EstablishSud")
    #PlayerControl.WorldNotify(0, "jiazu_liubo_671654", [curPlayer.GetName(), fullFamilyName, newFamilyID])
    PlayerFamilyZhenbaoge.OnZhenbaogeReset(curFamily)
    return
    return True
def CheckInputFamilyName(curPlayer, inputName):
    '''检查玩家输入的仙盟名是否合法,建盟、改名通用
    @return: None-不合法;非空-合法的仙盟全名
    '''检查玩家输入的公会名是否合法,建盟、改名通用
    【注】该函数仅在游戏服验证名字的通用合法性,如长度、敏感词等,公会名重名请在公会所在数据服验证
    @return: None-不合法;非空-合法的公会全名
    '''
    #C++过滤空格
    familyName = GameWorld.GetGameWorld().GetCharTrim(inputName)
    fullFamilyName = GetFamilyFullName(curPlayer, familyName)
    if not fullFamilyName:
        GameWorld.ErrLog("家族全名异常!", curPlayer.GetPlayerID())
    serverID = GameWorld.GetPlayerServerID(curPlayer)
    maxServerID = IpyGameDataPY.GetFuncCfg("FamilyNameFormat", 3)
    if serverID > maxServerID or serverID <= 0:
        GameWorld.ErrLog("公会全名 serverID=%s error! maxServerID=%s, check FamilyNameFormat.txt" % (serverID, maxServerID))
        return
    maxLen = IpyGameDataPY.GetFuncCfg("FamilyNameFormat", 2)
    if len(familyName) > maxLen:
        GameWorld.ErrLog("公会全名 familyName=%s, len=%s > %s, check FamilyNameFormat.txt" % (familyName, len(familyName), maxLen))
        PlayerControl.NotifyCode(curPlayer, "NameLenLimit", [maxLen / 3, maxLen])
        return
    
    if DirtyList.IsWordForbidden(familyName):
@@ -288,49 +524,30 @@
        PlayerControl.NotifyCode(curPlayer, "NameLenLimit", [Def_CreatFamily_MaxStr / 3, Def_CreatFamily_MaxStr])
        return
    
    return True
def CheckFamilyNameExists(crossPlayer, familyName, fromServerID=0):
    ## 重名在数据所在服务器验证
    familyMgr = DBDataMgr.GetFamilyMgr()
    if familyMgr.FindFamilyByName(fullFamilyName):
    zoneID = familyMgr.GetZoneIDInThisServer(fromServerID)
    if zoneID < 0:
        GameWorld.ErrLog("验证公会重名时分区异常也视为重名")
        return True
    zoneMgr = familyMgr.GetZoneFamilyMgr(zoneID)
    # 不同分区暂时允许重名
    if zoneMgr.FindFamilyByName(familyName):
        #XW_JZ_EstablishErr_Name    <n color="255,255,0">对不起,您输入的家族名已存在,建立家族失败!</n> 25  -   -
        PlayerControl.NotifyCode(curPlayer, "NameExists")
        return
    return fullFamilyName
        CrossPlayer.NotifyCode(crossPlayer, "NameExists")
        return True
    return False
## 获取家族全名
def GetFamilyFullName(curPlayer, familyName):
    serverID = GameWorld.GetPlayerServerID(curPlayer)
    maxServerID = IpyGameDataPY.GetFuncCfg("FamilyNameFormat", 3)
    if serverID > maxServerID or serverID <= 0:
        GameWorld.ErrLog("仙盟全名 serverID=%s error! maxServerID=%s, check FamilyNameFormat.txt" % (serverID, maxServerID))
        return ""
    specServerDict = IpyGameDataPY.GetFuncEvalCfg("FamilyNameFormat", 4, {})
    nameFormatInfo = GameWorld.GetDictValueByRangeKey(specServerDict, serverID)
    if not nameFormatInfo:
        nameFormatInfo = IpyGameDataPY.GetFuncEvalCfg("FamilyNameFormat", 1)
    if not nameFormatInfo:
        return ""
    nameFormat = nameFormatInfo[0]
    paramList = [eval(pName) for pName in nameFormatInfo[1:]]
    fullName = nameFormat % tuple(paramList)
    maxLen = IpyGameDataPY.GetFuncCfg("FamilyNameFormat", 2)
    if len(fullName) > maxLen:
        GameWorld.ErrLog("仙盟全名 familyName=%s,全名=%s len=%s > %s, check FamilyNameFormat.txt" % (familyName, fullName, len(fullName), maxLen))
        PlayerControl.NotifyCode(curPlayer, "NameLenLimit", [maxLen / 3, maxLen])
        return ""
    return fullName
def DoPlayerJionFamily(family, playerID, jionPlayer, jionFamilySetLv=IPY_PlayerDefine.fmlMember, broadcastFamilyChange=True):
def DoPlayerJionFamily(family, playerID, crossPlayer, jionFamilySetLv=IPY_PlayerDefine.fmlMember, broadcastFamilyChange=True):
    '''加入家族,支持离线玩家加入
    @param jionPlayer: 如果是离线玩家则为None
    @param crossPlayer: 如果是离线玩家则为None
    '''
    familyMgr = DBDataMgr.GetFamilyMgr()
    if isinstance(family, int):
        familyID = family
        familyMgr = DBDataMgr.GetFamilyMgr()
        curFamily = familyMgr.FindFamily(familyID)
    else:
        curFamily = family
@@ -341,37 +558,32 @@
    familyID = curFamily.GetID()
    member = curFamily.AddMember(playerID)
    member.SetFmLV(jionFamilySetLv)
    if jionPlayer:
        member.RefreshMember(jionPlayer)
    else:
        member.RefreshMemberByID(playerID)
    member.RefreshMemberByID(playerID)
    curFamily.SetFightPowerTotal(curFamily.GetFightPowerTotal() + member.GetFightPowerTotal())
    
    if jionFamilySetLv == IPY_PlayerDefine.fmlLeader:
        curFamily.SetLeaderID(playerID)
        
    #设置玩家身上保存的家族信息
    if jionPlayer:
        MapServer_FamilyRefresh(jionPlayer, familyID)
        Sync_FamilyInfo(jionPlayer)
    if broadcastFamilyChange:
        Broadcast_FamilyInfo(familyID, changeMemIDList=[playerID])
        
    if jionFamilySetLv != IPY_PlayerDefine.fmlLeader:
        if broadcastFamilyChange:
            Broadcast_FamilyChange(familyID, FamilyChangeType_MemJoin, excludeIDList=[playerID])
    familyMgr.DelPlayerReqJoinFamilyIDAll(playerID)
    #设置玩家身上保存的家族信息
    if crossPlayer:
        MapServer_FamilyRefresh(crossPlayer, familyID)
        Sync_RequestAddFamilyInfo(crossPlayer)
        PlayerFamilyTaofa.OnCrossPlayerEnterFamily(crossPlayer)
    #if jionFamilySetLv != IPY_PlayerDefine.fmlLeader:
        #通知所有家族成员, 这个人加入了家族
        #NotifyAllFamilyMemberMsg(familyID, "XW_JZ_EnterFamily", [member.GetPlayerName()], excludeIDList=[playerID])
        if jionPlayer:
            PlayerControl.NotifyCode(jionPlayer, 'XW_JZ_EnterFamilyInfo', [family.GetName()])
        #if jionPlayer:
        #    PlayerControl.NotifyCode(jionPlayer, 'XW_JZ_EnterFamilyInfo', [family.GetName()])
            
    #记录加入事件
    tick = GameWorld.GetGameWorld().GetTick()
    AddFamilyActionNote(member.GetPlayerName(), familyID, ShareDefine.Def_ActionType_FamilyEvent, 
                        [ShareDefine.Def_FamilyActionEvent_MemberChange, ShareDefine.Def_FamilyMemberChange_Join, jionFamilySetLv, 0], tick)
    #玩家缓存
    #PlayerViewCache.OnPlayerFamilyChange(jionPlayer.GetPlayerID(), curFamily.GetID(), curFamily.GetName())
    #SetMemberFightPower(familyMember, PlayerControl.GetFightPower(jionPlayer))
    #GetFamilyMgr().AddFamilyIDToFightPowerChangeList(curFamily.GetID(), jionPlayer.GetPlayerID())
    return
def AddFamilyActionNote(curName, familyID, actionType, actionDataList, tick, isClearNone=True, useData=""):
@@ -396,31 +608,1382 @@
            setFunc(value)
    return True
def MapServer_FamilyRefresh(curPlayer, refreshFamilyID, isVoluntarily=0):
    ''' 相当于GameServer调用 curPlayer.MapServer_FamilyRefresh()
    @param familyID: 玩家更新的familyID
    @param isVoluntarily: 是否自愿离开的,仅离开刷新时有效
    '''
def GetFamilyMemberHasPow(member, powerID):
    ## 公会成员是否有该权限
    powerDict = IpyGameDataPY.GetFuncEvalCfg("FamilyPower", 1, {})
    if str(powerID) not in powerDict:
        return False
    needMemberLV = powerDict[str(powerID)]
    return member.GetFmLV() >= needMemberLV
def Sync_FamilyInfo(crossPlayer, syncMemIDList=None, isSyncMem=True):
    ## 通知指定玩家 // A5 20 玩家家族信息 #tagMCRoleFamilyInfo
    familyID = crossPlayer.GetFamilyID()
    if not familyID:
        return
    clientPack = GetPack_FamilyInfo(familyID, syncMemIDList, isSyncMem)
    CrossPlayer.SendFakePack(crossPlayer, clientPack)
    return
def Broadcast_FamilyInfo(familyID, changeMemIDList=None, isSyncMem=True, excludeIDList=None):
    ## 广播给公会成员 // A5 20 玩家家族信息 #tagMCRoleFamilyInfo
    # @param changeMemIDList: 指定仅通知哪些变化成员信息,差异更新通知
    # @param isSyncMem: 是否需要通知成员信息
    clientPack = GetPack_FamilyInfo(familyID, changeMemIDList, isSyncMem)
    CrossPlayer.SendFakePackByFamily(familyID, clientPack, excludeIDList)
    return
def GetPack_FamilyInfo(familyID, changeMemIDList=None, isSyncMem=True):
    familyMgr = DBDataMgr.GetFamilyMgr()
    curFamily = familyMgr.FindFamily(familyID)
    if not curFamily:
        return
    
    clientPack = ChPyNetSendPack.tagMCRoleFamilyInfo()
    clientPack.FamilyID = familyID
    clientPack.FamilyName = curFamily.GetName()
    clientPack.FamilyLV = curFamily.GetLV()
    clientPack.FamilyLVExp = curFamily.GetExp()
    clientPack.JoinReview = curFamily.GetJoinReview()
    clientPack.JoinLVMin = curFamily.GetJoinLVMin()
    clientPack.ServerID = curFamily.GetServerID()
    clientPack.EmblemID = curFamily.GetEmblemID()
    clientPack.EmblemWord = curFamily.GetEmblemWord()
    clientPack.FightPower = curFamily.GetFightPower()
    clientPack.FightPowerEx = curFamily.GetFightPowerEx()
    clientPack.Broadcast = curFamily.GetBroadcast()
    clientPack.BroadcastLen = len(clientPack.Broadcast)
    clientPack.LeaderID = curFamily.GetLeaderID()
    clientPack.MemberList = []
    # ---------------
    # 测试用,同步全部,等前端同步修改后删除
    #isSyncMem = True
    #changeMemIDList = []
    # ---------------
    if isSyncMem:
        for index in xrange(curFamily.GetCount()):
            member = curFamily.GetAt(index)
            memID = member.GetPlayerID()
            if changeMemIDList and memID not in changeMemIDList:
                continue
            memInfo = ChPyNetSendPack.tagMCRoleFamilyMember()
            memInfo.PlayerID = member.GetPlayerID()
            memInfo.JoinTime = member.GetJoinTime()
            memInfo.Name = member.GetPlayerName()
            memInfo.NameLen = len(memInfo.Name)
            memInfo.LV = member.GetLV()
            memInfo.Job = member.GetJob()
            memInfo.RealmLV = member.GetRealmLV()
            memInfo.Face = member.GetFace()
            memInfo.FacePic = member.GetFacePic()
            memInfo.TitleID = member.GetTitleID()
            memInfo.FightPower = member.GetFightPower()
            memInfo.FightPowerEx = member.GetFightPowerEx()
            memInfo.FmLV = member.GetFmLV()
            memInfo.ServerID = member.GetServerID()
            memInfo.ContribTotal = member.GetContribTotal()
            memInfo.ContribDay = member.GetContribDay()
            memInfo.DonateCntTotal = member.GetDonateCntTotal()
            memInfo.DonateCntDay = member.GetDonateCntDay()
            memInfo.OffTime = member.GetOffTime()
            clientPack.MemberList.append(memInfo)
    clientPack.MemberCount = len(clientPack.MemberList)
    clientPack.Extra1 = curFamily.GetExtra1()
    return clientPack
def GetPack_FamilyDel(delPlayerID, playerName, delType=0):
    # @param delType: 0-踢出;1-主动退出
    clientPack = ChPyNetSendPack.tagSCFamilyMemDel()
    clientPack.Type = delType
    clientPack.PlayerID = delPlayerID
    clientPack.Name = playerName
    clientPack.NameLen = len(clientPack.Name)
    return clientPack
#// A6 01 向玩家申请加入家族 #tagCMRequestJoinFamilyByPlayer
#
#struct    tagCMRequestJoinFamilyByPlayer
#{
#    tagHead        Head;
#    DWORD        TagPlayerID;    //目标家族玩家ID
#};
def OnRequestJoinFamilyByPlayer(index, clientData, tick):
    #屏蔽,默认只使用 A6 02
    #curPlayer = GameWorld.GetPlayerManager().GetPlayerByIndex(index)
    #tagPlayerID = clientData.TagPlayerID
    #tagPlayer = GameWorld.GetPlayerManager().FindPlayerByID(tagPlayerID)
    #if not tagPlayer:
    #    GameWorld.DebugLog("对方不在线! tagPlayerID=%s" % tagPlayerID)
    #    return
    #tagFamilyID = tagPlayer.GetFamilyID()
    #if tagFamilyID <= 0:
    #    GameWorld.DebugLog("对方没有家族! tagPlayerID=%s" % tagPlayerID)
    #    return
    #RequestJoinTagFamily(curPlayer, tagFamilyID)
    return
#// A6 02 申请加入家族#tagCMRequesJoinFamily
#
#struct    tagCMRequesJoinFamily
#{
#    tagHead        Head;
#    BYTE        Type;        //申请类型,0-申请;1-撤销
#    DWORD        TagFamilyID;    //目标家族ID,申请时为0代表一键申请家族任意家族
#};
def OnRequesJoinFamily(index, clientData, tick):
    curPlayer = GameWorld.GetPlayerManager().GetPlayerByIndex(index)
    requestType = clientData.Type
    if requestType == 0:
        if CheckInJoinCD(curPlayer):
            return
    FamilyPyPackForwarding(curPlayer, clientData, tick, "__OnRequesJoinFamily", True)
    return
def __OnRequesJoinFamily(crossPlayer, clientData, tick, fromServerID=0, reqDataEx=None):
    tagFamilyID = clientData.TagFamilyID  # 申请进入的家族ID
    requestType = clientData.Type   # 申请类型(申请/撤销)
    GameWorld.DebugLog("__OnRequesJoinFamily: tagFamilyID=%s,requestType=%s" % (tagFamilyID, requestType))
    # 申请加入
    if requestType == 0:
        if not tagFamilyID:
            AutoJoinFamily(crossPlayer)
        else:
            RequestJoinTagFamily(crossPlayer, tagFamilyID)
    # 撤销申请
    elif requestType == 1:
        CancelJoinTagFamily(crossPlayer, tagFamilyID)
    return True
def __OnRequesJoinFamily_Ret(curPlayer, clientData, isOK):
    requestType = clientData.Type   # 申请类型(申请/撤销)
    # 申请加入
    if requestType == 0:
        PlayerTask.AddTaskValue(curPlayer, ChConfig.TaskType_ReqOrJoinFamily, 1)
    return
def CheckInJoinCD(curPlayer):
    ## 检查是否加入公会CD中
    leaveFamilyTime = PlayerControl.GetLeaveFamilyTimeEx(curPlayer)
    if not leaveFamilyTime:
        return False
    leaveCnt, kickedCnt, lastVoluntarily = PlayerControl.GetLeaveFamilyInfo(curPlayer)
    joinCDMinute = 0
    if lastVoluntarily:
        if leaveCnt <= 0:
            return False
        joinCDMinuteList = IpyGameDataPY.GetFuncEvalCfg("FamilyLeave", 1)
        if joinCDMinuteList:
            joinCDMinute = joinCDMinuteList[leaveCnt - 1] if len(joinCDMinuteList) >= leaveCnt else joinCDMinuteList[-1]
    else:
        if kickedCnt <= 0:
            return False
        joinCDMinuteList = IpyGameDataPY.GetFuncEvalCfg("FamilyLeave", 2)
        if joinCDMinuteList:
            joinCDMinute = joinCDMinuteList[kickedCnt - 1] if len(joinCDMinuteList) >= kickedCnt else joinCDMinuteList[-1]
    if joinCDMinute:
        cdTimes = joinCDMinute * 60
        passTimes = int(time.time()) - leaveFamilyTime
        if passTimes < cdTimes:
            GameWorld.DebugLog("加入公会CD中: leaveCnt=%s,kickedCnt=%s,lastVoluntarily=%s,leaveFamilyTime=%s(%s),passTimes=%s < %s"
                   % (leaveCnt, kickedCnt, lastVoluntarily, leaveFamilyTime, GameWorld.ChangeTimeNumToStr(leaveFamilyTime), passTimes, cdTimes))
            return True
    return False
def AutoJoinFamily(crossPlayer):
    if crossPlayer.GetFamilyID():
        return
    playerID = crossPlayer.GetPlayerID()
    realmLV = crossPlayer.GetRealmLV()
    GameWorld.DebugLog("玩家一键自动加入家族! realmLV=%s" % realmLV, playerID)
    #if CheckInJoinCD(curPlayer):
    #    return
    mainServerID = crossPlayer.GetMainServerID()
    familyMgr = DBDataMgr.GetFamilyMgr()
    zoneID = familyMgr.GetZoneIDInThisServer(mainServerID)
    if zoneID < 0:
        return
    zoneMgr = familyMgr.GetZoneFamilyMgr(zoneID)
    indexList = range(zoneMgr.GetCount())
    random.shuffle(indexList) #打乱顺序
    for index in indexList:
        family = zoneMgr.GetAt(index)
        if not family:
            continue
        #familyID = family.GetID()
        lvMin = family.GetJoinLVMin()
        if lvMin and realmLV < lvMin:
            #GameWorld.DebugLog("    官职不足的不处理! familyID=%s,lvMin=%s" % (familyID, lvMin), playerID)
            continue
        if family.GetJoinReview():
            #GameWorld.DebugLog("    需要审核的不处理! familyID=%s" % familyID, playerID)
            continue
        MemberMax = GetFamilySetting(family.GetLV(), "MemberMax")
        if family.GetCount() >= MemberMax:
            #GameWorld.DebugLog("    成员已满的不处理! familyID=%s" % familyID, playerID)
            continue
        #直接加入
        DoPlayerJionFamily(family, playerID, crossPlayer)
        return
    # 可再扩展自动请求,暂时不处理
    GameWorld.DebugLog("没有可自动进入的公会!")
    CrossPlayer.NotifyCode(crossPlayer, "QuickEnterFamilyFail")
    return
def GetFamilySetting(familyLV, fieldName):
    ## 获取公会等级表对应字段值
    if not fieldName:
        return 0
    ipyData = IpyGameDataPY.GetIpyGameData("Family", familyLV)
    if not ipyData:
        return 0
    attrName = "Get%s" % fieldName
    if not hasattr(ipyData, attrName):
        return 0
    return getattr(ipyData, attrName)()
def RequestJoinTagFamily(crossPlayer, familyID):
    ## 申请加入
    #if CheckInJoinCD(curPlayer):
    #    return
    playerID = crossPlayer.GetPlayerID()
    if crossPlayer.GetFamilyID():
        GameWorld.DebugLog('已经有公会不能再申请加入! familyID=%s' % crossPlayer.GetFamilyID(), playerID)
        return
    familyMgr = DBDataMgr.GetFamilyMgr()
    reqFamilyIDList = familyMgr.GetPlayerReqJoinFamilyIDList(playerID)
    if playerID in reqFamilyIDList:
        GameWorld.DebugLog('已经在申请加入公会列表中! familyID=%s' % familyID, playerID)
        return
    maxReqFamilyCnt = IpyGameDataPY.GetFuncCfg("FamilyReqJoin", 2)
    if len(reqFamilyIDList) >= maxReqFamilyCnt:
        GameWorld.DebugLog('已经达到最大申请加入公会数! %s, %s' % (len(reqFamilyIDList), reqFamilyIDList), playerID)
        return
    tagFamily = familyMgr.FindFamily(familyID)
    if not tagFamily:
        return
    lvMin = tagFamily.GetJoinLVMin()
    if crossPlayer.GetRealmLV() < lvMin:
        GameWorld.DebugLog('官职未达到该公会加入最低限制! realmLV=%s < %s' % (crossPlayer.GetRealmLV(), lvMin), playerID)
        return
    # 需要审核,满员后端不限制申请,由前端自行决定是否可申请
    if tagFamily.GetJoinReview():
        reqPlayerIDDict = tagFamily.GetReqJoinPlayerInfo()
        if playerID not in reqPlayerIDDict:
            maxReqPlayerCnt = IpyGameDataPY.GetFuncCfg("FamilyReqJoin", 1)
            if len(reqPlayerIDDict) >= maxReqPlayerCnt:
                GameWorld.DebugLog('目标公会申请加入数已满! %s, %s' % (len(reqFamilyIDList), reqFamilyIDList), playerID)
                CrossPlayer.NotifyCode(crossPlayer, "jiazu_pan_141056")
                return
        tagFamily.AddReqJoinPlayerID(playerID)
        # 广播给有招人权限的
        SendFamilyReqJoinInfo(familyID)
        #jiazu_pan_500807:申请入帮成功!请等待帮会管理人员审批!
        CrossPlayer.NotifyCode(crossPlayer, "jiazu_pan_500807")
        Sync_RequestAddFamilyInfo(crossPlayer)
        return
    # 不需要审核,自动加入
    memberMax = GetFamilySetting(tagFamily.GetLV(), "MemberMax")
    if tagFamily.GetCount() >= memberMax:
        GameWorld.DebugLog('目标公会成员已满! familyLV=%s,memberMax=%s' % (tagFamily.GetLV(), memberMax), playerID)
        return
    DoPlayerJionFamily(tagFamily, playerID, crossPlayer)
    return
def CancelJoinTagFamily(crossPlayer, familyID):
    # 撤销申请
    familyMgr = DBDataMgr.GetFamilyMgr()
    playerID = crossPlayer.GetPlayerID()
    tagFamily = familyMgr.FindFamily(familyID)
    if tagFamily:
        tagFamily.DelReqJoinPlayerID(playerID)
    familyMgr.DelPlayerReqJoinFamilyID(playerID, familyID)
    SendFamilyReqJoinInfo(familyID)
    Sync_RequestAddFamilyInfo(crossPlayer)
    return
def Sync_RequestAddFamilyInfo(crossPlayer, isForce=True):
    ## 通知当前申请加入的哪些家族
    playerID = crossPlayer.GetPlayerID()
    familyMgr = DBDataMgr.GetFamilyMgr()
    reqFamilyIDList = familyMgr.GetPlayerReqJoinFamilyIDList(playerID)
    # 非强制通知时没有申请记录的可不通知,如登录
    if not isForce and not reqFamilyIDList:
        return
    clientPack = ChPyNetSendPack.tagMCNotifyRequestJoinFamilyInfo()
    clientPack.Clear()
    clientPack.RequestJoinFamilyIDList = reqFamilyIDList
    clientPack.RequestCount = len(clientPack.RequestJoinFamilyIDList)
    CrossPlayer.SendFakePack(crossPlayer, clientPack)
    return
def IsFamilyNeedViewPlayer(playerID):
    ## 公会功能中查看玩家是否需要用到的
    # 公会成员已存储成员信息,所以成员不用判断,仅判断其他即可
    # 是否有请求加入某个公会
    familyMgr = DBDataMgr.GetFamilyMgr()
    if familyMgr.GetPlayerReqJoinFamilyIDList(playerID):
        return True
    return False
def SendFamilyReqJoinInfo(familyID):
    ## 广播给公会有招人权限的
    CrossPlayer.SendFakePackByFamily(familyID, GetPack_FamilyReqJoinInfo(familyID), None, GetFamilyMemberHasPow, FamilyPowerID_Call)
    return
def GetPack_FamilyReqJoinInfo(familyID):
    ## 获取 // A5 22 家族申请加入的玩家信息 #tagMCFamilyReqJoinInfo
    familyMgr = DBDataMgr.GetFamilyMgr()
    curFamily = familyMgr.FindFamily(familyID)
    if not curFamily:
        return
    reqPlayerIDDict = curFamily.GetReqJoinPlayerInfo()
    #没人申请也要通知
    #if not reqPlayerIDDict:
    #    return
    crossPlayerMgr = CrossPlayer.GetCrossPlayerMgr()
    clientPack = ChPyNetSendPack.tagMCFamilyReqJoinInfo()
    clientPack.ReqJoinList = []
    for playerID, reqTime in reqPlayerIDDict.items():
        crossPlayer = crossPlayerMgr.FindCrossPlayer(playerID)
        reqInfo = ChPyNetSendPack.tagMCFamilyReqJoinPlayer()
        reqInfo.PlayerID = playerID
        reqInfo.ReqTime = reqTime
        reqInfo.IsOnLine = 1 if crossPlayer else 0
        viewCache = PlayerViewCache.FindViewCache(playerID)
        if viewCache:
            reqInfo.Name = viewCache.GetPlayerName()
            reqInfo.NameLen = len(reqInfo.Name)
            reqInfo.LV = viewCache.GetLV()
            reqInfo.Job = viewCache.GetJob()
            reqInfo.RealmLV = viewCache.GetRealmLV()
            reqInfo.Face = viewCache.GetFace()
            reqInfo.FacePic = viewCache.GetFacePic()
            reqInfo.TitleID = viewCache.GetTitleID()
            reqInfo.FightPower = viewCache.GetFightPower()
            reqInfo.FightPowerEx = viewCache.GetFightPowerEx()
            reqInfo.ServerID = viewCache.GetServerID()
        clientPack.ReqJoinList.append(reqInfo)
        if len(clientPack.ReqJoinList) >= 100:
            break
    clientPack.ReqCnt = len(clientPack.ReqJoinList)
    return clientPack
#// A6 21 审核请求加入家族 #tagCMJoinFamilyReply
#
#struct tagCMJoinFamilyReply
#{
#    tagHead    Head;
#    DWORD    TagPlayerID;    //被审核玩家ID 0则代表全部
#    BYTE    IsOK;        //是否同意其加入
#};
def OnJoinFamilyReply(index, clientData, tick):
    curPlayer = GameWorld.GetPlayerManager().GetPlayerByIndex(index)
    FamilyPyPackForwarding(curPlayer, clientData, tick, "__OnJoinFamilyReply")
    return
def __OnJoinFamilyReply(crossPlayer, clientData, tick, fromServerID=0, reqDataEx=None):
    tagPlayerID = clientData.TagPlayerID
    isOK = clientData.IsOK
    GameWorld.DebugLog("__OnJoinFamilyReply: tagPlayerID=%s,isOK=%s" % (tagPlayerID, isOK))
    playerID = crossPlayer.GetPlayerID()
    familyID = crossPlayer.GetFamilyID()
    if familyID <= 0:
        return
    familyMgr = DBDataMgr.GetFamilyMgr()
    family = familyMgr.FindFamily(familyID)
    if not family:
        return
    curMember = family.FindMember(playerID)
    if not curMember:
        return
    if not GetFamilyMemberHasPow(curMember, FamilyPowerID_Call):
        GameWorld.DebugLog("没有招人权限,无法审核人员入盟!", playerID)
        #PlayerControl.NotifyCode(curPlayer, "XW_JZ_InviteErr_Popedom")
        return
    GameWorld.DebugLog("审核入盟申请: tagPlayerID=%s,familyID=%s,isOK=%s" % (tagPlayerID, familyID, isOK), playerID)
    reqPlayerIDDict = family.GetReqJoinPlayerInfo()
    tagPlayerIDList = reqPlayerIDDict.keys()
    if tagPlayerID:
        if tagPlayerID not in reqPlayerIDDict:
            GameWorld.DebugLog("不存在该申请人员! tagPlayerID=%s" % tagPlayerID)
            return
        tagPlayerIDList = [tagPlayerID]
    if not tagPlayerIDList:
        GameWorld.DebugLog("没有申请人员!")
        return
    crossPlayerMgr = CrossPlayer.GetCrossPlayerMgr()
    # 拒绝
    if not isOK:
        for tagPlayerID in tagPlayerIDList:
            family.DelReqJoinPlayerID(tagPlayerID)
            tagCrossPlayer = crossPlayerMgr.FindCrossPlayer(tagPlayerID)
            if not tagCrossPlayer:
                continue
            Sync_RequestAddFamilyInfo(tagCrossPlayer)
            #jiazu_pan_592934:{%S}拒绝了您的入帮申请
            CrossPlayer.NotifyCode(tagCrossPlayer, "jiazu_pan_592934", [family.GetName()])
        SendFamilyReqJoinInfo(familyID)
        return
    # 处理同意
    offlinePlayerCanJoin = IpyGameDataPY.GetFuncCfg("FamilyReqJoin", 3)
    MemberMax = GetFamilySetting(family.GetLV(), "MemberMax")
    joinOKPlayerIDList = []
    for tagPlayerID in tagPlayerIDList:
        if family.GetCount() >= MemberMax:
            CrossPlayer.NotifyCode(crossPlayer, "jiazu_lhs_202580")
            break
        tagCrossPlayer = crossPlayerMgr.FindCrossPlayer(tagPlayerID)
        #申请目标不在线
        if not tagCrossPlayer:
            if not offlinePlayerCanJoin:
                GameWorld.DebugLog("离线玩家无法加入公会! tagPlayerID=%s" % tagPlayerID, playerID)
                CrossPlayer.NotifyCode(crossPlayer, "jiazu_hwj35_367906")
                continue
        family.DelReqJoinPlayerID(tagPlayerID) # 以下只要操作的都删除
        if family.FindMember(tagPlayerID):
            GameWorld.DebugLog("已经是本盟成员! tagPlayerID=%s" % tagPlayerID, playerID)
            CrossPlayer.NotifyCode(crossPlayer, "XW_JZ_InviteErr_Repeat")
            continue
        tagFamilyID = familyMgr.GetPlayerFamilyID(tagPlayerID)
        if tagFamilyID:
            GameWorld.DebugLog("已经加入其他公会! tagPlayerID=%s,tagFamilyID=%s" % (tagPlayerID, tagFamilyID), playerID)
            CrossPlayer.NotifyCode(crossPlayer, "XW_JZ_InviteErr_Repeat")
            continue
        DoPlayerJionFamily(family, tagPlayerID, tagCrossPlayer, broadcastFamilyChange=False)
        joinOKPlayerIDList.append(tagPlayerID)
    #if not joinOKPlayerIDList:
    #    return
    SendFamilyReqJoinInfo(familyID)
    if joinOKPlayerIDList:
        Broadcast_FamilyInfo(familyID, changeMemIDList=joinOKPlayerIDList)
    return
#// A6 22 修改收人方式 #tagCMChangeFamilyJoin
#
#struct    tagCMChangeFamilyJoin
#{
#    tagHead         Head;
#    BYTE        JoinReview;    //成员加入是否需要审核,默认0自动加入
#    WORD        JoinLVMin;    //限制最低可加入的玩家等级
#};
def OnChangeFamilyJoin(index, clientData, tick):
    curPlayer = GameWorld.GetPlayerManager().GetPlayerByIndex(index)
    FamilyPyPackForwarding(curPlayer, clientData, tick, "__OnChangeFamilyJoin")
    return
def __OnChangeFamilyJoin(crossPlayer, clientData, tick, fromServerID=0, reqDataEx=None):
    joinReview = clientData.JoinReview
    joinLVMin = clientData.JoinLVMin # 官职
    GameWorld.DebugLog("__OnChangeFamilyJoin: joinReview=%s,joinLVMin=%s" % (joinReview, joinLVMin))
    playerID = crossPlayer.GetPlayerID()
    familyID = crossPlayer.GetFamilyID()
    if familyID <= 0:
        return
    familyMgr = DBDataMgr.GetFamilyMgr()
    family = familyMgr.FindFamily(familyID)
    if not family:
        return
    curMember = family.FindMember(playerID)
    if not curMember:
        return
    if not GetFamilyMemberHasPow(curMember, FamilyPowerID_Call):
        GameWorld.DebugLog("没有招人权限", playerID)
        return
    GameWorld.DebugLog("修改招人设置: familyID=%s,joinReview=%s,joinLVMin=%s" % (familyID, joinReview, joinLVMin), playerID)
    family.SetJoinReview(joinReview)
    family.SetJoinLVMin(joinLVMin)
    Broadcast_FamilyInfo(familyID, isSyncMem=False)
    return
#// A6 23 修改家族公告 #tagCMChangeFamilyBroadcast
#
#struct    tagCMChangeFamilyBroadcast
#{
#    tagHead        Head;
#    char        Msg[200];
#};
def OnChangeFamilyBroadcast(index, clientData, tick):
    curPlayer = GameWorld.GetPlayerManager().GetPlayerByIndex(index)
    FamilyPyPackForwarding(curPlayer, clientData, tick, "__OnChangeFamilyBroadcast")
    return
def __OnChangeFamilyBroadcast(crossPlayer, clientData, tick, fromServerID=0, reqDataEx=None):
    broadcast = clientData.Msg
    playerID = crossPlayer.GetPlayerID()
    familyID = crossPlayer.GetFamilyID()
    if familyID <= 0:
        return
    familyMgr = DBDataMgr.GetFamilyMgr()
    family = familyMgr.FindFamily(familyID)
    if not family:
        return
    curMember = family.FindMember(playerID)
    if not curMember:
        return
    if not GetFamilyMemberHasPow(curMember, FamilyPowerID_Broadcast):
        GameWorld.DebugLog("没有修改公告权限", playerID)
        return
    family.SetBroadcast(broadcast)
    GameWorld.DebugLog('更改公会公告: Family=%s,公告=%s' % (GameWorld.CodeToGbk(family.GetName()), GameWorld.CodeToGbk(broadcast)), playerID)
    Broadcast_FamilyInfo(familyID, isSyncMem=False)
    return
#// A6 24 修改家族徽章 #tagCMChangeFamilyEmblem
#
#struct    tagCMChangeFamilyEmblem
#{
#    tagHead        Head;
#    BYTE        EmblemID;    // 更换的徽章ID
#    char        EmblemWord[3];    // 徽章文字
#};
def OnChangeFamilyEmblem(index, clientData, tick):
    curPlayer = GameWorld.GetPlayerManager().GetPlayerByIndex(index)
    FamilyPyPackForwarding(curPlayer, clientData, tick, "__OnChangeFamilyEmblem")
    return
def __OnChangeFamilyEmblem(crossPlayer, clientData, tick, fromServerID=0, reqDataEx=None):
    changeEmblemID = clientData.EmblemID
    emblemWord = clientData.EmblemWord
    PlayerFamilyEmblem.OnChangeFamilyEmblem(crossPlayer, changeEmblemID, emblemWord)
    return
#// A6 25 修改家族成员职位 #tagCMChangeFamilyMemLV
#
#struct    tagCMChangeFamilyMemLV
#{
#    tagHead        Head;
#    DWORD        PlayerID; // 目标成员ID
#    BYTE        FmLV;  // 变更为xx职位
#};
def OnChangeFamilyMemLV(index, clientData, tick):
    curPlayer = GameWorld.GetPlayerManager().GetPlayerByIndex(index)
    FamilyPyPackForwarding(curPlayer, clientData, tick, "__OnChangeFamilyMemLV")
    return
def __OnChangeFamilyMemLV(crossPlayer, clientData, tick, fromServerID=0, reqDataEx=None):
    OnChangeFamilyMemberLV(crossPlayer, clientData.PlayerID, clientData.FmLV)
    return
def OnChangeFamilyMemberLV(crossPlayer, tagID, changeFmlv, isGMOP=False):
    '''变更成员职位
    @param curPlayer: 操作的玩家
    @param tagID: 目标成员ID
    @param changeFmlv: 修改为xx职位
    @param isGMOP: 是否是GM后台发起的,如果是GM发起的,一般curPlayer传入的为目标成员ID实例
    '''
    if not crossPlayer:
        return
    playerID = crossPlayer.GetPlayerID()
    familyID = crossPlayer.GetFamilyID()
    if familyID <= 0:
        return
    familyMgr = DBDataMgr.GetFamilyMgr()
    family = familyMgr.FindFamily(familyID)
    if not family:
        return
    curMember = family.FindMember(playerID)
    if not curMember:
        return
    if changeFmlv < 0 or changeFmlv > IPY_PlayerDefine.fmlLeader:
        GameWorld.DebugLog("不存在该职位等级! changeFmlv=%s" % changeFmlv)
        return
    # 非GM操作的需检查权限
    if not isGMOP:
        if not GetFamilyMemberHasPow(curMember, FamilyPowerID_ChangeFmlv):
            return
        if playerID == tagID:
            GameWorld.DebugLog("不能任免自己的家族职位", playerID)
            return
    tagMember = family.FindMember(tagID)
    if tagMember == None:
        GameWorld.DebugLog("更改家族成员职位时目标成员不存在! tagID=%s" % tagID, playerID)
        return
    if not isGMOP:
        if curMember.GetFmLV() != IPY_PlayerDefine.fmlLeader:
            if changeFmlv >= curMember.GetFmLV():
                GameWorld.DebugLog("修改的职位不能比自己高或平级! changeFmlv=%s" % changeFmlv, playerID)
                return
            if tagMember.GetFmLV() >= curMember.GetFmLV():
                GameWorld.DebugLog("修改的目标成员职位不能比自己高或平级! tagFmlv=%s" % tagMember.GetFmLV(), playerID)
                return
    if changeFmlv == IPY_PlayerDefine.fmlLeader:
        ChangeFamilyLeader(family, tagMember)
    else:
        fmLVMemCnt = 0
        for index in range(family.GetCount()):
            familyMember = family.GetAt(index)
            if familyMember.GetFmLV() != changeFmlv:
                continue
            fmLVMemCnt += 1
        maxCnt = GetFamilySetting(family.GetLV(), Def_FmlSetAttrName.get(changeFmlv, ""))
        if fmLVMemCnt >= maxCnt:
            # jiazu_hwj35_272921 改为 jiazu_chenxin_31379
            CrossPlayer.NotifyCode(crossPlayer, "jiazu_chenxin_31379")
            GameWorld.DebugLog("目前该职位的人数已经达到上限! changeFmlv=%s,fmLVMemCnt=%s >= %s" % (changeFmlv, fmLVMemCnt, maxCnt))
            return
        ChangeFamilyMemberLv(tagMember, changeFmlv)
    if isGMOP:
        family.SetBroadcast("")
    Broadcast_FamilyInfo(familyID, changeMemIDList=[tagID])
    return True
def ChangeFamilyLeader(family, newLeaderMem):
    ## 变更家族族长
    familyID = family.GetID()
    befLeaderID = family.GetLeaderID()
    newLeaderID = newLeaderMem.GetPlayerID()
    if befLeaderID == newLeaderID:
        return
    befLeaderMem = family.FindMember(befLeaderID)
    if befLeaderMem:
        #把原族长降为普通成员
        ChangeFamilyMemberLv(befLeaderMem, IPY_PlayerDefine.fmlMember)
    family.SetLeaderID(newLeaderID)
    ChangeFamilyMemberLv(newLeaderMem, IPY_PlayerDefine.fmlLeader)
    GameWorld.Log("家族设置新族长! familyID=%s,newLeaderID=%s,befLeaderID=%s" % (familyID, newLeaderID, befLeaderID))
    return
def ChangeFamilyMemberLv(tagMember, changeFamilyLV):
    ## 修改成员职位,只做修改逻辑,不做验证,验证由各调用入口自行验证
    familyID = tagMember.GetFamilyID()
    tagID = tagMember.GetPlayerID()
    memName = tagMember.GetPlayerName()
    befFamilyLV = tagMember.GetFmLV()
    tagMember.SetFmLV(changeFamilyLV)
    tagCrossPlayer = CrossPlayer.GetCrossPlayerMgr().FindCrossPlayer(tagID)
    if tagCrossPlayer:
        MapServer_FamilyRefresh(tagCrossPlayer, familyID)
        if GetFamilyMemberHasPow(tagMember, FamilyPowerID_Call):
            CrossPlayer.SendFakePack(tagCrossPlayer, GetPack_FamilyReqJoinInfo(familyID))
    # 记录家族事件记录信息
    tick = GameWorld.GetGameWorld().GetTick()
    AddFamilyActionNote(memName, familyID, ShareDefine.Def_ActionType_FamilyEvent,
                        [ShareDefine.Def_FamilyActionEvent_MemberChange, ShareDefine.Def_FamilyMemberChange_FMLV, changeFamilyLV, befFamilyLV], tick)
    #xx被任命为xx
    #NotifyAllFamilyMemberMsg(familyID, "XW_JZ_AppointFamily", [memName, changeFamilyLV])
    #GetFamilyMgr().SetSyncCrossFamilyUpd(familyMember.GetFamilyID(), familyMember.GetPlayerID(), syncNow=True) # 成员职位变更
    return
def __AutoChangeLeader(curFamily):
    ## 自动传位
    leaderID = curFamily.GetLeaderID()
    leaderMem = curFamily.FindMember(leaderID)
    if not leaderMem:
        return
    offTime = leaderMem.GetOffTime()
    if not offTime:
        return
    familyID = curFamily.GetID()
    curTime = int(time.time())
    passTime = curTime - offTime
    passHours = passTime / 3600.0
    needHours = IpyGameDataPY.GetFuncCfg("FamilyLeaderAutoChange", 1)
    if passHours < needHours:
        GameWorld.DebugLogEx("盟主离线未超过限制小时,不处理自动传位!familyID=%s,leaderID=%s,offTime=%s,passHours=%s < %s",
                             familyID, leaderID, GameWorld.ChangeTimeNumToStr(offTime), passHours, needHours)
        return
    priorityHours = IpyGameDataPY.GetFuncCfg("FamilyLeaderAutoChange", 1) # 优先传给离线不超过x小时的成员,一样按优先级
    priorityList = []
    commList = []
    for i in range(0, curFamily.GetCount()):
        member = curFamily.GetAt(i)
        if member.GetFmLV() == IPY_PlayerDefine.fmlLeader:
            continue
        memOffTime = member.GetOffTime()
        if memOffTime:
            sortTime = memOffTime
            memPassTime = curTime - memOffTime
            memPassHours = memPassTime / 3600.0
        else:
            sortTime = curTime # 排序用的时间,越大越优先
            memPassTime = 0
            memPassHours = 0
        fmLV = member.GetFmLV() # ְλ
        contribTotal = member.GetContribTotal() # 总贡献
        commList.append([fmLV, sortTime, contribTotal, member])
        if priorityHours and memPassHours <= priorityHours:
            priorityList.append([fmLV, sortTime, contribTotal, member])
    if not priorityList and not commList:
        # 没有可传位的目标成员
        return
    toMember = None
    if priorityList:
        priorityList.sort(reverse=True)
        toMember = priorityList[0][-1]
    else:
        commList.sort(reverse=True)
        toMember = commList[0][-1]
    if not toMember:
        return
    newLeaderID = toMember.GetPlayerID()
    GameWorld.Log("公会自动传位: familyID=%s,leaderID=%s,offTime=%s,passHours=%s,newLeaderID=%s"
                  % (familyID, leaderID, GameWorld.ChangeTimeNumToStr(offTime), passHours, newLeaderID))
    ChangeFamilyLeader(curFamily, toMember)
    Broadcast_FamilyInfo(familyID, changeMemIDList=[leaderID, newLeaderID])
    # 邮件通知
    toServerID = toMember.GetServerID()
    PlayerMail.SendMailByKey("FamilyLeaderAutoChange", newLeaderID, [], [curFamily.GetName()], toServerID=toServerID)
    return
#// A6 26 请求家族成员列表 #tagCMGetFamilyInfo
#
#struct    tagCMGetFamilyInfo
#{
#    tagHead        Head;
#};
def OnGetFamilyInfo(index, clientData, tick):
    #改为后端主动同步差异,不用再请求了
    #curPlayer = GameWorld.GetPlayerManager().GetPlayerByIndex(index)
    #Sync_FamilyInfo(crossPlayer)
    return
#// A6 03 离开家族 #tagCMLeaveFamily
#
#struct    tagCMLeaveFamily
#{
#    tagHead        Head;
#};
def OnLeaveFamily(index, clientData, tick):
    curPlayer = GameWorld.GetPlayerManager().GetPlayerByIndex(index)
    FamilyPyPackForwarding(curPlayer, clientData, tick, "__OnMemLeaveFamily")
    return
def __OnMemLeaveFamily(crossPlayer, clientData, tick, fromServerID=0, reqDataEx=None):
    playerID = crossPlayer.GetPlayerID()
    familyID = crossPlayer.GetFamilyID()
    if familyID <= 0:
        return
    familyMgr = DBDataMgr.GetFamilyMgr()
    family = familyMgr.FindFamily(familyID)
    if not family:
        return
    curMember = family.FindMember(playerID)
    if not curMember:
        return
    familyLV = curMember.GetFmLV()  # ְλ
    if family.GetCount() > 1 and familyLV == IPY_PlayerDefine.fmlLeader:
        GameWorld.DebugLog("族长在成员人数大于1时不能直接退出家族", playerID)
        return
    # 功能限制退出公会
    # ...
    # 进出时间限制暂不做,等正式功能再补
    #PlayerControl.SetLeaveFamilyTime(curPlayer, updTime)
    # 执行退出
    GameWorld.DebugLog("离开家族! familyID=%s" % familyID, playerID)
    family.DeleteMember(playerID)
    AddFamilyActionNote(crossPlayer.GetPlayerName(), familyID, ShareDefine.Def_ActionType_FamilyEvent,
                        [ShareDefine.Def_FamilyActionEvent_MemberChange, ShareDefine.Def_FamilyMemberChange_Leave], tick)
    #XW_JZ_LeaveFamily   <n color="0,190,255">{%S1%}</n><n color="255,255,0">退出了家族!</n>  25  -   -
    #NotifyAllFamilyMemberMsg(familyID, "XW_JZ_LeaveFamily", [curPlayer.GetName()])
    __DoPlayerLeaveFamilyByID(family, playerID, crossPlayer)
    MapServer_FamilyRefresh(crossPlayer, 0, 1)
    CrossPlayer.SendFakePackByFamily(familyID, GetPack_FamilyDel(playerID, crossPlayer.GetPlayerName(), 1))
    if family.GetCount() == 0:
        #玩家离开后, 家族没有人了 , 删除这个家族
        familyMgr.DelFamily(familyID)
        return
    return
#// A6 05 删除家族成员 #tagCMDeleteFamilyMember
#
#struct    tagCMDeleteFamilyMember
#{
#    tagHead        Head;
#    DWORD        MemberID;
#};
def OnDeleteFamilyMember(index, clientData, tick):
    curPlayer = GameWorld.GetPlayerManager().GetPlayerByIndex(index)
    FamilyPyPackForwarding(curPlayer, clientData, tick, "__OnDeleteFamilyMember")
    return
def __OnDeleteFamilyMember(crossPlayer, clientData, tick, fromServerID=0, reqDataEx=None):
    tagMemberID = clientData.MemberID
    GameWorld.DebugLog("__OnDeleteFamilyMember tagMemberID=%s" % tagMemberID)
    playerID = crossPlayer.GetPlayerID()
    if playerID == tagMemberID:
        return
    familyID = crossPlayer.GetFamilyID()
    if familyID <= 0:
        return
    familyMgr = DBDataMgr.GetFamilyMgr()
    family = familyMgr.FindFamily(familyID)
    if not family:
        return
    curMember = family.FindMember(playerID)
    if not curMember:
        return
    if not GetFamilyMemberHasPow(curMember, FamilyPowerID_Kick):
        GameWorld.DebugLog("没有踢人权限!")
        return
    tagMember = family.FindMember(tagMemberID)
    if not tagMember:
        return
    curFmlv = curMember.GetFmLV()
    tagFmlv = tagMember.GetFmLV()
    if tagFmlv >= curFmlv:
        GameWorld.DebugLog("只能踢比自己职位低的成员! tagMemberID=%s,tagFmlv(%s) >= curFmlv(%s)" % (tagMemberID, tagFmlv, curFmlv), playerID)
        return
    # 功能限制踢人
    # ...
    tagPlayerName = tagMember.GetPlayerName()  # 被踢玩家名
    tagPlayerID = tagMember.GetPlayerID()  # 被踢玩家ID
    family.DeleteMember(tagPlayerID)
    AddFamilyActionNote(tagPlayerName, familyID, ShareDefine.Def_ActionType_FamilyEvent,
                        [ShareDefine.Def_FamilyActionEvent_MemberChange, ShareDefine.Def_FamilyMemberChange_KickOut], tick)
    #XW_JZ_LeaveFamily   <n color="0,190,255">{%S1%}</n><n color="255,255,0">退出了家族!</n>  25  -   -
    #NotifyAllFamilyMemberMsg(familyID, "XW_JZ_LeaveFamily", [tagPlayerName])
    #删除玩家
    crossPlayer = CrossPlayer.GetCrossPlayerMgr().FindCrossPlayer(tagMemberID)
    __DoPlayerLeaveFamilyByID(family, tagPlayerID, crossPlayer)
    if crossPlayer:
        MapServer_FamilyRefresh(crossPlayer, 0)
    CrossPlayer.SendFakePackByFamily(familyID, GetPack_FamilyDel(tagMemberID, tagPlayerName, 0))
    return
def __DoPlayerLeaveFamilyByID(curFamily, leavePlayerID, crossPlayer=None):
    ## 有玩家离开家族处理,主要针对家族层级的,玩家个人的在 __OnLeaveFamily 处理
    PlayerFamilyTaofa.OnFamilyMemberLeave(curFamily, leavePlayerID)
    return
#// A6 11 家族改名 #tagCMRenameFamily
#
#struct tagCMRenameFamily
#{
#    tagHead        Head;
#    BYTE        NewNameLen;
#    char        NewName[NewNameLen];
#    BYTE        ItemIndex;  //改名物品在背包中的位置
#};
def UpdateFamilyName(index, clientData, tick):
    curPlayer = GameWorld.GetPlayerManager().GetPlayerByIndex(index)
    newName = clientData.NewName
    if not CheckInputFamilyName(curPlayer, newName):
        GameWorld.DebugLog("名字验证不通过")
        return
    moneyType, moneyValue = IpyGameDataPY.GetFuncEvalCfg("FamilyRename", 1)
    if moneyType and moneyValue and not PlayerControl.HaveMoney(curPlayer, moneyType, moneyValue):
        return
    FamilyPyPackForwarding(curPlayer, clientData, tick, "__UpdateFamilyName", True, 20)
    return
def __UpdateFamilyName(crossPlayer, clientData, tick, fromServerID=0, reqDataEx=None):
    newName = clientData.NewName
    #itemIndex = clientData.ItemIndex
    playerID = crossPlayer.GetPlayerID()
    familyID = crossPlayer.GetFamilyID()
    if familyID <= 0:
        return
    familyMgr = DBDataMgr.GetFamilyMgr()
    family = familyMgr.FindFamily(familyID)
    if not family:
        return
    curMember = family.FindMember(playerID)
    if not curMember:
        return
    if curMember.GetFmLV() != IPY_PlayerDefine.fmlLeader:
        GameWorld.DebugLog("非盟主不可改名!", playerID)
        return
    curTime = int(time.time())
    cdHours = IpyGameDataPY.GetFuncCfg("FamilyRename", 2)
    if cdHours:
        cdSeconds = cdHours * 3600
        lastRenameTime = GetRenameTime(family)
        if lastRenameTime and (curTime - lastRenameTime) < cdSeconds:
            GameWorld.DebugLog("公会改名CD中! lastRenameTime=%s,cdHours=%s" % (GameWorld.ChangeTimeNumToStr(lastRenameTime), cdHours))
            return
    # 验证重名
    if CheckFamilyNameExists(crossPlayer, newName, fromServerID):
        return
    moneyType, moneyValue = IpyGameDataPY.GetFuncEvalCfg("FamilyRename", 1)
    if moneyType and moneyValue:
        CrossPlayer.CostPlayerResources(crossPlayer, "FamilyRename", costMoneyDict={moneyType:moneyValue})
    family.SetName(newName)
    if cdHours:
        SetRenameTime(family, curTime)
    crossPlayerMgr = CrossPlayer.GetCrossPlayerMgr()
    for index in xrange(family.GetCount()):
        member = family.GetAt(index)
        memID = member.GetPlayerID()
        memCrossPlayer = crossPlayerMgr.FindCrossPlayer(memID)
        if not memCrossPlayer:
            continue
        MapServer_FamilyRefresh(memCrossPlayer, familyID)
        #player.Notify_FamilyNameRefresh() #//04 36    周围玩家家族名刷新#tagPlayerFamilyNameRefresh
    Broadcast_FamilyInfo(familyID, isSyncMem=False)
    return True
#// A6 20 搜索家族列表 #tagCMViewFamilyPage
#
#struct    tagCMViewFamilyPage
#{
#    tagHead        Head;
#    BYTE        MsgLen;        //模糊搜索家族,如果输入为空,则为不限制该条件
#    char        Msg[MsgLen];    //size = MsgLen
#    BYTE        PageIndex;    //查询第X页索引,0~n
#    BYTE        ShowCount;    //每页数量,前端可自行指定,最大50
#};
def OnViewFamilyPage(index, clientData, tick):
    curPlayer = GameWorld.GetPlayerManager().GetPlayerByIndex(index)
    FamilyPyPackForwarding(curPlayer, clientData, tick, "__OnViewFamilyPage")
    return
def __OnViewFamilyPage(crossPlayer, clientData, tick, fromServerID=0, reqDataEx=None):
    msg = clientData.Msg
    pageIndex = clientData.PageIndex
    showCount = min(clientData.ShowCount, 50)
    familyMgr = DBDataMgr.GetFamilyMgr()
    zoneID = familyMgr.GetZoneIDInThisServer(fromServerID)
    if zoneID < 0:
        GameWorld.ErrLog("找不到服务器ID在本服中的公会分区! fromServerID=%s" % fromServerID)
        return
    playerFamilyID = crossPlayer.GetFamilyID()
    zoneMgr = familyMgr.GetZoneFamilyMgr(zoneID)
    zoneMgr.Sort(True)
    familyCount = zoneMgr.GetCount()
    totalPage = 0
    if not msg:
        startIndex = pageIndex * showCount
        endIndex = startIndex + showCount - 1
        if familyCount > 0:
            totalPage = GameWorld.GetIntUpper(familyCount, showCount)
    # 有指定搜索内容的后端固定返回单页
    else:
        pageIndex = 0
        showCount = 20
        totalPage = 1
        startIndex = 0
        endIndex = familyCount - 1
    clientPack = ChPyNetSendPack.tagMCFamilyViewList()
    clientPack.Msg = msg
    clientPack.MsgLen = len(clientPack.Msg)
    clientPack.PageIndex = pageIndex
    clientPack.ShowCount = showCount
    clientPack.TotalPage = totalPage
    clientPack.Rank = zoneMgr.GetFamilyRank(playerFamilyID)
    clientPack.FamilyList = []
    for index in range(startIndex, endIndex + 1):
        if index >= familyCount:
            break
        family = zoneMgr.GetAt(index)
        if not family:
            continue
        if msg:
            if msg in family.GetName() or msg == str(family.GetID()):
                pass
            else:
                continue
        familyView = ChPyNetSendPack.tagMCFamilyView()
        familyView.Rank = index + 1
        familyView.FamilyID = family.GetID()
        familyView.FamilyName = family.GetName()
        familyView.FamilyNameLen = len(familyView.FamilyName)
        familyView.LeaderID = family.GetLeaderID()
        leaderMember = family.FindMember(familyView.LeaderID)
        familyView.LeaderName = leaderMember.GetPlayerName() if leaderMember else ""
        familyView.LeaderNameLen = len(familyView.LeaderName)
        familyView.FamilyLV = family.GetLV()
        familyView.JoinReview = family.GetJoinReview()
        familyView.JoinLVMin = family.GetJoinLVMin()
        familyView.ServerID = family.GetServerID()
        familyView.EmblemID = family.GetEmblemID()
        familyView.EmblemWord = family.GetEmblemWord()
        familyView.FightPower = family.GetFightPower()
        familyView.FightPowerEx = family.GetFightPowerEx()
        familyView.MemberCount = family.GetCount()
        clientPack.FamilyList.append(familyView)
        clientPack.FamilyCount = len(clientPack.FamilyList)
        if clientPack.FamilyCount >= showCount:
            break
    CrossPlayer.SendFakePack(crossPlayer, clientPack)
    return
#// A6 12 家族捐献货币 #tagCMFamilyMoneyDonate
#
#struct     tagCMFamilyMoneyDonate
#{
#    tagHead        Head;
#    BYTE        DonateType;    // 捐献类型
#};
def OnFamilyMoneyDonate(index, clientData, tick):
    curPlayer = GameWorld.GetPlayerManager().GetPlayerByIndex(index)
    playerID = curPlayer.GetPlayerID()
    donateType = clientData.DonateType
    ipyData = IpyGameDataPY.GetIpyGameData("FamilyDonate", donateType)
    if not ipyData:
        return
    dailyCntMax = ipyData.GetDailyCnt()
    donateCnt = curPlayer.NomalDictGetProperty(ChConfig.Def_Player_Dict_FamilyDonateCnt % donateType)
    if donateCnt >= dailyCntMax:
        GameWorld.DebugLog("今日捐献次数已达上限! donateType=%s,donateCnt=%s >= %s" % (donateType, donateCnt, dailyCntMax), playerID)
        return
    moneyType = ipyData.GetMoneyType()
    moneyValue = ipyData.GetMoneyValue()
    if not moneyType or not moneyValue:
        return
    if not PlayerControl.HaveMoney(curPlayer, moneyType, moneyValue):
        return
    reqDataEx = {"donateCnt":donateCnt}
    FamilyPyPackForwarding(curPlayer, clientData, tick, "__OnFamilyMoneyDonate", True, 20, reqDataEx=reqDataEx)
    return
def __OnFamilyMoneyDonate(crossPlayer, clientData, tick, fromServerID=0, reqDataEx=None):
    donateType = clientData.DonateType
    playerID = crossPlayer.GetPlayerID()
    familyID = crossPlayer.GetFamilyID()
    if familyID <= 0:
        return
    familyMgr = DBDataMgr.GetFamilyMgr()
    curFamily = familyMgr.FindFamily(familyID)
    if not curFamily:
        return
    curMember = curFamily.FindMember(playerID)
    if not curMember:
        return
    if not reqDataEx:
        return
    if "donateCnt" not in reqDataEx:
        return
    donateCnt = reqDataEx["donateCnt"]
    ipyData = IpyGameDataPY.GetIpyGameData("FamilyDonate", donateType)
    if not ipyData:
        return
    moneyType = ipyData.GetMoneyType()
    moneyValue = ipyData.GetMoneyValue()
    if not moneyType or not moneyValue:
        return
    CrossPlayer.CostPlayerResources(crossPlayer, "FamilyMoneyDonate", costMoneyDict={moneyType:moneyValue})
    awardItemList = ipyData.GetAwardItemList()
    donateCnt += 1
    CrossPlayer.SetPlayerNomalDict(crossPlayer, {ChConfig.Def_Player_Dict_FamilyDonateCnt % donateType:donateCnt}, isDayReset=True)
    #SyncDonateCntInfo(curPlayer)
    # 增加成员捐献次数记录
    memDonateCntDay = curMember.GetDonateCntDay() + 1
    memDonateCntTotal = min(curMember.GetDonateCntTotal() + 1, ChConfig.Def_UpperLimit_DWord)
    curMember.SetDonateCntDay(memDonateCntDay)
    curMember.SetDonateCntTotal(memDonateCntTotal)
    GameWorld.DebugLog("家族捐献: donateType=%s,donateCnt=%s,%s,memDonateCntDay=%s,memDonateCntDay=%s"
                       % (donateType, donateCnt, awardItemList, memDonateCntDay, memDonateCntTotal), playerID)
    CrossPlayer.GivePlayerResources(crossPlayer, awardItemList, eventName="FamilyMoneyDonate")
    return True
def __OnFamilyMoneyDonate_Ret(curPlayer, clientData, isOK):
    if not isOK:
        return
    SyncDonateCntInfo(curPlayer)
    return
def AddFamilyExp(curPlayer, addExp):
    ## 增加玩家家族经验
    clientData, tick = None, 0
    reqDataEx = {"addExp":addExp}
    FamilyPyPackForwarding(curPlayer, clientData, tick, "__AddFamilyExp", reqCD=0, reqDataEx=reqDataEx)
    return
def __AddFamilyExp(crossPlayer, clientData, tick, fromServerID=0, reqDataEx=None):
    playerID = crossPlayer.GetPlayerID()
    familyID = crossPlayer.GetFamilyID()
    if familyID <= 0:
        return
    familyMgr = DBDataMgr.GetFamilyMgr()
    curFamily = familyMgr.FindFamily(familyID)
    if not curFamily:
        return
    curLV = curFamily.GetLV()
    curExp = curFamily.GetExp()
    if not reqDataEx:
        return
    addExp = reqDataEx["addExp"]
    updLV = curLV
    updExp = curExp + addExp
    GameWorld.DebugLog("增加公会经验: curLV=%s,curExp=%s,addExp=%s,updExp=%s" % (curLV, curExp, addExp, updExp), playerID)
    ipyData = IpyGameDataPY.GetIpyGameData("Family", curLV)
    lvUPExp = ipyData.GetNeedExp()
    while lvUPExp and updExp >= lvUPExp:
        ipyData = IpyGameDataPY.GetIpyGameDataNotLog("Family", updLV + 1)
        if not ipyData:
            break
        updLV += 1
        updExp -= lvUPExp
        lvUPExp = ipyData.GetNeedExp()
        GameWorld.DebugLog("    公会升级: updLV=%s,updExp=%s,lvUPExp=%s" % (updLV, updExp, lvUPExp), playerID)
    curFamily.SetLV(updLV)
    curFamily.SetExp(updExp)
    Broadcast_FamilyInfo(familyID, isSyncMem=False)
    return True
def AddFamilyContrib(curPlayer, addContribValue):
    ## 增加玩家累计家族贡献
    clientData, tick = None, 0
    reqDataEx = {"addContribValue":addContribValue}
    FamilyPyPackForwarding(curPlayer, clientData, tick, "__AddFamilyContrib", reqCD=0, reqDataEx=reqDataEx)
    return
def __AddFamilyContrib(crossPlayer, clientData, tick, fromServerID=0, reqDataEx=None):
    playerID = crossPlayer.GetPlayerID()
    familyID = crossPlayer.GetFamilyID()
    if familyID <= 0:
        return
    familyMgr = DBDataMgr.GetFamilyMgr()
    curFamily = familyMgr.FindFamily(familyID)
    if not curFamily:
        return
    curMember = curFamily.FindMember(playerID)
    if not curMember:
        return
    if not reqDataEx:
        return
    addContribValue = reqDataEx["addContribValue"]
    contribDay = curMember.GetContribDay() + addContribValue
    contribTotal = min(curMember.GetContribTotal() + addContribValue, ChConfig.Def_UpperLimit_DWord)
    curMember.SetContribDay(contribDay)
    curMember.SetContribTotal(contribTotal)
    GameWorld.DebugLog("增加成员贡献: familyID=%s,addContribValue=%s,contribDay=%s,contribTotal=%s" % (familyID, addContribValue, contribDay, contribTotal), playerID)
    Broadcast_FamilyInfo(familyID, isSyncMem=False)
    return
## ------------------------------------------------------------------------------------------------
#// A6 17 查询家族行为信息 #tagCMQueryFamilyAction
#
#struct    tagCMQueryFamilyAction
#{
#    tagHead        Head;
#    BYTE        ActionType;        // 行为类型
#    DWORD        FamilyID;         // 家族ID,发0默认自己家族
#};
def OnQueryFamilyAction(index, clientData, tick):
    curPlayer = GameWorld.GetPlayerManager().GetPlayerByIndex(index)
    FamilyPyPackForwarding(curPlayer, clientData, tick, "__OnQueryFamilyAction")
    return
def __OnQueryFamilyAction(crossPlayer, clientData, tick, fromServerID=0, reqDataEx=None):
    actionType = clientData.ActionType
    familyID = clientData.FamilyID
    if not familyID:
        familyID = crossPlayer.GetFamilyID()
    SendFamilyActionInfo(crossPlayer, familyID, actionType)
    return
def SendFamilyActionInfo(crossPlayer, familyID, actionType):
    ## 发送家族行为
    # @param crossPlayer: 为None时通知该公会所有成员
    if not familyID:
        return
    familyAction = DBDataMgr.GetFamilyActionMgr().GetFamilyAction(familyID, actionType)
    clientPack = ChPyNetSendPack.tagMCFamilyActionInfo()
    clientPack.FamilyID = familyID
    clientPack.ActionType = actionType
    clientPack.FamilyActionList = []
    for index in xrange(familyAction.Count()):
        familyActionData = familyAction.At(index)
        actionData = ChPyNetSendPack.tagMCFamilyAction()
        actionData.Time = familyActionData.GetTime()
        actionData.Name = familyActionData.GetName()
        actionData.NameLen = len(actionData.Name)
        actionData.Value1 = familyActionData.GetValue1()
        actionData.Value2 = familyActionData.GetValue2()
        actionData.Value3 = familyActionData.GetValue3()
        actionData.Value4 = familyActionData.GetValue4()
        actionData.Value5 = familyActionData.GetValue5()
        actionData.Value6 = familyActionData.GetValue6()
        actionData.UseData = familyActionData.GetUserData()
        actionData.UseDataLen = len(actionData.UseData)
        clientPack.FamilyActionList.append(actionData)
    clientPack.Count = len(clientPack.FamilyActionList)
    if crossPlayer:
        CrossPlayer.SendFakePack(crossPlayer, clientPack)
        return
    CrossPlayer.SendFakePackByFamily(familyID, clientPack)
    return
def SendFamilyAction(actionDataList, crossPlayer=None):
    ## 同步指定公会action
    # @param actionDataList: 支持列表或指定actionData
    # @param crossPlayer: 为None时通知该公会所有成员
    if not isinstance(actionDataList, list):
        actionDataList = [actionDataList]
    if not actionDataList:
        return
    familyActionData = actionDataList[0]
    familyID = familyActionData.GetFamilyID()
    actionType = familyActionData.GetActionType()
    clientPack = ChPyNetSendPack.tagMCFamilyActionInfo()
    clientPack.FamilyID = familyID
    clientPack.ActionType = actionType
    clientPack.FamilyActionList = []
    for familyActionData in actionDataList:
        actionData = ChPyNetSendPack.tagMCFamilyAction()
        actionData.Time = familyActionData.GetTime()
        actionData.Name = familyActionData.GetName()
        actionData.NameLen = len(actionData.Name)
        actionData.Value1 = familyActionData.GetValue1()
        actionData.Value2 = familyActionData.GetValue2()
        actionData.Value3 = familyActionData.GetValue3()
        actionData.Value4 = familyActionData.GetValue4()
        actionData.Value5 = familyActionData.GetValue5()
        actionData.Value6 = familyActionData.GetValue6()
        actionData.UseData = familyActionData.GetUserData()
        actionData.UseDataLen = len(actionData.UseData)
        clientPack.FamilyActionList.append(actionData)
    clientPack.Count = len(clientPack.FamilyActionList)
    if crossPlayer:
        CrossPlayer.SendFakePack(crossPlayer, clientPack)
        return
    CrossPlayer.SendFakePackByFamily(familyID, clientPack)
    return
## -------------------------------------- 游戏服本服处理 --------------------------------------------
'''
为方便本服、跨服互通公会逻辑统一,公会相关数据处理统一使用 CrossPlayer,视为以前的GameServer处理,这样本服跨服的公会管理通用
本服的curPlayer仅处理以前类似MapServer的curPlayer相关逻辑
【注】 MapServer的逻辑不能再直接获取 family 实例进行逻辑处理,只能处理 curPlayer 可用的逻辑
'''
def C2S_FamilyMapPlayer(dataMsg, playerID):
    curPlayer = GameWorld.GetPlayerManager().FindPlayerByID(playerID)
    if not curPlayer:
        return
    doType = dataMsg["doType"]
    doData = dataMsg["doData"]
    if doType == "FamilyRefresh":
        Do_MapServer_FamilyRefresh(curPlayer, doData)
    return
def Do_MapServer_PlayerOnDay(curPlayer):
    ResetDailyDonateCnt(curPlayer)
    return
def Do_MapServer_PlayerLogin(curPlayer):
    SyncDonateCntInfo(curPlayer)
    PlayerFamilyZhenbaoge.OnPlayerLogin(curPlayer)
    return
def Do_MapServer_FamilyRefresh(curPlayer, doData):
    tick = GameWorld.GetGameWorld().GetTick()
    playerID = curPlayer.GetPlayerID()
    refreshFmLV = 0
    refreshFamilyLV = 0
    refreshFamilyName = ""
    if refreshFamilyID:
        familyMgr = DBDataMgr.GetFamilyMgr()
        curFamily = familyMgr.FindFamily(refreshFamilyID)
        if curFamily:
            refreshFamilyLV = curFamily.GetLV()
            refreshFamilyName = curFamily.GetName()
            member = curFamily.FindMember(playerID)
            if member:
                refreshFmLV = member.GetFmLV()
        else:
            refreshFamilyID = 0
    refreshFamilyID = doData["FamilyID"]
    refreshFmLV = doData.get("FmLV", 0)
    refreshFamilyLV = doData.get("FamilyLV", 0)
    refreshFamilyName = doData.get("FamilyName", "")
    refreshEmblemID = doData.get("EmblemID", 0)
    refreshEmblemWord = doData.get("EmblemWord", "")
    PlayerViewCache.UpdPlayerViewFamilyInfo(playerID, refreshFamilyID, refreshFamilyName, refreshEmblemID, refreshEmblemWord)
    lastFamilyID = curPlayer.GetFamilyID()
    lastFamilyLV = curPlayer.GetFamilyLV() # 仙盟等级,非职位等级
    lastFamilyLV = curPlayer.GetFamilyLV() # 公会等级,非职位等级
    lastFmLV = PlayerControl.GetFamilyMemberLV(curPlayer)
    
    if lastFamilyID != refreshFamilyID:
@@ -428,7 +1991,7 @@
        
    if curPlayer.GetFamilyName() != refreshFamilyName:
        curPlayer.SetFamilyName(refreshFamilyName)
        curPlayer.Notify_FamilyNameRefresh() #//04 36    周围玩家家族名刷新#tagPlayerFamilyNameRefresh
        #curPlayer.Notify_FamilyNameRefresh() #//04 36    周围玩家家族名刷新#tagPlayerFamilyNameRefresh
        
    if lastFmLV != refreshFmLV:
        PlayerControl.SetFamilyMemberLV(curPlayer, refreshFmLV)
@@ -446,6 +2009,7 @@
        
    if lastFamilyID != 0 and curPlayer.GetFamilyID() == 0:
        #玩家离开家族
        isVoluntarily = doData.get("isVoluntarily", 0)
        __OnLeaveFamily(curPlayer, isVoluntarily, tick)
        
    elif lastFamilyID == 0 and curPlayer.GetFamilyID() != 0:
@@ -462,10 +2026,6 @@
def __OnEnterFamily(curPlayer, tick):
    ## 进入家族触发事件
    familyMgr = DBDataMgr.GetFamilyMgr()
    familyMgr.DelPlayerReqJoinFamilyIDAll(curPlayer.GetPlayerID())
    Sync_RequestAddFamilyInfo(curPlayer)
    PlayerFamilyTaofa.OnPlayerEnterFamily(curPlayer)
    PlayerTask.UpdTaskValue(curPlayer, ChConfig.TaskType_ReqOrJoinFamily)
    return
@@ -506,1003 +2066,6 @@
    FBLogic.OnLeaveFamily(curPlayer, tick)
    return
def GetFamilyMemberHasPow(member, powerID):
    ## 仙盟成员是否有该权限
    powerDict = IpyGameDataPY.GetFuncEvalCfg("FamilyPower", 1, {})
    if str(powerID) not in powerDict:
        return False
    needMemberLV = powerDict[str(powerID)]
    return member.GetFmLV() >= needMemberLV
def Sync_FamilyInfo(curPlayer, infoPack=None):
    ## // A5 20 玩家家族信息 #tagMCRoleFamilyInfo
    familyID = curPlayer.GetFamilyID()
    if not familyID:
        return
    if not infoPack:
        infoPack = GetPack_FamilyInfo(familyID)
    NetPackCommon.SendFakePack(curPlayer, infoPack)
    return
def GetPack_FamilyInfo(familyID):
    familyMgr = DBDataMgr.GetFamilyMgr()
    curFamily = familyMgr.FindFamily(familyID)
    if not curFamily:
        return
    clientPack = ChPyNetSendPack.tagMCRoleFamilyInfo()
    clientPack.FamilyID = familyID
    clientPack.FamilyName = curFamily.GetName()
    clientPack.FamilyLV = curFamily.GetLV()
    clientPack.FamilyLVExp = curFamily.GetExp()
    clientPack.JoinReview = curFamily.GetJoinReview()
    clientPack.JoinLVMin = curFamily.GetJoinLVMin()
    clientPack.ServerID = curFamily.GetServerID()
    clientPack.EmblemID = curFamily.GetEmblemID()
    clientPack.EmblemWord = curFamily.GetEmblemWord()
    clientPack.FightPower = curFamily.GetFightPower()
    clientPack.FightPowerEx = curFamily.GetFightPowerEx()
    clientPack.Broadcast = curFamily.GetBroadcast()
    clientPack.BroadcastLen = len(clientPack.Broadcast)
    clientPack.LeaderID = curFamily.GetLeaderID()
    clientPack.MemberList = []
    for index in xrange(curFamily.GetCount()):
        member = curFamily.GetAt(index)
        memInfo = ChPyNetSendPack.tagMCRoleFamilyMember()
        memInfo.PlayerID = member.GetPlayerID()
        memInfo.JoinTime = member.GetJoinTime()
        memInfo.Name = member.GetPlayerName()
        memInfo.NameLen = len(memInfo.Name)
        memInfo.LV = member.GetLV()
        memInfo.Job = member.GetJob()
        memInfo.RealmLV = member.GetRealmLV()
        memInfo.Face = member.GetFace()
        memInfo.FacePic = member.GetFacePic()
        memInfo.TitleID = member.GetTitleID()
        memInfo.FightPower = member.GetFightPower()
        memInfo.FightPowerEx = member.GetFightPowerEx()
        memInfo.FmLV = member.GetFmLV()
        memInfo.ServerID = member.GetServerID()
        memInfo.ContribTotal = member.GetContribTotal()
        memInfo.ContribDay = member.GetContribDay()
        memInfo.DonateCntTotal = member.GetDonateCntTotal()
        memInfo.DonateCntDay = member.GetDonateCntDay()
        memInfo.OffTime = member.GetOffTime()
        clientPack.MemberList.append(memInfo)
    clientPack.MemberCount = len(clientPack.MemberList)
    return clientPack
def Broadcast_FamilyChange(familyID, changeType=FamilyChangeType_None, powerID=None, excludeIDList=None):
    ## // A5 21 家族变更 #tagMCFamilyChange
    # @param excludeIDList: 不广播的成员ID列表
    clientPack = ChPyNetSendPack.tagMCFamilyChange()
    clientPack.Type = changeType
    Broadcast_FamilyPack(familyID, clientPack, powerID, excludeIDList)
    return
def Broadcast_FamilyPack(familyID, clientPack, powerID=None, excludeIDList=None):
    ## 广播家族成员封包
    # @param powerID: 可指定只发给有该权限的成员
    # @param excludeIDList: 不广播的成员ID列表
    if not clientPack:
        return
    familyMgr = DBDataMgr.GetFamilyMgr()
    curFamily = familyMgr.FindFamily(familyID)
    if not curFamily:
        return
    playerManager = GameWorld.GetPlayerManager()
    for index in xrange(curFamily.GetCount()):
        member = curFamily.GetAt(index)
        playerID = member.GetPlayerID()
        if excludeIDList and playerID in excludeIDList:
            continue
        curPlayer = playerManager.FindPlayerByID(playerID)
        if not curPlayer:
            continue
        if powerID != None and not GetFamilyMemberHasPow(member, powerID):
            continue
        NetPackCommon.SendFakePack(curPlayer, clientPack)
    return
def NotifyAllFamilyMemberMsg(familyID, code, paramList=[], excludeIDList=None):
    ## 通知所有家族成员信息
    # @param excludeIDList: 不通知的成员ID列表
    familyMgr = DBDataMgr.GetFamilyMgr()
    curFamily = familyMgr.FindFamily(familyID)
    if not curFamily:
        return
    playerManager = GameWorld.GetPlayerManager()
    for i in xrange(curFamily.GetCount()):
        member = curFamily.GetAt(i)
        playerID = member.GetPlayerID()
        if excludeIDList and playerID in excludeIDList:
            continue
        curPlayer = playerManager.FindPlayerByID(playerID)
        if not curPlayer:
            continue
        PlayerControl.NotifyCode(curPlayer, code, paramList)
    return
#// A6 01 向玩家申请加入家族 #tagCMRequestJoinFamilyByPlayer
#
#struct    tagCMRequestJoinFamilyByPlayer
#{
#    tagHead        Head;
#    DWORD        TagPlayerID;    //目标家族玩家ID
#};
def OnRequestJoinFamilyByPlayer(index, clientData, tick):
    curPlayer = GameWorld.GetPlayerManager().GetPlayerByIndex(index)
    tagPlayerID = clientData.TagPlayerID
    tagPlayer = GameWorld.GetPlayerManager().FindPlayerByID(tagPlayerID)
    if not tagPlayer:
        GameWorld.DebugLog("对方不在线! tagPlayerID=%s" % tagPlayerID)
        return
    tagFamilyID = tagPlayer.GetFamilyID()
    if tagFamilyID <= 0:
        GameWorld.DebugLog("对方没有家族! tagPlayerID=%s" % tagPlayerID)
        return
    RequestJoinTagFamily(curPlayer, tagFamilyID)
    return
#// A6 02 申请加入家族#tagCMRequesJoinFamily
#
#struct    tagCMRequesJoinFamily
#{
#    tagHead        Head;
#    BYTE        Type;        //申请类型,0-申请;1-撤销
#    DWORD        TagFamilyID;    //目标家族ID,申请时为0代表一键申请家族任意家族
#};
def OnRequesJoinFamily(index, clientData, tick):
    curPlayer = GameWorld.GetPlayerManager().GetPlayerByIndex(index)
    tagFamilyID = clientData.TagFamilyID  # 申请进入的家族ID
    requestType = clientData.Type   # 申请类型(申请/撤销)
    # 申请加入
    if requestType == 0:
        PlayerTask.AddTaskValue(curPlayer, ChConfig.TaskType_ReqOrJoinFamily, 1)
        if not tagFamilyID:
            AutoJoinFamily(curPlayer)
        else:
            RequestJoinTagFamily(curPlayer, tagFamilyID)
    # 撤销申请
    elif requestType == 1:
        CancelJoinTagFamily(curPlayer, tagFamilyID)
    return
def CheckInJoinCD(curPlayer):
    ## 检查是否加入仙盟CD中
    leaveFamilyTime = PlayerControl.GetLeaveFamilyTimeEx(curPlayer)
    if not leaveFamilyTime:
        return False
    leaveCnt, kickedCnt, lastVoluntarily = PlayerControl.GetLeaveFamilyInfo(curPlayer)
    joinCDMinute = 0
    if lastVoluntarily:
        if leaveCnt <= 0:
            return False
        joinCDMinuteList = IpyGameDataPY.GetFuncEvalCfg("FamilyLeave", 1)
        if joinCDMinuteList:
            joinCDMinute = joinCDMinuteList[leaveCnt - 1] if len(joinCDMinuteList) >= leaveCnt else joinCDMinuteList[-1]
    else:
        if kickedCnt <= 0:
            return False
        joinCDMinuteList = IpyGameDataPY.GetFuncEvalCfg("FamilyLeave", 2)
        if joinCDMinuteList:
            joinCDMinute = joinCDMinuteList[kickedCnt - 1] if len(joinCDMinuteList) >= kickedCnt else joinCDMinuteList[-1]
    if joinCDMinute:
        cdTimes = joinCDMinute * 60
        passTimes = int(time.time()) - leaveFamilyTime
        if passTimes < cdTimes:
            GameWorld.DebugLog("加入仙盟CD中: leaveCnt=%s,kickedCnt=%s,lastVoluntarily=%s,leaveFamilyTime=%s(%s),passTimes=%s < %s"
                   % (leaveCnt, kickedCnt, lastVoluntarily, leaveFamilyTime, GameWorld.ChangeTimeNumToStr(leaveFamilyTime), passTimes, cdTimes))
            return True
    return False
def AutoJoinFamily(curPlayer):
    if curPlayer.GetFamilyID():
        return
    playerID = curPlayer.GetPlayerID()
    realmLV = curPlayer.GetOfficialRank()
    GameWorld.DebugLog("玩家一键自动加入家族! realmLV=%s" % realmLV, playerID)
    if CheckInJoinCD(curPlayer):
        return
    familyMgr = DBDataMgr.GetFamilyMgr()
    indexList = range(familyMgr.GetCount())
    random.shuffle(indexList) #打乱顺序
    for index in indexList:
        family = familyMgr.GetAt(index)
        if not family:
            continue
        #familyID = family.GetID()
        lvMin = family.GetJoinLVMin()
        if lvMin and realmLV < lvMin:
            #GameWorld.DebugLog("    官职不足的不处理! familyID=%s,lvMin=%s" % (familyID, lvMin), playerID)
            continue
        if family.GetJoinReview():
            #GameWorld.DebugLog("    需要审核的不处理! familyID=%s" % familyID, playerID)
            continue
        MemberMax = GetFamilySetting(family.GetLV(), "MemberMax")
        if family.GetCount() >= MemberMax:
            #GameWorld.DebugLog("    成员已满的不处理! familyID=%s" % familyID, playerID)
            continue
        #直接加入
        DoPlayerJionFamily(family, playerID, curPlayer)
        return
    # 可再扩展自动请求,暂时不处理
    GameWorld.DebugLog("没有可自动进入的仙盟!")
    PlayerControl.NotifyCode(curPlayer, "QuickEnterFamilyFail")
    return
def GetFamilySetting(familyLV, fieldName):
    ## 获取仙盟等级表对应字段值
    if not fieldName:
        return 0
    ipyData = IpyGameDataPY.GetIpyGameData("Family", familyLV)
    if not ipyData:
        return 0
    attrName = "Get%s" % fieldName
    if not hasattr(ipyData, attrName):
        return 0
    return getattr(ipyData, attrName)()
def RequestJoinTagFamily(curPlayer, familyID):
    ## 申请加入
    if CheckInJoinCD(curPlayer):
        return
    playerID = curPlayer.GetPlayerID()
    if curPlayer.GetFamilyID():
        GameWorld.DebugLog('已经有仙盟不能再申请加入! familyID=%s' % curPlayer.GetFamilyID(), playerID)
        return
    familyMgr = DBDataMgr.GetFamilyMgr()
    reqFamilyIDList = familyMgr.GetPlayerReqJoinFamilyIDList(playerID)
    if playerID in reqFamilyIDList:
        GameWorld.DebugLog('已经在申请加入仙盟列表中! familyID=%s' % familyID, playerID)
        return
    maxReqFamilyCnt = IpyGameDataPY.GetFuncCfg("FamilyReqJoin", 2)
    if len(reqFamilyIDList) >= maxReqFamilyCnt:
        GameWorld.DebugLog('已经达到最大申请加入仙盟数! %s, %s' % (len(reqFamilyIDList), reqFamilyIDList), playerID)
        return
    tagFamily = familyMgr.FindFamily(familyID)
    if not tagFamily:
        return
    lvMin = tagFamily.GetJoinLVMin()
    if curPlayer.GetOfficialRank() < lvMin:
        GameWorld.DebugLog('官职未达到该仙盟加入最低限制! realmLV=%s < %s' % (curPlayer.GetOfficialRank(), lvMin), playerID)
        return
    # 需要审核,满员后端不限制申请,由前端自行决定是否可申请
    if tagFamily.GetJoinReview():
        reqPlayerIDDict = tagFamily.GetReqJoinPlayerInfo()
        if playerID not in reqPlayerIDDict:
            maxReqPlayerCnt = IpyGameDataPY.GetFuncCfg("FamilyReqJoin", 1)
            if len(reqPlayerIDDict) >= maxReqPlayerCnt:
                GameWorld.DebugLog('目标仙盟申请加入数已满! %s, %s' % (len(reqFamilyIDList), reqFamilyIDList), playerID)
                PlayerControl.NotifyCode(curPlayer, "jiazu_pan_141056")
                return
        tagFamily.AddReqJoinPlayerID(playerID)
        # 广播给有招人权限的
        Broadcast_FamilyPack(familyID, GetPack_FamilyReqJoinInfo(familyID), FamilyPowerID_Call)
        #jiazu_pan_500807:申请入帮成功!请等待帮会管理人员审批!
        PlayerControl.NotifyCode(curPlayer, "jiazu_pan_500807")
        Sync_RequestAddFamilyInfo(curPlayer)
        return
    # 不需要审核,自动加入
    memberMax = GetFamilySetting(tagFamily.GetLV(), "MemberMax")
    if tagFamily.GetCount() >= memberMax:
        GameWorld.DebugLog('目标仙盟成员已满! familyLV=%s,memberMax=%s' % (tagFamily.GetLV(), memberMax), playerID)
        return
    DoPlayerJionFamily(tagFamily, playerID, curPlayer)
    return
def CancelJoinTagFamily(curPlayer, familyID):
    # 撤销申请
    familyMgr = DBDataMgr.GetFamilyMgr()
    playerID = curPlayer.GetPlayerID()
    tagFamily = familyMgr.FindFamily(familyID)
    if tagFamily:
        tagFamily.DelReqJoinPlayerID(playerID)
    familyMgr.DelPlayerReqJoinFamilyID(playerID, familyID)
    Broadcast_FamilyPack(familyID, GetPack_FamilyReqJoinInfo(familyID), FamilyPowerID_Call)
    Sync_RequestAddFamilyInfo(curPlayer)
    return
def Sync_RequestAddFamilyInfo(curPlayer, isForce=True):
    ## 通知当前申请加入的哪些家族
    playerID = curPlayer.GetPlayerID()
    familyMgr = DBDataMgr.GetFamilyMgr()
    reqFamilyIDList = familyMgr.GetPlayerReqJoinFamilyIDList(playerID)
    # 非强制通知时没有申请记录的可不通知,如登录
    if not isForce and not reqFamilyIDList:
        return
    clientPack = ChPyNetSendPack.tagMCNotifyRequestJoinFamilyInfo()
    clientPack.Clear()
    clientPack.RequestJoinFamilyIDList = reqFamilyIDList
    clientPack.RequestCount = len(clientPack.RequestJoinFamilyIDList)
    NetPackCommon.SendFakePack(curPlayer, clientPack)
    return
def GetPack_FamilyReqJoinInfo(familyID):
    ## 获取 // A5 22 家族申请加入的玩家信息 #tagMCFamilyReqJoinInfo
    familyMgr = DBDataMgr.GetFamilyMgr()
    curFamily = familyMgr.FindFamily(familyID)
    if not curFamily:
        return
    reqPlayerIDDict = curFamily.GetReqJoinPlayerInfo()
    playerManager = GameWorld.GetPlayerManager()
    clientPack = ChPyNetSendPack.tagMCFamilyReqJoinInfo()
    clientPack.ReqJoinList = []
    for playerID, reqTime in reqPlayerIDDict.items():
        curPlayer = playerManager.FindPlayerByID(playerID)
        reqInfo = ChPyNetSendPack.tagMCFamilyReqJoinPlayer()
        reqInfo.PlayerID = playerID
        reqInfo.ReqTime = reqTime
        if curPlayer:
            reqInfo.IsOnLine = True
        viewCache = PlayerViewCache.FindViewCache(playerID)
        if viewCache:
            reqInfo.Name = viewCache.GetPlayerName()
            reqInfo.NameLen = len(reqInfo.Name)
            reqInfo.LV = viewCache.GetLV()
            reqInfo.Job = viewCache.GetJob()
            reqInfo.RealmLV = viewCache.GetRealmLV()
            reqInfo.Face = viewCache.GetFace()
            reqInfo.FacePic = viewCache.GetFacePic()
            reqInfo.TitleID = viewCache.GetTitleID()
            reqInfo.FightPower = viewCache.GetFightPower()
            reqInfo.FightPowerEx = viewCache.GetFightPowerEx()
            reqInfo.ServerID = viewCache.GetServerID()
        clientPack.ReqJoinList.append(reqInfo)
        if len(clientPack.ReqJoinList) >= 100:
            break
    clientPack.ReqCnt = len(clientPack.ReqJoinList)
    return clientPack
#// A6 21 审核请求加入家族 #tagCMJoinFamilyReply
#
#struct tagCMJoinFamilyReply
#{
#    tagHead    Head;
#    DWORD    TagPlayerID;    //被审核玩家ID 0则代表全部
#    BYTE    IsOK;        //是否同意其加入
#};
def OnJoinFamilyReply(index, clientData, tick):
    curPlayer = GameWorld.GetPlayerManager().GetPlayerByIndex(index)
    tagPlayerID = clientData.TagPlayerID
    isOK = clientData.IsOK
    playerID = curPlayer.GetPlayerID()
    familyID = curPlayer.GetFamilyID()
    if familyID <= 0:
        return
    familyMgr = DBDataMgr.GetFamilyMgr()
    family = familyMgr.FindFamily(familyID)
    if not family:
        return
    curMember = family.FindMember(playerID)
    if not curMember:
        return
    if not GetFamilyMemberHasPow(curMember, FamilyPowerID_Call):
        #GameWorld.DebugLog("没有招人权限,无法审核人员入盟!", playerID)
        PlayerControl.NotifyCode(curPlayer, "XW_JZ_InviteErr_Popedom")
        return
    GameWorld.DebugLog("审核入盟申请: tagPlayerID=%s,familyID=%s,isOK=%s" % (tagPlayerID, familyID, isOK), playerID)
    reqPlayerIDDict = family.GetReqJoinPlayerInfo()
    tagPlayerIDList = reqPlayerIDDict.keys()
    if tagPlayerID:
        if tagPlayerID not in reqPlayerIDDict:
            GameWorld.DebugLog("不存在该申请人员! tagPlayerID=%s" % tagPlayerID)
            return
        tagPlayerIDList = [tagPlayerID]
    if not tagPlayerIDList:
        GameWorld.DebugLog("没有申请人员!")
        return
    playerManager = GameWorld.GetPlayerManager()
    # 拒绝
    if not isOK:
        for tagPlayerID in tagPlayerIDList:
            family.DelReqJoinPlayerID(tagPlayerID)
            tagPlayer = playerManager.FindPlayerByID(tagPlayerID)
            if not tagPlayer:
                continue
            Sync_RequestAddFamilyInfo(tagPlayer)
            #jiazu_pan_592934:{%S}拒绝了您的入帮申请
            PlayerControl.NotifyCode(tagPlayer, "jiazu_pan_592934", [family.GetName()])
        Broadcast_FamilyPack(familyID, GetPack_FamilyReqJoinInfo(familyID), FamilyPowerID_Call)
        return
    # 处理同意
    offlinePlayerCanJoin = IpyGameDataPY.GetFuncCfg("FamilyReqJoin", 3)
    MemberMax = GetFamilySetting(family.GetLV(), "MemberMax")
    joinOKPlayerIDList = []
    for tagPlayerID in tagPlayerIDList:
        if family.GetCount() >= MemberMax:
            PlayerControl.NotifyCode(curPlayer, "jiazu_lhs_202580")
            break
        tagPlayer = playerManager.FindPlayerByID(tagPlayerID)
        #申请目标不在线
        if not tagPlayer:
            if not offlinePlayerCanJoin:
                GameWorld.DebugLog("离线玩家无法加入仙盟! tagPlayerID=%s" % tagPlayerID, playerID)
                PlayerControl.NotifyCode(curPlayer, "jiazu_hwj35_367906")
                continue
        if family.FindMember(tagPlayerID):
            GameWorld.DebugLog("已经是本盟成员! tagPlayerID=%s" % tagPlayerID, playerID)
            PlayerControl.NotifyCode(curPlayer, "XW_JZ_InviteErr_Repeat")
            continue
        tagFamilyID = familyMgr.GetPlayerFamilyID(tagPlayerID)
        if tagFamilyID:
            GameWorld.DebugLog("已经加入其他仙盟! tagPlayerID=%s,tagFamilyID=%s" % (tagPlayerID, tagFamilyID), playerID)
            PlayerControl.NotifyCode(curPlayer, "XW_JZ_InviteErr_Repeat")
            continue
        DoPlayerJionFamily(family, tagPlayerID, tagPlayer, broadcastFamilyChange=False)
        joinOKPlayerIDList.append(tagPlayerID)
    if not joinOKPlayerIDList:
        return
    Broadcast_FamilyChange(familyID, FamilyChangeType_MemJoin, excludeIDList=joinOKPlayerIDList)
    Broadcast_FamilyPack(familyID, GetPack_FamilyReqJoinInfo(familyID), FamilyPowerID_Call)
    return
#// A6 22 修改收人方式 #tagCMChangeFamilyJoin
#
#struct    tagCMChangeFamilyJoin
#{
#    tagHead         Head;
#    BYTE        JoinReview;    //成员加入是否需要审核,默认0自动加入
#    WORD        JoinLVMin;    //限制最低可加入的玩家等级
#};
def OnChangeFamilyJoin(index, clientData, tick):
    curPlayer = GameWorld.GetPlayerManager().GetPlayerByIndex(index)
    joinReview = clientData.JoinReview
    joinLVMin = clientData.JoinLVMin # 官职
    playerID = curPlayer.GetPlayerID()
    familyID = curPlayer.GetFamilyID()
    if familyID <= 0:
        return
    familyMgr = DBDataMgr.GetFamilyMgr()
    family = familyMgr.FindFamily(familyID)
    if not family:
        return
    curMember = family.FindMember(playerID)
    if not curMember:
        return
    if not GetFamilyMemberHasPow(curMember, FamilyPowerID_Call):
        GameWorld.DebugLog("没有招人权限", playerID)
        return
    GameWorld.DebugLog("修改招人设置: familyID=%s,joinReview=%s,joinLVMin=%s" % (familyID, joinReview, joinLVMin), playerID)
    family.SetJoinReview(joinReview)
    family.SetJoinLVMin(joinLVMin)
    Sync_FamilyInfo(curPlayer)
    Broadcast_FamilyChange(familyID, FamilyChangeType_JoinSet, FamilyPowerID_Call, excludeIDList=[playerID])
    return
#// A6 23 修改家族公告 #tagCMChangeFamilyBroadcast
#
#struct    tagCMChangeFamilyBroadcast
#{
#    tagHead        Head;
#    char        Msg[200];
#};
def OnChangeFamilyBroadcast(index, clientData, tick):
    curPlayer = GameWorld.GetPlayerManager().GetPlayerByIndex(index)
    broadcast = clientData.Msg
    playerID = curPlayer.GetPlayerID()
    familyID = curPlayer.GetFamilyID()
    if familyID <= 0:
        return
    familyMgr = DBDataMgr.GetFamilyMgr()
    family = familyMgr.FindFamily(familyID)
    if not family:
        return
    curMember = family.FindMember(playerID)
    if not curMember:
        return
    if not GetFamilyMemberHasPow(curMember, FamilyPowerID_Broadcast):
        GameWorld.DebugLog("没有修改公告权限", playerID)
        return
    family.SetBroadcast(broadcast)
    GameWorld.DebugLog('更改仙盟公告: Family=%s,公告=%s' % (GameWorld.CodeToGbk(family.GetName()), GameWorld.CodeToGbk(broadcast)), playerID)
    Sync_FamilyInfo(curPlayer)
    Broadcast_FamilyChange(familyID, FamilyChangeType_Broadcast, excludeIDList=[playerID])
    return
#// A6 24 修改家族徽章 #tagCMChangeFamilyEmblem
#
#struct    tagCMChangeFamilyEmblem
#{
#    tagHead        Head;
#    BYTE        EmblemID;    // 更换的徽章ID
#    char        EmblemWord[3];    // 徽章文字
#};
def OnChangeFamilyEmblem(index, clientData, tick):
    curPlayer = GameWorld.GetPlayerManager().GetPlayerByIndex(index)
    changeEmblemID = clientData.EmblemID
    emblemWord = clientData.EmblemWord
    PlayerFamilyEmblem.OnChangeFamilyEmblem(curPlayer, changeEmblemID, emblemWord)
    return
#// A6 25 修改家族成员职位 #tagCMChangeFamilyMemLV
#
#struct    tagCMChangeFamilyMemLV
#{
#    tagHead        Head;
#    DWORD        PlayerID; // 目标成员ID
#    BYTE        FmLV;  // 变更为xx职位
#};
def OnChangeFamilyMemLV(index, clientData, tick):
    curPlayer = GameWorld.GetPlayerManager().GetPlayerByIndex(index)
    OnChangeFamilyMemberLV(curPlayer, clientData.PlayerID, clientData.FmLV)
    return
def OnChangeFamilyMemberLV(curPlayer, tagID, changeFmlv, isGMOP=False):
    '''变更成员职位
    @param curPlayer: 操作的玩家
    @param tagID: 目标成员ID
    @param changeFmlv: 修改为xx职位
    @param isGMOP: 是否是GM后台发起的,如果是GM发起的,一般curPlayer传入的为目标成员ID实例
    '''
    if not curPlayer:
        return
    playerID = curPlayer.GetPlayerID()
    familyID = curPlayer.GetFamilyID()
    if familyID <= 0:
        return
    familyMgr = DBDataMgr.GetFamilyMgr()
    family = familyMgr.FindFamily(familyID)
    if not family:
        return
    curMember = family.FindMember(playerID)
    if not curMember:
        return
    if changeFmlv < 0 or changeFmlv > IPY_PlayerDefine.fmlLeader:
        GameWorld.DebugLog("不存在该职位等级! changeFmlv=%s" % changeFmlv)
        return
    # 非GM操作的需检查权限
    if not isGMOP:
        if not GetFamilyMemberHasPow(curMember, FamilyPowerID_ChangeFmlv):
            return
        if playerID == tagID:
            GameWorld.DebugLog("不能任免自己的家族职位", playerID)
            return
    tagMember = family.FindMember(tagID)
    if tagMember == None:
        GameWorld.DebugLog("更改家族成员职位时目标成员不存在! tagID=%s" % tagID, playerID)
        return
    if not isGMOP:
        if curMember.GetFmLV() != IPY_PlayerDefine.fmlLeader:
            if changeFmlv >= curMember.GetFmLV():
                GameWorld.DebugLog("修改的职位不能比自己高或平级! changeFmlv=%s" % changeFmlv, playerID)
                return
            if tagMember.GetFmLV() >= curMember.GetFmLV():
                GameWorld.DebugLog("修改的目标成员职位不能比自己高或平级! tagFmlv=%s" % tagMember.GetFmLV(), playerID)
                return
    if changeFmlv == IPY_PlayerDefine.fmlLeader:
        changeType = FamilyChangeType_LeaderChange
        ChangeFamilyLeader(family, tagMember)
    else:
        fmLVMemCnt = 0
        for index in range(family.GetCount()):
            familyMember = family.GetAt(index)
            if familyMember.GetFmLV() != changeFmlv:
                continue
            fmLVMemCnt += 1
        maxCnt = GetFamilySetting(family.GetLV(), Def_FmlSetAttrName.get(changeFmlv, ""))
        if fmLVMemCnt >= maxCnt:
            # jiazu_hwj35_272921 改为 jiazu_chenxin_31379
            PlayerControl.NotifyCode(curPlayer, "jiazu_chenxin_31379")
            GameWorld.DebugLog("目前该职位的人数已经达到上限! changeFmlv=%s,fmLVMemCnt=%s >= %s" % (changeFmlv, fmLVMemCnt, maxCnt))
            return
        changeType = FamilyChangeType_MemFmlvChange
        ChangeFamilyMemberLv(tagMember, changeFmlv)
    if isGMOP:
        family.SetBroadcast("")
    Sync_FamilyInfo(curPlayer)
    Broadcast_FamilyChange(familyID, changeType, excludeIDList=[playerID, tagID])
    return True
def ChangeFamilyLeader(family, newLeaderMem):
    ## 变更家族族长
    familyID = family.GetID()
    befLeaderID = family.GetLeaderID()
    newLeaderID = newLeaderMem.GetPlayerID()
    if befLeaderID == newLeaderID:
        return
    befLeaderMem = family.FindMember(befLeaderID)
    if befLeaderMem:
        #把原族长降为普通成员
        ChangeFamilyMemberLv(befLeaderMem, IPY_PlayerDefine.fmlMember)
    family.SetLeaderID(newLeaderID)
    ChangeFamilyMemberLv(newLeaderMem, IPY_PlayerDefine.fmlLeader)
    GameWorld.Log("家族设置新族长! familyID=%s,newLeaderID=%s,befLeaderID=%s" % (familyID, newLeaderID, befLeaderID))
    return
def ChangeFamilyMemberLv(tagMember, changeFamilyLV):
    ## 修改成员职位,只做修改逻辑,不做验证,验证由各调用入口自行验证
    familyID = tagMember.GetFamilyID()
    tagID = tagMember.GetPlayerID()
    memName = tagMember.GetPlayerName()
    befFamilyLV = tagMember.GetFmLV()
    tagMember.SetFmLV(changeFamilyLV)
    tagPlayer = GameWorld.GetPlayerManager().FindPlayerByID(tagID)
    if tagPlayer:
        MapServer_FamilyRefresh(tagPlayer, familyID)
        Sync_FamilyInfo(tagPlayer)
        if GetFamilyMemberHasPow(tagMember, FamilyPowerID_Call):
            NetPackCommon.SendFakePack(tagPlayer, GetPack_FamilyReqJoinInfo(familyID))
    # 记录家族事件记录信息
    tick = GameWorld.GetGameWorld().GetTick()
    AddFamilyActionNote(memName, familyID, ShareDefine.Def_ActionType_FamilyEvent,
                        [ShareDefine.Def_FamilyActionEvent_MemberChange, ShareDefine.Def_FamilyMemberChange_FMLV, changeFamilyLV, befFamilyLV], tick)
    #xx被任命为xx
    NotifyAllFamilyMemberMsg(familyID, "XW_JZ_AppointFamily", [memName, changeFamilyLV])
    #GetFamilyMgr().SetSyncCrossFamilyUpd(familyMember.GetFamilyID(), familyMember.GetPlayerID(), syncNow=True) # 成员职位变更
    return
#// A6 26 请求家族成员列表 #tagCMGetFamilyInfo
#
#struct    tagCMGetFamilyInfo
#{
#    tagHead        Head;
#};
def OnGetFamilyInfo(index, clientData, tick):
    curPlayer = GameWorld.GetPlayerManager().GetPlayerByIndex(index)
    Sync_FamilyInfo(curPlayer)
    return
#// A6 03 离开家族 #tagCMLeaveFamily
#
#struct    tagCMLeaveFamily
#{
#    tagHead        Head;
#};
def OnLeaveFamily(index, clientData, tick):
    curPlayer = GameWorld.GetPlayerManager().GetPlayerByIndex(index)
    playerID = curPlayer.GetPlayerID()
    familyID = curPlayer.GetFamilyID()
    if familyID <= 0:
        return
    familyMgr = DBDataMgr.GetFamilyMgr()
    family = familyMgr.FindFamily(familyID)
    if not family:
        return
    curMember = family.FindMember(playerID)
    if not curMember:
        return
    familyLV = curMember.GetFmLV()  # ְλ
    if family.GetCount() > 1 and familyLV == IPY_PlayerDefine.fmlLeader:
        GameWorld.DebugLog("族长在成员人数大于1时不能直接退出家族", playerID)
        return
    # 功能限制退出仙盟
    # ...
    # 进出时间限制暂不做,等正式功能再补
    #PlayerControl.SetLeaveFamilyTime(curPlayer, updTime)
    # 执行退出
    GameWorld.DebugLog("离开家族! familyID=%s" % familyID, playerID)
    family.DeleteMember(playerID)
    AddFamilyActionNote(curPlayer.GetName(), familyID, ShareDefine.Def_ActionType_FamilyEvent,
                        [ShareDefine.Def_FamilyActionEvent_MemberChange, ShareDefine.Def_FamilyMemberChange_Leave], tick)
    #XW_JZ_LeaveFamily   <n color="0,190,255">{%S1%}</n><n color="255,255,0">退出了家族!</n>  25  -   -
    NotifyAllFamilyMemberMsg(familyID, "XW_JZ_LeaveFamily", [curPlayer.GetName()])
    __DoPlayerLeaveFamilyByID(family, playerID, curPlayer)
    MapServer_FamilyRefresh(curPlayer, 0, 1)
    if family.GetCount() == 0:
        #玩家离开后, 家族没有人了 , 删除这个家族
        familyMgr.DelFamily(familyID)
        return
    Broadcast_FamilyChange(familyID, FamilyChangeType_MemLeave)
    return
#// A6 05 删除家族成员 #tagCMDeleteFamilyMember
#
#struct    tagCMDeleteFamilyMember
#{
#    tagHead        Head;
#    DWORD        MemberID;
#};
def OnDeleteFamilyMember(index, clientData, tick):
    curPlayer = GameWorld.GetPlayerManager().GetPlayerByIndex(index)
    tagMemberID = clientData.MemberID
    playerID = curPlayer.GetPlayerID()
    if playerID == tagMemberID:
        return
    familyID = curPlayer.GetFamilyID()
    if familyID <= 0:
        return
    familyMgr = DBDataMgr.GetFamilyMgr()
    family = familyMgr.FindFamily(familyID)
    if not family:
        return
    curMember = family.FindMember(playerID)
    if not curMember:
        return
    if not GetFamilyMemberHasPow(curMember, FamilyPowerID_Kick):
        GameWorld.DebugLog("没有踢人权限!")
        return
    tagMember = family.FindMember(tagMemberID)
    if not tagMember:
        return
    curFmlv = curMember.GetFmLV()
    tagFmlv = tagMember.GetFmLV()
    if tagFmlv >= curFmlv:
        GameWorld.DebugLog("只能踢比自己职位低的成员! tagMemberID=%s,tagFmlv(%s) >= curFmlv(%s)" % (tagMemberID, tagFmlv, curFmlv), playerID)
        return
    # 功能限制踢人
    # ...
    tagPlayerName = tagMember.GetPlayerName()  # 被踢玩家名
    tagPlayerID = tagMember.GetPlayerID()  # 被踢玩家ID
    family.DeleteMember(tagPlayerID)
    AddFamilyActionNote(tagPlayerName, familyID, ShareDefine.Def_ActionType_FamilyEvent,
                        [ShareDefine.Def_FamilyActionEvent_MemberChange, ShareDefine.Def_FamilyMemberChange_KickOut], tick)
    #XW_JZ_LeaveFamily   <n color="0,190,255">{%S1%}</n><n color="255,255,0">退出了家族!</n>  25  -   -
    NotifyAllFamilyMemberMsg(familyID, "XW_JZ_LeaveFamily", [tagPlayerName])
    #删除玩家
    tagPlayer = GameWorld.GetPlayerManager().FindPlayerByID(tagMemberID)
    __DoPlayerLeaveFamilyByID(family, tagPlayerID, tagPlayer)
    if tagPlayer:
        MapServer_FamilyRefresh(tagPlayer, 0)
    Broadcast_FamilyChange(familyID, FamilyChangeType_MemLeave)
    return
def __DoPlayerLeaveFamilyByID(curFamily, leavePlayerID, tagPlayer=None):
    ## 有玩家离开家族处理,主要针对家族层级的,玩家个人的在 __OnLeaveFamily 处理
    PlayerFamilyTaofa.OnFamilyMemberLeave(curFamily, leavePlayerID, tagPlayer)
    return
#// A6 11 家族改名 #tagCMRenameFamily
#
#struct tagCMRenameFamily
#{
#    tagHead        Head;
#    BYTE        NewNameLen;
#    char        NewName[NewNameLen];
#    BYTE        ItemIndex;  //改名物品在背包中的位置
#};
def UpdateFamilyName(index, clientData, tick):
    curPlayer = GameWorld.GetPlayerManager().GetPlayerByIndex(index)
    newName = clientData.NewName
    #itemIndex = clientData.ItemIndex
    playerID = curPlayer.GetPlayerID()
    familyID = curPlayer.GetFamilyID()
    if familyID <= 0:
        return
    familyMgr = DBDataMgr.GetFamilyMgr()
    family = familyMgr.FindFamily(familyID)
    if not family:
        return
    curMember = family.FindMember(playerID)
    if not curMember:
        return
    if curMember.GetFmLV() != IPY_PlayerDefine.fmlLeader:
        GameWorld.DebugLog("非盟主不可改名!", playerID)
        return
    cdHours = IpyGameDataPY.GetFuncCfg("FamilyRename", 2)
    if cdHours:
        cdSeconds = cdHours * 3600
        curTime = int(time.time())
        dataAction = GetFamilyDataAction(familyID)
        lastRenameTime = GetRenameTime(dataAction)
        if lastRenameTime and (curTime - lastRenameTime) < cdSeconds:
            GameWorld.DebugLog("仙盟改名CD中! lastRenameTime=%s,cdHours=%s" % (GameWorld.ChangeTimeNumToStr(lastRenameTime), cdHours))
            return
    familyName = CheckInputFamilyName(curPlayer, newName)
    if not familyName:
        return
    moneyType, moneyValue = IpyGameDataPY.GetFuncEvalCfg("FamilyRename", 1)
    if moneyType and moneyValue and not PlayerControl.PayMoney(curPlayer, moneyType, moneyValue, "FamilyRename"):
        return
    family.SetName(familyName)
    infoPack = GetPack_FamilyInfo(familyID)
    playerManager = GameWorld.GetPlayerManager()
    for index in xrange(family.GetCount()):
        member = family.GetAt(index)
        memID = member.GetPlayerID()
        player = playerManager.FindPlayerByID(memID)
        if not player:
            continue
        Sync_FamilyInfo(player, infoPack)
        player.SetFamilyName(familyName)
        #player.Notify_FamilyNameRefresh() #//04 36    周围玩家家族名刷新#tagPlayerFamilyNameRefresh
    if cdHours:
        SetRenameTime(dataAction, curTime)
        SendFamilyActionInfo(None, familyID, ShareDefine.Def_ActionType_FamilyData)
    return
#// A6 20 搜索家族列表 #tagCMViewFamilyPage
#
#struct    tagCMViewFamilyPage
#{
#    tagHead        Head;
#    BYTE        MsgLen;        //模糊搜索家族,如果输入为空,则为不限制该条件
#    char        Msg[MsgLen];    //size = MsgLen
#    BYTE        PageIndex;    //查询第X页索引,0~n
#    BYTE        ShowCount;    //每页数量,前端可自行指定,最大50
#};
def OnViewFamilyPage(index, clientData, tick):
    curPlayer = GameWorld.GetPlayerManager().GetPlayerByIndex(index)
    msg = clientData.Msg
    pageIndex = clientData.PageIndex
    showCount = min(clientData.ShowCount, 50)
    familyMgr = DBDataMgr.GetFamilyMgr()
    familyCount = familyMgr.GetCount()
    totalPage = 0
    if not msg:
        startIndex = pageIndex * showCount
        endIndex = startIndex + showCount - 1
        if familyCount > 0:
            totalPage = GameWorld.GetIntUpper(familyCount, showCount)
    # 有指定搜索内容的后端固定返回单页
    else:
        pageIndex = 0
        showCount = 20
        totalPage = 1
        startIndex = 0
        endIndex = familyCount - 1
    clientPack = ChPyNetSendPack.tagMCFamilyViewList()
    clientPack.Msg = msg
    clientPack.MsgLen = len(clientPack.Msg)
    clientPack.PageIndex = pageIndex
    clientPack.ShowCount = showCount
    clientPack.TotalPage = totalPage
    clientPack.FamilyList = []
    for index in range(startIndex, endIndex + 1):
        if index >= familyCount:
            break
        family = familyMgr.GetAt(index)
        if not family:
            continue
        if msg:
            if msg in family.GetName() or msg == str(family.GetID()):
                pass
            else:
                continue
        familyView = ChPyNetSendPack.tagMCFamilyView()
        familyView.Rank = index + 1
        familyView.FamilyID = family.GetID()
        familyView.FamilyName = family.GetName()
        familyView.FamilyNameLen = len(familyView.FamilyName)
        familyView.LeaderID = family.GetLeaderID()
        leaderMember = family.FindMember(familyView.LeaderID)
        familyView.LeaderName = leaderMember.GetPlayerName() if leaderMember else ""
        familyView.LeaderNameLen = len(familyView.LeaderName)
        familyView.FamilyLV = family.GetLV()
        familyView.JoinReview = family.GetJoinReview()
        familyView.JoinLVMin = family.GetJoinLVMin()
        familyView.ServerID = family.GetServerID()
        familyView.EmblemID = family.GetEmblemID()
        familyView.EmblemWord = family.GetEmblemWord()
        familyView.FightPower = family.GetFightPower()
        familyView.FightPowerEx = family.GetFightPowerEx()
        familyView.MemberCount = family.GetCount()
        clientPack.FamilyList.append(familyView)
        clientPack.FamilyCount = len(clientPack.FamilyList)
        if clientPack.FamilyCount >= showCount:
            break
    NetPackCommon.SendFakePack(curPlayer, clientPack)
    return
#// A6 12 家族捐献货币 #tagCMFamilyMoneyDonate
#
#struct     tagCMFamilyMoneyDonate
#{
#    tagHead        Head;
#    BYTE        DonateType;    // 捐献类型
#};
def OnFamilyMoneyDonate(index, clientData, tick):
    curPlayer = GameWorld.GetPlayerManager().GetPlayerByIndex(index)
    donateType = clientData.DonateType
    playerID = curPlayer.GetPlayerID()
    familyID = curPlayer.GetFamilyID()
    if familyID <= 0:
        return
    familyMgr = DBDataMgr.GetFamilyMgr()
    curFamily = familyMgr.FindFamily(familyID)
    if not curFamily:
        return
    curMember = curFamily.FindMember(playerID)
    if not curMember:
        return
    ipyData = IpyGameDataPY.GetIpyGameData("FamilyDonate", donateType)
    if not ipyData:
        return
    dailyCntMax = ipyData.GetDailyCnt()
    donateCnt = curPlayer.NomalDictGetProperty(ChConfig.Def_Player_Dict_FamilyDonateCnt % donateType)
    if donateCnt >= dailyCntMax:
        GameWorld.DebugLog("今日捐献次数已达上限! donateType=%s,donateCnt=%s >= %s" % (donateType, donateCnt, dailyCntMax), playerID)
        return
    moneyType = ipyData.GetMoneyType()
    moneyValue = ipyData.GetMoneyValue()
    if not moneyType or not moneyValue:
        return
    if not PlayerControl.PayMoney(curPlayer, moneyType, moneyValue, "FamilyMoneyDonate"):
        return
    awardItemList = ipyData.GetAwardItemList()
    donateCnt += 1
    PlayerControl.NomalDictSetProperty(curPlayer, ChConfig.Def_Player_Dict_FamilyDonateCnt % donateType, donateCnt)
    SyncDonateCntInfo(curPlayer)
    # 增加成员捐献次数记录
    memDonateCntDay = curMember.GetDonateCntDay() + 1
    memDonateCntTotal = min(curMember.GetDonateCntTotal() + 1, ChConfig.Def_UpperLimit_DWord)
    curMember.SetDonateCntDay(memDonateCntDay)
    curMember.SetDonateCntTotal(memDonateCntTotal)
    GameWorld.DebugLog("家族捐献: donateType=%s,donateCnt=%s,%s,memDonateCntDay=%s,memDonateCntDay=%s"
                       % (donateType, donateCnt, awardItemList, memDonateCntDay, memDonateCntTotal), playerID)
    ItemControler.GivePlayerItemOrMail(curPlayer, awardItemList, event=["FamilyMoneyDonate", False, {}])
    return
def ResetDailyDonateCnt(curPlayer):
    isReset = False
    ipyDataMgr = IpyGameDataPY.IPY_Data()
@@ -1527,168 +2090,8 @@
        donateCntList.append(donateCnt)
    if not donateCntList:
        return
    clientPack = ObjPool.GetPoolMgr().acquire(ChPyNetSendPack.tagSCDonateCntInfo)
    clientPack = ChPyNetSendPack.tagSCDonateCntInfo()
    clientPack.DonateCntList = donateCntList
    clientPack.Count = len(clientPack.DonateCntList)
    NetPackCommon.SendFakePack(curPlayer, clientPack)
    return
def AddFamilyExp(curPlayer, addExp):
    ## 增加玩家家族经验
    playerID = curPlayer.GetPlayerID()
    familyID = curPlayer.GetFamilyID()
    if familyID <= 0:
        return
    familyMgr = DBDataMgr.GetFamilyMgr()
    curFamily = familyMgr.FindFamily(familyID)
    if not curFamily:
        return
    curLV = curFamily.GetLV()
    curExp = curFamily.GetExp()
    updLV = curLV
    updExp = curExp + addExp
    GameWorld.DebugLog("增加仙盟经验: curLV=%s,curExp=%s,addExp=%s,updExp=%s" % (curLV, curExp, addExp, updExp), playerID)
    ipyData = IpyGameDataPY.GetIpyGameData("Family", curLV)
    lvUPExp = ipyData.GetNeedExp()
    while lvUPExp and updExp >= lvUPExp:
        ipyData = IpyGameDataPY.GetIpyGameDataNotLog("Family", updLV + 1)
        if not ipyData:
            break
        updLV += 1
        updExp -= lvUPExp
        lvUPExp = ipyData.GetNeedExp()
        GameWorld.DebugLog("    仙盟升级: updLV=%s,updExp=%s,lvUPExp=%s" % (updLV, updExp, lvUPExp), playerID)
    curFamily.SetLV(updLV)
    curFamily.SetExp(updExp)
    Sync_FamilyInfo(curPlayer)
    Broadcast_FamilyChange(familyID, FamilyChangeType_FamilyLVExp, excludeIDList=[playerID])
    return True
def AddFamilyContrib(curPlayer, addContribValue):
    ## 增加玩家累计家族贡献
    playerID = curPlayer.GetPlayerID()
    familyID = curPlayer.GetFamilyID()
    if familyID <= 0:
        return
    familyMgr = DBDataMgr.GetFamilyMgr()
    curFamily = familyMgr.FindFamily(familyID)
    if not curFamily:
        return
    curMember = curFamily.FindMember(playerID)
    if not curMember:
        return
    contribDay = curMember.GetContribDay() + addContribValue
    contribTotal = min(curMember.GetContribTotal() + addContribValue, ChConfig.Def_UpperLimit_DWord)
    curMember.SetContribDay(contribDay)
    curMember.SetContribTotal(contribTotal)
    GameWorld.DebugLog("增加成员贡献: familyID=%s,addContribValue=%s,contribDay=%s,contribTotal=%s" % (familyID, addContribValue, contribDay, contribTotal), playerID)
    Sync_FamilyInfo(curPlayer)
    Broadcast_FamilyChange(familyID, FamilyChangeType_MemContrib, excludeIDList=[playerID])
    return
## ------------------------------------------------------------------------------------------------
#// A6 17 查询家族行为信息 #tagCMQueryFamilyAction
#
#struct    tagCMQueryFamilyAction
#{
#    tagHead        Head;
#    BYTE        ActionType;        // 行为类型
#    DWORD        FamilyID;         // 家族ID,发0默认自己家族
#};
def OnQueryFamilyAction(index, cliendData, tick):
    curPlayer = GameWorld.GetPlayerManager().GetPlayerByIndex(index)
    if not curPlayer:
        return
    actionType = cliendData.ActionType
    familyID = cliendData.FamilyID
    if not familyID:
        familyID = curPlayer.GetFamilyID()
    SendFamilyActionInfo(curPlayer, familyID, actionType)
    return
def SendFamilyActionInfo(curPlayer, familyID, actionType):
    ## 发送家族行为
    # @param curPlayer: 为None时通知该仙盟所有成员
    if not familyID:
        return
    familyAction = DBDataMgr.GetFamilyActionMgr().GetFamilyAction(familyID, actionType)
    clientPack = ChPyNetSendPack.tagMCFamilyActionInfo()
    clientPack.FamilyID = familyID
    clientPack.ActionType = actionType
    clientPack.FamilyActionList = []
    for index in xrange(familyAction.Count()):
        familyActionData = familyAction.At(index)
        actionData = ChPyNetSendPack.tagMCFamilyAction()
        actionData.Time = familyActionData.GetTime()
        actionData.Name = familyActionData.GetName()
        actionData.NameLen = len(actionData.Name)
        actionData.Value1 = familyActionData.GetValue1()
        actionData.Value2 = familyActionData.GetValue2()
        actionData.Value3 = familyActionData.GetValue3()
        actionData.Value4 = familyActionData.GetValue4()
        actionData.Value5 = familyActionData.GetValue5()
        actionData.Value6 = familyActionData.GetValue6()
        actionData.UseData = familyActionData.GetUserData()
        actionData.UseDataLen = len(actionData.UseData)
        clientPack.FamilyActionList.append(actionData)
    clientPack.Count = len(clientPack.FamilyActionList)
    if curPlayer:
        NetPackCommon.SendFakePack(curPlayer, clientPack)
        return
    Broadcast_FamilyPack(familyID, clientPack)
    return
def SendFamilyAction(actionDataList, curPlayer=None):
    ## 同步指定仙盟action
    # @param actionDataList: 支持列表或指定actionData
    # @param curPlayer: 为None时通知该仙盟所有成员
    if not isinstance(actionDataList, list):
        actionDataList = [actionDataList]
    if not actionDataList:
        return
    familyActionData = actionDataList[0]
    familyID = familyActionData.GetFamilyID()
    actionType = familyActionData.GetActionType()
    clientPack = ChPyNetSendPack.tagMCFamilyActionInfo()
    clientPack.FamilyID = familyID
    clientPack.ActionType = actionType
    clientPack.FamilyActionList = []
    for familyActionData in actionDataList:
        actionData = ChPyNetSendPack.tagMCFamilyAction()
        actionData.Time = familyActionData.GetTime()
        actionData.Name = familyActionData.GetName()
        actionData.NameLen = len(actionData.Name)
        actionData.Value1 = familyActionData.GetValue1()
        actionData.Value2 = familyActionData.GetValue2()
        actionData.Value3 = familyActionData.GetValue3()
        actionData.Value4 = familyActionData.GetValue4()
        actionData.Value5 = familyActionData.GetValue5()
        actionData.Value6 = familyActionData.GetValue6()
        actionData.UseData = familyActionData.GetUserData()
        actionData.UseDataLen = len(actionData.UseData)
        clientPack.FamilyActionList.append(actionData)
    clientPack.Count = len(clientPack.FamilyActionList)
    if curPlayer:
        NetPackCommon.SendFakePack(curPlayer, clientPack)
        return
    Broadcast_FamilyPack(familyID, clientPack)
    return