ServerPython/CoreServerGroup/GameServer/Script/Player/PlayerViewCache.py
@@ -16,239 +16,534 @@
# @change: "2016-05-18 16:00" hxp 战盟有官员成员缓存不清除
#---------------------------------------------------------------------
#"""Version = 2016-05-18 16:00"""
#------------------------------------------------------------------------------
#---------------------------------------------------------------------
import GameWorld
import ChPyNetSendPack
import IPY_PlayerDefine
import PlayerFamilyAction
import GameWorship
import GameXiangong
import PlayerControl
import NetPackCommon
import ReadChConfig
import PlayerFamily
import IPY_GameServer
import GameWorldArena
import ChPyNetSendPack
import PlayerFBHelpBattle
import GameWorldSkyTower
import CrossChampionship
import CrossBattlefield
import CrossRealmPlayer
import PyGameDataStruct
import PlayerPackData
import IpyGameDataPY
import PyDataManager
import CrossRealmMsg
import ShareDefine
import PyGameData
import CommFunc
import ChPlayer
import ChConfig
import time
import json
import time
import random
ViewCacheMgr = GameWorld.GameWorldData.GetPlayerViewCacheMgr()
TempCache = PyGameDataStruct.tagPlayerViewCachePy()
# 优先取缓存数据,后取可保存数据
def GetItemData(curCache):
    result = curCache.GetItemDataNoSave()
    if not result:
        return curCache.GetItemData()
    return result
def GetPlusData(curCache):
    result = curCache.GetPlusDataNoSave()
    if not result:
        return curCache.GetPlusData()
    return result
##更新缓存数据
#  @param PlayerID, PropData, ItemData, PlusData, isSaveDB
#  @return None
def UpdatePlayerCache(PlayerID, PropData, ItemData, PlusData, isSaveDB=False):
    curCache = ViewCacheMgr.FindCache(PlayerID)
    if not curCache:
        curCache = ViewCacheMgr.AddNewCache(PlayerID)
    curCache.SetUpdateTime(GameWorld.GetCurrentDataTimeStr())
#玩家缓存管理,该类只做数据缓存存取,不写功能逻辑,防止重读脚本时功能逻辑脚本不生效
class PlayerViewCachePyManager(object):
    
    GameWorld.DebugLog('ViewCache### UpdatePlayerCache PlayerID %s, \
                        PropData(len %s): %s, \
                        ItemData(len %s):  %s, \
                        PlusData(len %s):  %s'%
           (PlayerID, len(PropData), PropData,len(ItemData), ItemData, len(PlusData), PlusData)
                        )
    #2017-12-21 由于保存数据库的数据(realtime)过多根据需求分SetItemData和SetItemDataNoSave
    # 关服尽量只保存基础数据,默认使用不保存接口 SetItemDataNoSave
    curCache.SetPropData(PropData,len(PropData))
    if curCache.GetItemData() or (isSaveDB and IsNeedSaveViewCacheAllInfo(PlayerID)):
        curCache.SetItemData(ItemData,len(ItemData))
        curCache.SetPlusData(PlusData,len(PlusData))
        curCache.SetItemDataNoSave("",0) # 避免占用内存
        curCache.SetPlusDataNoSave("",0)
    else:
        # 低级号不处理保存
        curCache.SetItemDataNoSave(ItemData,len(ItemData))
        curCache.SetPlusDataNoSave(PlusData,len(PlusData))
    curCache.SetNeedSaveDB(isSaveDB) #设置需要保存到数据库
    # 同步更新助战信息
    if PlayerFBHelpBattle.IsInHelpBattleCheckInList(PlayerID):
        PropDataDict = eval(PropData)
        fightPower = PropDataDict.get("FightPower", 0)
        familyID = PropDataDict.get("FamilyID", 0)
        PlayerFBHelpBattle.UpdateCheckInPlayerInfo(PlayerID, fightPower, familyID)
    #暂时关闭
    #===========================================================================
    # FamilyIDKey = "FamilyID"
    # if FamilyIDKey in PropData:
    #    PropDataDict = eval(PropData)
    #    familyID = PropDataDict[FamilyIDKey]
    #    if familyID > 0:
    #        PlayerFamilyAction.UpdFamilyOfficerModelEquip(familyID, PlayerID)
    #===========================================================================
    return
##玩家下线缓存数据
#  @param PlayerID, PlayerLV, PropData, ItemData, PlusData
#  @return None
def OnPlayerLogout(PlayerID, PlayerLV, PropData, ItemData, PlusData):
    #不需要保存离线数据的,直接删除缓存数据
    if not IsNeedSaveLogoutPlayer(PlayerID, PlayerLV):
        ViewCacheMgr.DeleteCache(PlayerID)
    def __init__(self):
        self.__viewCacheList = [] # [tagPlayerViewCachePy, ...]
        self.__idIndexDict = {} # {playerID:index, ...}
        self.__needSort = False
        self.__serverIDRangePlayerIDDict = {} # {serverIDRangeTuple:[playerID, ...], ....}
        return
    #更新数据,并设置需要保存数据库
    UpdatePlayerCache(PlayerID, PropData, ItemData, PlusData, True)
    def GetPlayerViewCache(self, playerID):
        self.__refreshIDIndex()
        viewCache = None
        if playerID in self.__idIndexDict:
            index = self.__idIndexDict[playerID]
            if index < len(self.__viewCacheList):
                viewCache = self.__viewCacheList[index]
        return viewCache
    def AddPlayerViewCache(self, playerID, viewCache):
        self.__refreshIDIndex()
        if playerID in self.__idIndexDict:
            return
        viewCache.PlayerID = playerID
        self.__viewCacheList.append(viewCache)
        self.__idIndexDict[playerID] = len(self.__viewCacheList) - 1
        self.__needSort = True
        return
    def GetPlayerIDListByServerIDInfo(self, serverIDList):
        ## 根据服务器ID列表信息获取对应服务器ID范围的玩家ID战力排序列表
        if serverIDList == None:
            return []
        self.Sort()
        key = tuple(serverIDList)
        if key not in self.__serverIDRangePlayerIDDict:
            playerIDList = []
            for viewCache in self.__viewCacheList:
                playerID = viewCache.PlayerID
                cacheDict = GetCachePropDataDict(viewCache)
                if not cacheDict:
                    continue
                serverID = GameWorld.GetAccIDServerID(cacheDict["AccID"])
                for idInfo in serverIDList:
                    if (isinstance(idInfo, int) and serverID == idInfo) \
                        or ((isinstance(idInfo, tuple) or isinstance(idInfo, list)) \
                            and len(idInfo) == 2 and idInfo[0] <= serverID <= idInfo[1]):
                        playerIDList.append(playerID)
            GameWorld.DebugLog("重新加载区服玩家查看缓存ID列表: %s, %s, %s" % (key, len(playerIDList), playerIDList))
            self.__serverIDRangePlayerIDDict[key] = playerIDList
        return self.__serverIDRangePlayerIDDict[key]
    def IsPlayerIn(self, playerID):
        self.__refreshIDIndex()
        return playerID in self.__idIndexDict
    def __refreshIDIndex(self):
        if not self.__idIndexDict:
            self.__idIndexDict = {}
            for index, viewCache in enumerate(self.__viewCacheList):
                self.__idIndexDict[viewCache.PlayerID] = index
        return self.__idIndexDict
    def DelPlayerViewCache(self, playerID):
        self.__refreshIDIndex()
        index = self.__idIndexDict.pop(playerID, -1)
        if index >= 0 and index < len(self.__viewCacheList):
            self.__viewCacheList.pop(index)
        for playerIDList in self.__serverIDRangePlayerIDDict.values():
            if playerID in playerIDList:
                playerIDList.remove(playerID)
        self.__idIndexDict = {}
        self.__serverIDRangePlayerIDDict = {}
        return
    def GetCount(self): return len(self.__viewCacheList)
    def At(self, index):
        viewCache = self.__viewCacheList[index]
        if not viewCache and False:
            viewCache = PyGameDataStruct.tagPlayerViewCachePy() # 不会执行到,只为了.出代码提示
        return viewCache
    def Sort(self):
        ## 默认按战力倒序排
        if not self.__needSort:
            return
        self.__needSort = False
        self.__viewCacheList.sort(cmp=self.__cmp)
        self.__idIndexDict = {}
        self.__serverIDRangePlayerIDDict = {}
        self.__refreshIDIndex()
        return
    def __cmp(self, a, b):
        ## 按战力倒序, cmp模式排序效率较低,如果需要再改为key模式
        aFightPower = 0
        cacheDict = GetCachePropDataDict(a)
        if cacheDict:
            aFightPower = cacheDict.get("FightPower", 0)
        bFightPower = 0
        cacheDict = GetCachePropDataDict(b)
        if cacheDict:
            bFightPower = cacheDict.get("FightPower", 0)
        return cmp(bFightPower, aFightPower)
    # 保存数据 存数据库和realtimebackup
    def GetSaveData(self):
        savaData = ""
        cntData = ""
        cnt = 0
        for dbData in self.__viewCacheList:
            #if dbData.PlayerID < 10000:
                # 假人玩家不存储
            #    continue
            cnt += 1
            savaData += dbData.getBuffer()
        GameWorld.Log("Save PlayerViewCachePy count :%s len=%s" % (cnt, len(savaData)))
        return CommFunc.WriteDWORD(cntData, cnt) + savaData
    # 从数据库载入数据
    def LoadPyGameData(self, datas, pos, dataslen):
        cnt, pos = CommFunc.ReadDWORD(datas, pos)
        GameWorld.Log("Load PlayerViewCachePy count :%s" % cnt)
        for _ in xrange(cnt):
            dbData = PyGameDataStruct.tagPlayerViewCachePy()
            dbData.clear()
            pos += dbData.readData(datas, pos, dataslen)
            self.AddPlayerViewCache(dbData.PlayerID, dbData)
        self.Sort()
        return pos
def DoOnDayEx():
    DelOutofTimeViewCacheData()
    return
## 根据规则判定是否需要继续保存离线玩家数据
#  @param PlayerID, PlayerLV
#  @return None
def IsNeedSaveLogoutPlayer(PlayerID, PlayerLV):
    if PlayerFBHelpBattle.IsInHelpBattleCheckInList(PlayerID):
def IsSaveDBViewCache(viewCache):
    ## 缓存数据是否入库
    if not viewCache:
        return False
    playerID = viewCache.PlayerID
    if PlayerFBHelpBattle.IsInHelpBattleCheckInList(playerID):
        return True
    if GameWorldArena.IsArenaBattlePlayer(playerID):
        return True
    if CrossBattlefield.IsBattlefieldCallPlayer(playerID):
        return True
    if CrossChampionship.IsChampionshipPlayer(playerID):
        return True
    if GameWorship.IsWorshipPlayer(playerID):
        return True
    if PyDataManager.GetDBPyFuncTeamManager().IsTeamPlayer(playerID):
        return True
    if GameXiangong.IsXiangongPlayer(playerID):
        return True
    if GameWorldSkyTower.IsSkyTowerPassPlayer(playerID):
        return True
    if PlayerPackData.IsPackDataPlayer(playerID):
        return True
    #跨服榜单上的默认保留
    if GameWorld.IsCrossServer():
        billboardMgr = PyDataManager.GetCrossBillboardManager()
        for billboardType in ShareDefine.CrossBillboardTypeList:
            groupList = billboardMgr.GetBillboardGroupList(billboardType)
            for billboardType, groupValue1, groupValue2 in groupList:
                billboardObj = billboardMgr.GetCrossBillboard(billboardType, groupValue1, groupValue2)
                if billboardObj.FindByID(playerID):
                    return True
    else:
        NeedCheckBillBoardType = IpyGameDataPY.GetFuncEvalCfg("PlayerViewCache", 2)
        #校验玩家是否上排行榜
        billboardMgr = GameWorld.GetBillboard()
        for BillBoardType in NeedCheckBillBoardType:
            curBillboard = billboardMgr.FindBillboard(BillBoardType)
            if not curBillboard:
                continue
            if curBillboard.FindByID(playerID):
                return True
    # 以上是相关功能需要用到的数据,必定不能删除的
    # 以下是保留近期活跃玩家,等级限制
    playerLV = viewCache.LV
    offTime = viewCache.OffTime
    if not playerLV and not offTime:
        # 跨服服务器之前某些情况没有存储LV及OffTime,防止误删,做旧数据兼容用
        return True
    
    SaveDBLimitLV = IpyGameDataPY.GetFuncCfg("PlayerViewCache", 1)
    #校验玩家等级
    if PlayerLV < SaveDBLimitLV:
        return False
    return True
# 上榜用户
def IsNeedSaveViewCacheAllInfo(PlayerID):
    if PlayerFBHelpBattle.IsInHelpBattleCheckInList(PlayerID):
        return True
    NeedCheckBillBoardType = IpyGameDataPY.GetFuncEvalCfg("PlayerViewCache", 2)
    #校验玩家是否上排行榜
    billboardMgr = GameWorld.GetBillboard()
    for BillBoardType in NeedCheckBillBoardType:
        curBillboard = billboardMgr.FindBillboard(BillBoardType)
        if not curBillboard:
            continue
        if curBillboard.FindByID(PlayerID):
    if playerLV >= SaveDBLimitLV:
        maxDays = IpyGameDataPY.GetFuncCfg("PlayerViewCache", 3)
        if not maxDays:
            maxDays = 7 # 默认7天
        MaxTime = maxDays * 3600 * 24
        curTime = int(time.time())
        passTime = curTime - viewCache.OffTime
        if passTime < MaxTime:
            return True
    return False
#    #校验玩家是否上排行榜
#    billboardMgr = GameWorld.GetBillboard()
#    for BillBoardType in NeedCheckBillBoardType:
#        curBillboard = billboardMgr.FindBillboard(BillBoardType)
#        if not curBillboard:
#            continue
#        if curBillboard.FindByID(PlayerID):
#            return True
#
#    #校验玩家竞技场是否进入排名
#    hightLadderMgr = GameWorld.GetHightLadderMgr()
#    hightLadderData = hightLadderMgr.FindPlayerData(PlayerID)
#    if hightLadderData:
#        if hightLadderData.GetOrder() < HighLadderLimitOrder:
#            return True
#
#    curPlayer = GameWorld.GetPlayerManager().FindPlayerByID(PlayerID)
#    if curPlayer:
#        # 非普通成员需保存
#        if PlayerFamily.GetPlayerFamilyMemberLV(curPlayer) != IPY_GameServer.fmlMember:
#            return True
#
#    return False
## //04 01 地图同步玩家数据到GameServer#tagMGUpdatePlayerCache
#  @param routeIndex, mapID, curPackData, tick
#  @return None
def OnMGUpdatePlayerCache(routeIndex, mapID, curPackData, tick):
    GameWorld.DebugLog('ViewCache### OnMGUpdatePlayerCache in %s'%curPackData.OutputString())
    PlayerID = curPackData.PlayerID
    PlayerLV = curPackData.PlayerLV
    if curPackData.IsLogouting:
        OnPlayerLogout(PlayerID, PlayerLV, \
                       curPackData.PropData, curPackData.ItemData, curPackData.PlusData)
    else:
        # 此处保存设置为True是为安全防范,比如突然断电,宕机等情况 导致误以为不保存,故等级可设置高一点
        UpdatePlayerCache(PlayerID, curPackData.PropData, \
                          curPackData.ItemData, curPackData.PlusData, True if PlayerLV > 150 else False)
    GameWorld.DebugLog('ViewCache### OnMGUpdatePlayerCache out')
def DelOutofTimeViewCacheData():
    ## 删除过期的查看缓存数据
    onlineMgr = ChPlayer.GetOnlinePlayerMgr()
    pyViewCacheMgr = PyDataManager.GetPlayerViewCachePyManager()
    for index in range(pyViewCacheMgr.GetCount())[::-1]: # 有删除需倒序遍历
        viewCache = pyViewCacheMgr.At(index)
        if IsSaveDBViewCache(viewCache):
            continue
        playerID = viewCache.PlayerID
        if onlineMgr.IsOnline(playerID):
            #在线的先不删除
            continue
        pyViewCacheMgr.DelPlayerViewCache(playerID)
    PyGameData.g_crossPlayerViewCache = {} # 每日直接清空跨服玩家查看缓存
    return
## //04 02 地图查询玩家缓存数据#tagMGQueryPlayerCache
#  @param routeIndex, mapID, curPackData, tick
#  @return None
def DeleteViewCache(playerID):
    ## 删除玩家缓存
    pyViewCacheMgr = PyDataManager.GetPlayerViewCachePyManager()
    pyViewCacheMgr.DelPlayerViewCache(playerID)
    GameWorld.DebugLog("删除查看缓存!", playerID)
    return
def FindViewCache(playerID, isAddNew=False, newPropData=None):
    ## 查找玩家缓存
    # @param newPropData: 新数据初始PropData {}, key: LV,RealmLV,Job,VIPLV,Name,FamilyID,FamilyName,FightPower
    pyViewCacheMgr = PyDataManager.GetPlayerViewCachePyManager()
    curCache = pyViewCacheMgr.GetPlayerViewCache(playerID)
    if curCache:
        pass
    elif isAddNew or playerID < 10000:
        # 内网测试假玩家
        if playerID < 10000:
            if not newPropData:
                newPropData = {}
            fakeName = newPropData.get("Name", "")
            if not fakeName:
                fakeName = "神秘道友".decode(ShareDefine.Def_Game_Character_Encoding).encode(GameWorld.GetCharacterEncoding())
                fakeName = "%s%s" % (fakeName, playerID)
            accID = newPropData.get("AccID", "")
            if not accID:
                serverID = playerID % 100 + 1 # 1 ~ 100 服
                accID = "fake%s@test@s%s" % (playerID, serverID)
            else:
                serverID = GameWorld.GetAccIDServerID(accID)
            serverGroupID = serverID
            if serverID < 50:
                serverGroupID = serverGroupID / 10 + 1 # 前50服每10服1主服
            isOnline = True if playerID % 2 == 0 else False
            olMgr = ChPlayer.GetOnlinePlayerMgr()
            olMgr.SetOnlineState(playerID, isOnline, serverGroupID)
            job = newPropData.get("Job", 0)
            if not job:
                openJobList = IpyGameDataPY.GetFuncEvalCfg("OpenJob", 1)
                job = random.choice(openJobList) if openJobList else 1
            lv = newPropData.get("LV", random.randint(100, 200))
            realmLV = newPropData.get("RealmLV", random.randint(5, 15))
            fightPower = newPropData.get("FightPower", random.randint(1000000, 100000000))
            newPropData.update({"AccID":accID, "PlayerID":playerID, "Name":fakeName, "Job":job, "LV":lv,
                                "RealmLV":realmLV, "FightPower":fightPower, "ServerGroupID":serverGroupID,})
        curCache = PyGameDataStruct.tagPlayerViewCachePy()
        curCache.PlayerID = playerID
        curCache.OffTime = int(time.time())
        if newPropData:
            curCache.PropData = json.dumps(newPropData, ensure_ascii=False).replace(" ", "")
            curCache.PropDataSize = len(curCache.PropData)
        pyViewCacheMgr.AddPlayerViewCache(playerID, curCache)
    return curCache
def GetCachePropDataDict(curCache):
    ## 获取缓存基础属性字典信息
    if not curCache:
        return {}
    if not hasattr(curCache, "PropDataDict"):
        curCache.PropDataDict = {}
    if not curCache.PropDataDict and curCache.PropData:
        curCache.PropDataDict = eval(curCache.PropData)
    return curCache.PropDataDict
def GetShotCacheDict(playerID, *exAttrs):
    ## 获取玩家简短的缓存信息字典
    viewCache = FindViewCache(playerID)
    cacheDict = GetCachePropDataDict(viewCache)
    if not cacheDict:
        return {}
    shotCacheDict = {
                     "Name":cacheDict["Name"],
                     "Job":cacheDict["Job"],
                     "LV":cacheDict["LV"],
                     "RealmLV":cacheDict["RealmLV"],
                     }
    for attrName in exAttrs:
        if attrName == "PlayerID":
            shotCacheDict["PlayerID"] = playerID
        elif attrName == "ServerID":
            shotCacheDict["ServerID"] = GameWorld.GetAccIDServerID(cacheDict["AccID"])
        elif attrName == "OfflineValue":
            olMgr = ChPlayer.GetOnlinePlayerMgr()
            shotCacheDict["OfflineValue"] = olMgr.GetOfflineValue(playerID, viewCache)
        # 附带外观模型展示相关
        elif attrName == "Model":
            shotCacheDict.update({
                                  "TitleID":cacheDict.get("TitleID", 0),
                                  "EquipShowSwitch":cacheDict.get("EquipShowSwitch", 0),
                                  "EquipShowID":cacheDict.get("EquipShowID", []),
                                  })
        elif attrName in cacheDict:
            shotCacheDict[attrName] = cacheDict[attrName]
    return shotCacheDict
def GetSyncCrossCacheBase(curPlayer):
    ## 获取同步跨服基础查看缓存,主要用于个别功能需要提前先同步玩家基础缓存到跨服,因为跨服不一定有玩家缓存,需要提前同步
    if isinstance(curPlayer, int):
        playerID = curPlayer
        curPlayer = None
    else:
        playerID = curPlayer.GetPlayerID()
    cacheDict = GetCachePropDataDict(FindViewCache(playerID))
    cacheBase = {
                 "AccID":curPlayer.GetAccID() if curPlayer else cacheDict.get("AccID", ""),
                 "LV":curPlayer.GetLV() if curPlayer else cacheDict.get("LV", 1),
                 "RealmLV":curPlayer.GetOfficialRank() if curPlayer else cacheDict.get("RealmLV", 1),
                 "Job":curPlayer.GetJob() if curPlayer else cacheDict.get("Job", 1),
                 "VIPLV":curPlayer.GetVIPLv() if curPlayer else cacheDict.get("VIPLV", 0),
                 "Name":curPlayer.GetName() if curPlayer else cacheDict.get("Name", ""), # 此处不用跨服名称,如前端需要展示跨服名称,可通过ServerID或AccID取得ServerID展示
                 "Face":curPlayer.GetFace() if curPlayer else cacheDict.get("Face", 0),
                 "FacePic":curPlayer.GetFacePic() if curPlayer else cacheDict.get("FacePic", 0),
                 "FamilyID":curPlayer.GetFamilyID() if curPlayer else cacheDict.get("FacmilyID", 0),
                 "FamilyName":cacheDict.get("FamilyName", ""),
                 "TitleID":cacheDict.get("TitleID", 0),
                 "FightPower":PlayerControl.GetFightPower(curPlayer) if curPlayer else cacheDict.get("FightPower", 0),
                 "EquipShowSwitch":cacheDict.get("EquipShowSwitch", 0),
                 "EquipShowID":cacheDict.get("EquipShowID", []),
                 "ServerGroupID":PlayerControl.GetPlayerServerGroupID(curPlayer) if curPlayer else cacheDict.get("ServerGroupID", GameWorld.GetServerGroupID()),
                 }
    return cacheBase
def UpdCrossCacheBase(playerID, cacheBase, isLogout=False):
    ## 更新同步跨服基础查看缓存
    curCache = FindViewCache(playerID, True, cacheBase)
    if not curCache:
        return {}
    curCache.LV = cacheBase.get("LV", 0)
    curCache.OffTime = int(time.time()) # 每次都更新,最后一次可视为跨服玩家的最近一次离线时间
    cacheDict = GetCachePropDataDict(curCache)
    if not cacheBase:
        return cacheDict
    for k, v in cacheBase.items():
        cacheDict[k] = v
    if isLogout:
        if not IsSaveDBViewCache(curCache):
            DeleteViewCache(playerID)
            return {}
    return cacheDict
ItemDataClassMax = 20 # 最大装备阶数
#//04 01 地图同步玩家缓存数据到GameServer#tagMGUpdatePlayerCache
#
#struct    tagMGUpdatePlayerCache
#{
#    tagHead        Head;
#    DWORD        PlayerID;        //玩家ID
#    WORD        PlayerLV;        //玩家等级
#    BYTE        IsLogouting;        //本次是否为下线同步
#    DWORD        OffTime;        // 下线时间戳
#    WORD        PropDataSize;
#    char        PropData[PropDataSize];    //属性记录
#    WORD        PlusDataSize;
#    char        PlusData[PlusDataSize];    //扩展记录
#    WORD        ItemDataSize1;
#    char        ItemData1[ItemDataSize1];    //1阶装备数据
#    ...         ...
#    WORD        ItemDataSize20;
#    char        ItemData20[ItemDataSize20];
#    BYTE        PackDataSyncState;    // 打包数据同步状态: 0-不同步;个位-是否同步本服;十位-是否同步跨服
#    DWORD       PackDataLen;
#    char        PackData[PackDataLen];
#    WORD        PackMsgLen;
#    char        PackMsg[PackMsgLen];
#};
def OnMGUpdatePlayerCache(routeIndex, mapID, curPackData, tick):
    playerID = curPackData.PlayerID
    #playerLV = curPackData.PlayerLV
    isLogout = curPackData.IsLogouting
    packDataSyncState = curPackData.PackDataSyncState
    packDataLen = curPackData.PackDataLen
    GameWorld.DebugLog('地图同步玩家缓存: isLogout=%s,packDataSyncState=%s,packDataLen=%s'
                       % (isLogout, packDataSyncState, packDataLen), playerID)
    curCache = FindViewCache(playerID, True)
    if not curCache:
        return
    curCache.LV = curPackData.PlayerLV
    curCache.OffTime = curPackData.OffTime
    curCache.PropDataDict = {} # 每次更新数据时,重置字典缓存,下次获取时重新eval缓存
    curCache.PropData = curPackData.PropData
    curCache.PropDataSize = curPackData.PropDataSize
    curCache.PlusData = curPackData.PlusData
    curCache.PlusDataSize = curPackData.PlusDataSize
    #GameWorld.DebugLog("    更新Prop数据: size=%s, %s" % (curCache.PropDataSize, curCache.PropData), playerID)
    #GameWorld.DebugLog("    更新Plus数据: size=%s, %s" % (curCache.PlusDataSize, curCache.PlusData), playerID)
    # 装备数据存储,只更新有同步的阶,只要该阶有同步,则至少是 {}
    for classLV in xrange(1, ItemDataClassMax + 1):
        itemDataSize = getattr(curPackData, "ItemDataSize%s" % classLV)
        if not itemDataSize:
            continue
        itemData = getattr(curPackData, "ItemData%s" % classLV)
        setattr(curCache, "ItemData%s" % classLV, itemData)
        setattr(curCache, "ItemDataSize%s" % classLV, itemDataSize)
        #GameWorld.DebugLog("    更新Item数据: classLV=%s,size=%s, %s" % (classLV, itemDataSize, itemData), playerID)
    msgInfo = eval(curPackData.PackMsg) if curPackData.PackMsg else {} # 打包数据附带的信息
    # 需要同步跨服
    if packDataSyncState&pow(2, 2):
        dataMsg = {"playerID":playerID, "cacheBuffer":curCache.getBuffer(), "msgInfo":msgInfo}
        CrossRealmMsg.SendMsgToCrossServer(ShareDefine.ClientServerMsg_PushPlayerCache, dataMsg)
    curPlayer = GameWorld.GetPlayerManager().FindPlayerByID(playerID)
    # 在可能删除之前执行打包数据相关逻辑
    PlayerPackData.OnMGUpdatePlayerPackData(curPlayer, curPackData, msgInfo)
    if isLogout:
        #不需要保存离线数据的,直接删除缓存数据
        if not IsSaveDBViewCache(curCache):
            DeleteViewCache(playerID)
            return
        if curPlayer:
            curCache.GeTuiID = curPlayer.GetGeTuiClientID()
            curCache.GeTuiIDSize = len(curCache.GeTuiID)
    #GameWorld.DebugLog("    %s" % curCache.outputString())
    # 同步更新助战信息
    if PlayerFBHelpBattle.IsInHelpBattleCheckInList(playerID):
        PropDataDict = GetCachePropDataDict(curCache)
        fightPower = PropDataDict.get("FightPower", 0)
        familyID = PropDataDict.get("FamilyID", 0)
        playerName = PropDataDict.get("Name", "")
        PlayerFBHelpBattle.UpdateCheckInPlayerInfo(playerID, fightPower, familyID, playerName)
    return
#//04 02 地图查询玩家缓存数据#tagMGQueryPlayerCache
#struct tagMGQueryPlayerCache
#{
#    tagHead        Head;
#    DWORD        PlayerID;        //玩家ID
#    DWORD        FindPlayerID;    //要查询的玩家ID
#    BYTE        EquipClassLV;    //大于0为查看指定境界阶装备信息,  0为查看默认信息
#};
def OnMGQueryPlayerCache(routeIndex, mapID, curPackData, tick):
    GameWorld.DebugLog('ViewCache### OnMGQueryPlayerCache in')
    curPlayer = GameWorld.GetPlayerManager().FindPlayerByID(curPackData.PlayerID)
    findPlayerID = curPackData.FindPlayerID
    curCache = ViewCacheMgr.FindCache(findPlayerID)
    if not curCache:
        PlayerControl.NotifyCode(curPlayer, "ViewPlayer_OffLine")
        return
    #回包客户端
    sendPack = ChPyNetSendPack.tagSCQueryPlayerCacheResult()
    sendPack.PlayerID = findPlayerID
    if not curCache:
        #失败回包 空数据
        GameWorld.DebugLog("PlayerCache## OnMGQueryPlayerCache %s no found"%findPlayerID)
        sendPack.PropData = ""
        sendPack.PropDataSize = len(sendPack.PropData)
        sendPack.ItemData = ""
        sendPack.ItemDataSize = len(sendPack.ItemData)
        sendPack.PlusData = ""
        sendPack.PlusDataSize = len(sendPack.PlusData)
    else:
        #成功回包 缓存数据
        sendPack.PropData = curCache.GetPropData()
        sendPack.PropDataSize = len(sendPack.PropData)
        sendPack.ItemData = GetItemData(curCache)
        sendPack.ItemDataSize = len(sendPack.ItemData)
        sendPack.PlusData = GetPlusData(curCache)
        sendPack.PlusDataSize = len(sendPack.PlusData)
    NetPackCommon.SendFakePack(curPlayer, sendPack)
    GameWorld.DebugLog('ViewCache### OnMGQueryPlayerCache out')
    equipClassLV = curPackData.EquipClassLV
    OnQueryPlayerCache(curPlayer, findPlayerID, equipClassLV)
    return
## 获取玩家缓存模型装备信息
def GetPlayerCacheEquipView(findPlayerID):
    curCache = ViewCacheMgr.FindCache(findPlayerID)
    if not curCache:
        return
    itemData = GetItemData(curCache)
    if not itemData:
        return
    playerEquipList = []
    equipItemList = eval(itemData)
    for equipItemDict in equipItemList:
        equipIndex = equipItemDict["ItemIndex"]
        if equipIndex not in ShareDefine.RoleEquipType:
            continue
        itemID = equipItemDict["ItemID"]
        if not itemID:
            continue
        playerEquipList.append([itemID, equipIndex, equipItemDict.get("StarLV", 0), 0])
    return playerEquipList
#// C0 02 查看跨服玩家信息 #tagCGViewCrossPlayerInfo
#
#struct    tagCGViewCrossPlayerInfo
#{
#    tagHead        Head;
#    DWORD        PlayerID;    // 跨服玩家ID
#    BYTE        EquipClassLV;    //大于0为查看指定境界阶装备信息,  0为查看默认信息
#};
def OnViewCrossPlayerInfo(index, clientData, tick):
    curPlayer = GameWorld.GetPlayerManager().GetPlayerByIndex(index)
    tagPlayerID = clientData.PlayerID
    equipClassLV = clientData.EquipClassLV
    OnQueryPlayerCache(curPlayer, tagPlayerID, equipClassLV)
    return
#===============================================================================
# //B3 06 查询玩家的简短信息 #tagCGViewPlayerShortInfo
@@ -259,31 +554,12 @@
# };
#===============================================================================
def OnViewPlayerShortInfo(index, clientPack, tick):
    ## 封包通知
    tagPlayer = GameWorld.GetPlayerManager().FindPlayerByID(clientPack.PlayerID)
    answerPack = ChPyNetSendPack.tagGCAnswerPlayerShortInfo()
    answerPack.Clear()
    if not tagPlayer:
        curCache = ViewCacheMgr.FindCache(clientPack.PlayerID)
        if not curCache:
            # 实在找不到设置为初始化数据
            answerPack.PlayerID = clientPack.PlayerID
            answerPack.PlayerName = ""
            answerPack.Job = 1
            answerPack.LV = 1
            answerPack.RealmLV = 1
            answerPack.OnlineType = ChConfig.Def_Offline
        else:
            cacheDict = eval(curCache.GetPropData())
            answerPack.PlayerID = clientPack.PlayerID
            answerPack.PlayerName = cacheDict["Name"]
            answerPack.Job = cacheDict["Job"]
            answerPack.LV = cacheDict["LV"]
            answerPack.RealmLV = cacheDict["RealmLV"]
            answerPack.OnlineType = ChConfig.Def_Offline
    else:
    curPlayer = GameWorld.GetPlayerManager().GetPlayerByIndex(index)
    tagPlayerID = clientPack.PlayerID
    tagPlayer = GameWorld.GetPlayerManager().FindPlayerByID(tagPlayerID)
    if tagPlayer:
        answerPack = ChPyNetSendPack.tagGCAnswerPlayerShortInfo()
        answerPack.Clear()
        answerPack.PlayerID = clientPack.PlayerID
        answerPack.PlayerName = tagPlayer.GetName()
        answerPack.Job = tagPlayer.GetJob()
@@ -291,24 +567,279 @@
        answerPack.RealmLV = tagPlayer.GetOfficialRank()
        answerPack.OnlineType = ChConfig.Def_Online
        answerPack.IsInTeam = tagPlayer.GetTeamID() > 0
        answerPack.ServerGroupID = PlayerControl.GetPlayerServerGroupID(tagPlayer)
        answerPack.Face = tagPlayer.GetFace()
        answerPack.FacePic = tagPlayer.GetFacePic()
        NetPackCommon.SendFakePack(curPlayer, answerPack)
    else:
        OnQueryPlayerCache(curPlayer, tagPlayerID, isShort=1)
    return
    curPlayer = GameWorld.GetPlayerManager().GetPlayerByIndex(index)
def SetNeedViewCache(playerIDList):
    ## 设置需要缓存数据,跨服专用
    if not GameWorld.IsCrossServer():
        return
    for playerID in playerIDList:
        curCache = FindViewCache(playerID)
        if curCache:
            continue
        dataMsg = {"tagPlayerID":playerID}
        CrossRealmMsg.SendMsgToClientServer(ShareDefine.CrossServerMsg_PullPlayerViewCache, dataMsg)
    return
def OnQueryPlayerCache(curPlayer, tagPlayerID, equipClassLV=0, isShort=0):
    '''查询玩家缓存,支持直接在本服或跨服查询任意服务器玩家
    @param tagPlayerID: 目标玩家ID
    @param equipClassLV: 指定查看某一阶装备信息
    @param isShort: 是否查看简短信息
    '''
    if not curPlayer:
        return
    playerID = curPlayer.GetPlayerID()
    GameWorld.DebugLog("查看玩家: tagPlayerID=%s,equipClassLV=%s,isShort=%s" % (tagPlayerID, equipClassLV, isShort), playerID)
    # 在跨服服务器查询
    if GameWorld.IsCrossServer():
        curCache = FindViewCache(tagPlayerID)
        if curCache:
            GameWorld.DebugLog("    在跨服查看玩家跨服有数据,直接回包! tagPlayerID=%s" % tagPlayerID, playerID)
            Sync_PlayerCache(curPlayer, curCache, equipClassLV, isShort)
        else:
            GameWorld.DebugLog("    在跨服查看玩家跨服无数据,从子服拉取! tagPlayerID=%s" % tagPlayerID, playerID)
            viewFrom = 0
            msgInfo = {"playerID":playerID, "tagPlayerID":tagPlayerID, "equipClassLV":equipClassLV, "isShort":isShort, "viewFrom":viewFrom}
            dataMsg = {"tagPlayerID":tagPlayerID, "msgInfo":msgInfo}
            CrossRealmMsg.SendMsgToClientServer(ShareDefine.CrossServerMsg_PullPlayerViewCache, dataMsg)
    # 在子服服务器查询
    else:
        # 同db玩家
        if PlayerControl.GetDBPlayerAccIDByID(tagPlayerID):
            GameWorld.DebugLog("    查看玩家本服有数据,直接回包! tagPlayerID=%s" % tagPlayerID, playerID)
            curCache = FindViewCache(tagPlayerID)
            Sync_PlayerCache(curPlayer, curCache, equipClassLV, isShort)
        else:
            if tagPlayerID in PyGameData.g_crossPlayerViewCache:
                tick = GameWorld.GetGameWorld().GetTick()
                curCache, cacheTick = PyGameData.g_crossPlayerViewCache[tagPlayerID]
                if tick - cacheTick <= 1 * 60 * 1000:
                    GameWorld.DebugLog("    1分钟内重复查看跨服玩家且本服有数据,直接回包! tagPlayerID=%s" % tagPlayerID, playerID)
                    Sync_PlayerCache(curPlayer, curCache, equipClassLV, isShort)
                    return
            GameWorld.DebugLog("    查看玩家本服没有数据,发跨服查! tagPlayerID=%s" % tagPlayerID, playerID)
            viewFrom = GameWorld.GetServerGroupID()
            dataMsg = {"playerID":playerID, "tagPlayerID":tagPlayerID, "equipClassLV":equipClassLV, "isShort":isShort, "viewFrom":viewFrom}
            CrossRealmMsg.SendMsgToCrossServer(ShareDefine.ClientServerMsg_ViewPlayerCache, dataMsg)
    return
def Sync_PlayerCache(curPlayer, curCache, equipClassLV=0, isShort=0):
    ## 同步玩家缓存
    if isShort:
        Sync_PlayerShortInfo(curPlayer, curCache)
        return
    if not curCache:
        PlayerControl.NotifyCode(curPlayer, "ViewPlayer_OffLine")
        return
    if equipClassLV:
        itemData = ""
        if hasattr(curCache, "ItemDataSize%s" % equipClassLV):
            itemData = getattr(curCache, "ItemData%s" % equipClassLV)
        sendPack = ChPyNetSendPack.tagSCPlayerEquipCacheResult()
        sendPack.PlayerID = curCache.PlayerID
        sendPack.EquipClassLV = equipClassLV
        sendPack.ItemData = itemData
        sendPack.ItemDataSize = len(sendPack.ItemData)
        NetPackCommon.SendFakePack(curPlayer, sendPack)
        return
    #回包客户端
    sendPack = ChPyNetSendPack.tagSCQueryPlayerCacheResult()
    sendPack.PlayerID = curCache.PlayerID
    sendPack.PropData = curCache.PropData
    sendPack.PropDataSize = len(sendPack.PropData)
    sendPack.PlusData = curCache.PlusData
    sendPack.PlusDataSize = len(sendPack.PlusData)
    NetPackCommon.SendFakePack(curPlayer, sendPack)
    return
def Sync_PlayerShortInfo(curPlayer, curCache):
    if not curCache:
        PlayerControl.NotifyCode(curPlayer, "ViewPlayer_OffLine")
        return
    answerPack = ChPyNetSendPack.tagGCAnswerPlayerShortInfo()
    answerPack.Clear()
    cacheDict = GetCachePropDataDict(curCache)
    answerPack.PlayerID = curCache.PlayerID
    answerPack.PlayerName = cacheDict["Name"]
    answerPack.Job = cacheDict["Job"]
    answerPack.LV = cacheDict["LV"]
    answerPack.RealmLV = cacheDict["RealmLV"]
    answerPack.OnlineType = ChConfig.Def_Offline
    answerPack.Face = cacheDict.get("Face", 0)
    answerPack.FacePic = cacheDict.get("FacePic", 0)
    if GameWorld.IsCrossServer():
        answerPack.ServerGroupID = cacheDict.get("ServerGroupID", 0)
    else:
        answerPack.ServerGroupID = GameWorld.GetServerGroupID()
    NetPackCommon.SendFakePack(curPlayer, answerPack)
    return
def OnPlayerFamilyChange(playerID, familyID, familyName):
    GameWorld.DebugLog("ViewCache->OnPlayerFamilyChange", playerID)
    curCache = ViewCacheMgr.FindCache(playerID)
    curCache = FindViewCache(playerID)
    if not curCache:
        return
    PropData = eval(curCache.GetPropData())
    PropData = GetCachePropDataDict(curCache)
    PropData["FamilyID"] = familyID
    PropData["FamilyName"] = familyName
    playerLV = PropData["LV"]
    PropData = json.dumps(PropData, ensure_ascii=False)
    ItemData = curCache.GetItemData()
    PlusData = curCache.GetPlusData()
    UpdatePlayerCache(playerID, PropData, ItemData, PlusData, True if playerLV > 150 else False)
    PropData = json.dumps(PropData, ensure_ascii=False).replace(" ", "")
    curCache.PropData = PropData
    curCache.PropDataSize = len(curCache.PropData)
    return
def ClientServerMsg_ViewPlayerCache(serverGroupID, msgData):
    playerID = msgData["playerID"]
    tagPlayerID = msgData["tagPlayerID"]
    GameWorld.DebugLog("收到子服查看跨服玩家信息: serverGroupID=%s,playerID=%s,tagPlayerID=%s" % (serverGroupID, playerID, tagPlayerID))
    curCache = FindViewCache(tagPlayerID)
    if curCache:
        Send_CrossServerMsg_ViewPlayerCacheRet(curCache, msgData, serverGroupID)
    else:
        dataMsg = {"tagPlayerID":tagPlayerID, "msgInfo":msgData}
        CrossRealmMsg.SendMsgToClientServer(ShareDefine.CrossServerMsg_PullPlayerViewCache, dataMsg)
    return
def Send_CrossServerMsg_ViewPlayerCacheRet(curCache, msgData, serverGroupID):
    ## 发送查看玩家缓存结果给目标子服
    msgData["cacheBuffer"] = curCache.getBuffer() if curCache else ""
    CrossRealmMsg.SendMsgToClientServer(ShareDefine.CrossServerMsg_ViewPlayerCacheRet, msgData, [serverGroupID])
    return
def ClientServerMsg_PushPlayerCache(serverGroupID, msgData):
    ## 收到子服推送玩家缓存信息
    playerID = msgData["playerID"]
    cacheBuffer = msgData["cacheBuffer"]
    msgInfo = msgData.get("msgInfo", {})
    curCache = None
    if not cacheBuffer:
        GameWorld.DebugLog("子服推送的玩家缓存数据为空! playerID=%s" % playerID, serverGroupID)
    else:
        curCache = FindViewCache(playerID, True)
        ReadCacheBufferToCacheObj(playerID, cacheBuffer, curCache)
    if not msgInfo:
        return
    viewFrom = msgInfo.get("viewFrom", 0)
    if viewFrom != 0:
        # 其他子服查询的,推送给目标服务器
        Send_CrossServerMsg_ViewPlayerCacheRet(curCache, msgInfo, viewFrom)
        return
    # 在跨服查询的,直接回包
    viewPlayerID = msgInfo.get("playerID", 0)
    if not viewPlayerID:
        return
    curPlayer = GameWorld.GetPlayerManager().FindPlayerByID(viewPlayerID)
    if not curPlayer:
        return
    equipClassLV = msgInfo.get("equipClassLV", 0)
    isShort = msgInfo.get("isShort", 0)
    Sync_PlayerCache(curPlayer, curCache, equipClassLV, isShort)
    return
def ReadCacheBufferToCacheObj(playerID, cacheBuffer, cacheObj=None):
    try:
        TempCache.clear()
        setattr(TempCache, "PropDataDict", {})
        if TempCache.readData(cacheBuffer) == -1:
            GameWorld.ErrLog("玩家缓存数据readData失败! playerID=%s" % playerID)
            return
    except:
        GameWorld.ErrLog("玩家缓存数据readData异常! playerID=%s" % playerID)
        return
    if TempCache.PlayerID != playerID:
        GameWorld.ErrLog("玩家缓存数据readData后玩家ID不一致! playerID=%s != cachePlayerID=%s" % (playerID, TempCache.PlayerID))
        return
    if not cacheObj:
        return TempCache
    cacheObj.PropDataDict = {} # 每次更新数据时,重置字典缓存,下次获取时重新eval缓存
    cacheObj.PlayerID = TempCache.PlayerID
    cacheObj.GeTuiIDSize = TempCache.GeTuiIDSize
    cacheObj.GeTuiID = TempCache.GeTuiID
    cacheObj.LV = TempCache.LV
    cacheObj.OffTime = TempCache.OffTime
    cacheObj.PropData = TempCache.PropData
    cacheObj.PropDataSize = TempCache.PropDataSize
    cacheObj.PlusData = TempCache.PlusData
    cacheObj.PlusDataSize = TempCache.PlusDataSize
    for classLV in xrange(1, ItemDataClassMax + 1):
        itemDataSize = getattr(TempCache, "ItemDataSize%s" % classLV)
        if not itemDataSize:
            continue
        itemData = getattr(TempCache, "ItemData%s" % classLV)
        setattr(cacheObj, "ItemData%s" % classLV, itemData)
        setattr(cacheObj, "ItemDataSize%s" % classLV, itemDataSize)
    return cacheObj
def CrossServerMsg_ViewPlayerCacheRet(msgData, tick):
    ## 收到跨服服务器回复的查看玩家信息
    playerID = msgData["playerID"]
    tagPlayerID = msgData["tagPlayerID"]
    cacheBuffer = msgData["cacheBuffer"]
    equipClassLV = msgData["equipClassLV"]
    isShort = msgData["isShort"]
    curCache = None
    if cacheBuffer:
        if tagPlayerID in PyGameData.g_crossPlayerViewCache:
            curCache = PyGameData.g_crossPlayerViewCache[tagPlayerID][0]
        else:
            curCache = PyGameDataStruct.tagPlayerViewCachePy()
        if ReadCacheBufferToCacheObj(tagPlayerID, cacheBuffer, curCache):
            PyGameData.g_crossPlayerViewCache[tagPlayerID] = [curCache, tick] # 更新信息
    curPlayer = GameWorld.GetPlayerManager().FindPlayerByID(playerID)
    if curPlayer:
        Sync_PlayerCache(curPlayer, curCache, equipClassLV, isShort)
    return
def CrossServerMsg_PullPlayerViewCache(msgData):
    ## 收到跨服服务器拉取玩家玩家缓存数据
    msgInfo = msgData.get("msgInfo", {})
    tagPlayerID = msgData["tagPlayerID"]
    DoPullPlayerViewCache(tagPlayerID, msgInfo)
    return
def DoPullPlayerViewCache(playerID, msgInfo):
    if not PlayerControl.GetDBPlayerAccIDByID(playerID):
        # 不是本服db玩家不处理
        return
    curCache = FindViewCache(playerID)
    if curCache:
        GameWorld.DebugLog("本服有缓存玩家数据,直接推给跨服!", playerID)
        dataMsg = {"playerID":playerID, "cacheBuffer":curCache.getBuffer(), "msgInfo":msgInfo}
        CrossRealmMsg.SendMsgToCrossServer(ShareDefine.ClientServerMsg_PushPlayerCache, dataMsg)
        return
    curPlayer = GameWorld.GetPlayerManager().FindPlayerByID(playerID)
    if not curPlayer:
        GameWorld.DebugLog("本服无缓存玩家数据且不在线,直接回空数据给跨服!", playerID)
        dataMsg = {"playerID":playerID, "cacheBuffer":"", "msgInfo":msgInfo}
        CrossRealmMsg.SendMsgToCrossServer(ShareDefine.ClientServerMsg_PushPlayerCache, dataMsg)
        return
    GameWorld.DebugLog("玩家在线的发给地图同步缓存数据!", playerID)
    # 在线的转发给地图
    PlayerPackData.QueryPlayerResult_PlayerMirror(curPlayer, "PullPlayerViewCache", msgInfo)
    return