ServerPython/CoreServerGroup/GameServer/Script/Player/PlayerCompensation.py
@@ -28,12 +28,16 @@
import NetPackCommon
import CommFunc
import ChPyNetSendPack
import PyGameDataStruct
import CrossRealmMsg
import DataRecordPack
import ReadChConfig
import PlayerDBOper
import EventReport
import IpyGameDataPY
import PlayerControl
import PyDataManager
import PyGameData
import datetime
import uuid
import math
@@ -68,6 +72,216 @@
Def_RequestState = "CompensationRequestState"
#==================================================================================================
class CrossPersonalCompensationManager(object):
    ## 跨服邮件管理,注意该类只处理数据逻辑,功能相关逻辑不要写在该类,不然重读脚本不会生效
    def __init__(self):
        self.playerMailDict = {} # 玩家补偿列表 {playerID:{GUID:tagDBCrossPersonalCompensation, ...}, ...}
        return
    # 保存数据 存数据库和realtimebackup
    def GetSaveData(self):
        savaData = ""
        cntData = ""
        cnt = 0
        for mailDict in self.playerMailDict.values():
            for mailObj in mailDict.values():
                cnt += 1
                savaData += mailObj.getBuffer()
        GameWorld.Log("Save DBCrossPersonalCompensation 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 DBCrossPersonalCompensation count :%s" % cnt)
        for _ in xrange(cnt):
            mailObj = PyGameDataStruct.tagDBCrossPersonalCompensation()
            mailObj.clear()
            pos += mailObj.readData(datas, pos, dataslen)
            playerID = mailObj.PlayerID
            guid = mailObj.GUID
            if playerID not in self.playerMailDict:
                self.playerMailDict[playerID] = {}
            mailDict = self.playerMailDict[playerID]
            mailDict[guid] = mailObj
        return pos
def Sync_CrossMailPlayerIDToClientServer(serverGroupID=0):
    ''' 同步有跨服邮件的玩家ID到子服
    @param serverGroupID: 为0时同步所有子服
    '''
    crossMailMgr = PyDataManager.GetCrossPersonalCompensationManager()
    addMailPlayerIDList = crossMailMgr.playerMailDict.keys()
    if not addMailPlayerIDList:
        return
    # 同步子服务器
    serverGroupIDList = [serverGroupID] if serverGroupID else []
    dataMsg = {"IDType":"Add", "PlayerIDList":addMailPlayerIDList}
    CrossRealmMsg.SendMsgToClientServer(ShareDefine.CrossServerMsg_MailPlayerIDList, dataMsg, serverGroupIDList)
    return
def CrossServerMsg_MailPlayerIDList(dataMsg):
    ## 收到跨服服务器同步有跨服邮件的玩家ID列表
    idType = dataMsg["IDType"]
    playerIDList = dataMsg["PlayerIDList"]
    for playerID in playerIDList:
        if idType == "Del":
            if playerID in PyGameData.g_crossMailPlayerDict:
                PyGameData.g_crossMailPlayerDict.pop(playerID)
            continue
        if idType == "Add":
            if playerID in PyGameData.g_crossMailPlayerDict:
                continue
            PyGameData.g_crossMailPlayerDict[playerID] = 0
            player = GameWorld.GetPlayerManager().FindPlayerByID(playerID)
            if not player or not player.GetInitOK():
                continue
            RequestToGetCrossMail(player)
    return
def RequestToGetCrossMail(curPlayer):
    ## 请求同步跨服邮件内容
    playerID = curPlayer.GetPlayerID()
    if playerID not in PyGameData.g_crossMailPlayerDict:
        return
    lastTick = PyGameData.g_crossMailPlayerDict[playerID]
    tick = GameWorld.GetGameWorld().GetTick()
    if tick - lastTick <= 30000:
        return
    PyGameData.g_crossMailPlayerDict[playerID] = tick
    dataMsg = {"CMD":"Get", "PlayerID":playerID}
    CrossRealmMsg.SendMsgToCrossServer(ShareDefine.ClientServerMsg_MailContent, dataMsg)
    return
def ClientServerMsg_MailContent(serverGroupID, msgData, tick):
    ## 收到子服玩家请求同步跨服邮件
    GameWorld.Log("收到子服玩家同步个人邮件命令: serverGroupID=%s, %s" % (serverGroupID, msgData))
    reqCMD = msgData["CMD"]
    playerID = msgData["PlayerID"]
    guidList = msgData.get("GuidList", [])
    crossMailMgr = PyDataManager.GetCrossPersonalCompensationManager()
    if playerID not in crossMailMgr.playerMailDict:
        return
    playerMailDict = crossMailMgr.playerMailDict[playerID]
    if reqCMD == "Get":
        SyncTickAttrKey = "SyncTick"
        getMailList = []
        for guid, mailObj in playerMailDict.items():
            if hasattr(mailObj, SyncTickAttrKey):
                getTick = getattr(mailObj, SyncTickAttrKey)
                if tick - getTick <= 30000:
                    GameWorld.DebugLog("短时间内重复请求领取的邮件不同步,防止重复发放! GUID=%s" % guid)
                    continue
            setattr(mailObj, SyncTickAttrKey, tick)
            crossMailDict = {"PlayerID":mailObj.PlayerID, "GUID":mailObj.GUID, "LimitTime":mailObj.LimitTime,
                             "Text":mailObj.Text, "ItemInfo":mailObj.ItemInfo, "Detail":mailObj.Detail,
                             "Gold":mailObj.Gold, "GoldPaper":mailObj.GoldPaper, "Silver":mailObj.Silver,
                             "MoneySource":mailObj.MoneySource
                             }
            getMailList.append(crossMailDict)
        if getMailList:
            CrossRealmMsg.SendMsgToClientServer(ShareDefine.CrossServerMsg_MailContent, getMailList, [serverGroupID])
    elif reqCMD == "GetOK":
        for guid in guidList:
            playerMailDict.pop(guid, None)
            DataRecordPack.DR_GiveCompensationSuccess(playerID, guid)
        if not playerMailDict:
            crossMailMgr.playerMailDict.pop(playerID, None)
            dataMsg = {"IDType":"Del", "PlayerIDList":[playerID]}
            CrossRealmMsg.SendMsgToClientServer(ShareDefine.CrossServerMsg_MailPlayerIDList, dataMsg, [serverGroupID])
    return
def CrossServerMsg_MailContent(getMailList):
    ## 收到跨服服务器同步的待发送邮件内容
    playerGetGUIDInfo = {}
    for mailDict in getMailList:
        GUID = mailDict.get("GUID", "")
        playerID = mailDict.get("PlayerID", 0)
        if not GUID or not playerID:
            continue
        GameWorld.Log("收到跨服个人邮件内容:%s" % mailDict, playerID)
        addItemDictList = eval(mailDict.get("ItemInfo", "[]"))
        LimitTime = mailDict.get("LimitTime", "")
        Text = mailDict.get("Text", "")
        gold = GameWorld.ToIntDef(mailDict.get("Gold"))
        goldPaper = GameWorld.ToIntDef(mailDict.get("GoldPaper"))
        silver = GameWorld.ToIntDef(mailDict.get("Silver"))
        moneySource = GameWorld.ToIntDef(mailDict.get("MoneySource"), ChConfig.Def_GiveMoney_Mail)
        detail = mailDict.get("Detail", "")
        AddPersonalItem(GUID, addItemDictList, [playerID], LimitTime, Text, gold, goldPaper, silver, detail, moneySource)
        if playerID not in playerGetGUIDInfo:
            playerGetGUIDInfo[playerID] = []
        guidList = playerGetGUIDInfo[playerID]
        guidList.append(GUID)
    for playerID, guidList in playerGetGUIDInfo.items():
        dataMsg = {"CMD":"GetOK", "PlayerID":playerID, "GuidList":guidList}
        CrossRealmMsg.SendMsgToCrossServer(ShareDefine.ClientServerMsg_MailContent, dataMsg)
    return
def __AddPlayerCrossMail(addItemDictList, PlayerIDList, LimitTime, Text, gold, goldPaper, silver, detail, moneySource):
    ## 添加跨服玩家个人补偿邮件
    addMailPlayerIDList = []
    crossMailMgr = PyDataManager.GetCrossPersonalCompensationManager()
    for playerID in PlayerIDList:
        GUID = str(uuid.uuid1()) # 由于跨服邮件由每个玩家独自同步个人邮件,所以插入时每个人的邮件单独GUID,防止批量发送同内容邮件时重复插入奖励物品
        mailObj = PyGameDataStruct.tagDBCrossPersonalCompensation()
        mailObj.PlayerID = playerID
        mailObj.GUID = GUID
        mailObj.LimitTime = LimitTime
        mailObj.Text = Text
        mailObj.TextLen = len(mailObj.Text)
        mailObj.Gold = gold
        mailObj.GoldPaper = goldPaper
        mailObj.Silver = silver
        mailObj.ItemInfo = json.dumps(addItemDictList, ensure_ascii=False)
        mailObj.ItemLen = len(mailObj.ItemInfo)
        mailObj.Detail = detail
        mailObj.DetailLen = len(mailObj.Detail)
        mailObj.MoneySource = moneySource
        if playerID not in crossMailMgr.playerMailDict:
            crossMailMgr.playerMailDict[playerID] = {}
            addMailPlayerIDList.append(playerID)
        playerMailDict = crossMailMgr.playerMailDict[playerID]
        playerMailDict[GUID] = mailObj
        #添加流向
        addDict = {"LimitTime":LimitTime, "Text":Text, "Gold":gold, "GoldPaper":goldPaper, "Silver":silver,
                   "ItemListLen":len(addItemDictList), "Detail":detail, "MoneySource":moneySource, "CrossMail":True}
        DataRecordPack.DR_AddPersonalCompensation(PlayerIDList, GUID, addItemDictList, addDict)
    if addMailPlayerIDList:
        dataMsg = {"IDType":"Add", "PlayerIDList":addMailPlayerIDList}
        CrossRealmMsg.SendMsgToClientServer(ShareDefine.CrossServerMsg_MailPlayerIDList, dataMsg)
    return
#==================================================================================================
## 根据物品信息字典,生成补偿物品实例,用于GM工具添加补偿
#  @param curItemDict 
#  @return IpyCompensationItem
@@ -86,10 +300,10 @@
#  @param addItemList [(itemID, itemCnt, 是否拍品), {或物品信息字典}, ...]
#  @return GUID
def SendPersonalItemMailEx(title, content, getDays, playerIDList, addItemList, gold = 0, goldPaper = 0, silver = 0, 
                           detail="", moneySource=ChConfig.Def_GiveMoney_Mail):
                           detail="", moneySource=ChConfig.Def_GiveMoney_Mail, crossMail=False):
    limitTime = str(GameWorld.GetDatetimeByDiffDays(getDays))
    limitTime = limitTime.split(".")[0]
    return SendPersonalItemMail(title, content, limitTime, playerIDList, addItemList, gold, goldPaper, silver, detail, moneySource)
    return SendPersonalItemMail(title, content, limitTime, playerIDList, addItemList, gold, goldPaper, silver, detail, moneySource, crossMail)
    
def SendPersonalItemMailBatch(batchMailInfoList):
    ## 批量发送邮件
@@ -123,14 +337,14 @@
    return
def SendMailByKey(mailTypeKey, playerIDList, addItemList, paramList=[], gold=0, goldPaper=0, silver=0, 
                  detail="", moneySource=ChConfig.Def_GiveMoney_Mail):
                  detail="", moneySource=ChConfig.Def_GiveMoney_Mail, crossMail=False):
    if not mailTypeKey:
        mailTypeKey = ShareDefine.DefaultLackSpaceMailType
    GameWorld.DebugLog("SendMailByKey %s, playerIDList=%s, addItemList=%s, paramList=%s, gold=%s, goldPaper=%s, silver=%s, moneySource=%s" 
                       % (mailTypeKey, playerIDList, addItemList, paramList, gold, goldPaper, silver, moneySource))
    title = ""
    content = "<MailTemplate>%s</MailTemplate>%s" % (mailTypeKey, json.dumps(paramList, ensure_ascii=False))
    return SendPersonalItemMailEx(title, content, 30, playerIDList, addItemList, gold, goldPaper, silver, detail, moneySource)
    return SendPersonalItemMailEx(title, content, 30, playerIDList, addItemList, gold, goldPaper, silver, detail, moneySource, crossMail)
def CrossServerMsg_SendMail(msgData):
    ## 收到跨服服务器同步的发送邮件
@@ -147,7 +361,7 @@
#  @return GUID
#  @remarks addItemList支持append字典
def SendPersonalItemMail(title, content, limitTime, playerIDList, addItemList, gold = 0, goldPaper = 0, silver = 0, 
                         detail="", moneySource=ChConfig.Def_GiveMoney_Mail):
                         detail="", moneySource=ChConfig.Def_GiveMoney_Mail, crossMail=False):
    if not playerIDList:
        return ""
    
@@ -181,11 +395,13 @@
    perMailItemCnt = IpyGameDataPY.GetFuncCfg("MailMaxItemCnt")
    mailCnt = max(1, int(math.ceil(len(addItemDictList)/float(perMailItemCnt)))) # 一封邮件最多5个物品
    for i in xrange(mailCnt):
        if i != 0:
            gold, goldPaper, silver = 0, 0, 0 # 拆分后非第一封的邮件货币归零,防止重复给货币奖励
        startIndex = i*perMailItemCnt
        GUID = str(uuid.uuid1())
        AddPersonalItem(GUID, addItemDictList[startIndex:startIndex + perMailItemCnt], playerIDList, 
                                           limitTime, "%s<$_$>%s<$_$>%s" % (ChConfig.Def_Mail_SenderSys, title, content),
                                           gold, goldPaper, silver, detail, moneySource)
                                           gold, goldPaper, silver, detail, moneySource, crossMail)
    return GUID
## 发送纯文字个人补偿
@@ -207,8 +423,51 @@
    limitLVType = (mailInfo - checkState * 1000000) / 100000
    return checkState, limitLVType, limitLV
def QueryCompensationPersonalInfo(playerID):
    '''个人补偿邮件查询
    '''
    retList = []
    compensationMgr = GameWorld.GetCompensationMgr()
    tempSign = "<MailTemplate>"
    tempSignEnd = "</MailTemplate>"
    curPersonalCount = compensationMgr.GetPersonalCompensationCount(playerID)
    GameWorld.DebugLog("QueryCompensationPersonalInfo %s" % curPersonalCount)
    for i in xrange(curPersonalCount):
        compensation = compensationMgr.PersonalCompensationAt(playerID, i)
        contentList = compensation.Text.split("<$_$>")
        if len(contentList) != 3:
            continue
        sender, title, content = contentList
        if tempSign in content and tempSignEnd in content:
            title = content[content.index(tempSign) + len(tempSign):content.index(tempSignEnd)]
            content = ""
        GUID = compensation.GUID
        itemList = []
        curGUIDItemCount = compensationMgr.FindItemCount(GUID)
        for i in xrange(curGUIDItemCount):
            curItem = compensationMgr.FindItemAt(GUID, i)
            itemID = curItem.ItemID
            if not itemID:
                continue
            itemList.append([itemID, curItem.Count, curItem.IsBind, curItem.UserData])
        recState = compensationMgr.FindPlayerRecState(playerID, GUID)
        infoDict = {"GUID":GUID, "Gold":compensation.Gold, "GoldPaper":compensation.GoldPaper, "Silver":compensation.Silver,
                    "Sender":sender, "Title":title, "Content":content, "RecState":recState,
                    "CreateTime":compensation.CreateTime, "LimitTime":compensation.LimitTime, "ItemList":itemList}
        retList.append(infoDict)
    return retList
def QueryCompensationInfo(fromDate, toDate, guid, searchTitle, searchContent, searchState=None, maxCount=10):
    '''补偿邮件查询
    '''全服补偿邮件查询
    '''
    
    compensationMgr = GameWorld.GetCompensationMgr()
@@ -446,14 +705,19 @@
    GameWorld.DebugLog("新增个人邮件: totalCount=%s,maxMailCount=%s" % (totalCount, maxMailCount), PlayerID)
    if delCount > 0:
        GameWorld.Log("个人邮件达到上限,需要删除!delCount=%s" % (delCount), PlayerID)
        delGUIDList = GetPlayerDelMailGUIDList(PlayerID)
        #先取得要删除的GUID
        delGUIDs = []
        for i in xrange(delCount):
            curIpyPersonalData = GameWorld.GetCompensationMgr().PersonalCompensationAt(PlayerID, i)
            delGUIDs.append(curIpyPersonalData.GUID)
        for _ in xrange(delCount):
            if not delGUIDList:
                break
            delGUID = delGUIDList.pop(0)
            curIpyPersonalData = GameWorld.GetCompensationMgr().FindPersonalCompensation(PlayerID, delGUID)
            if curIpyPersonalData.GUID == delGUID:
                delGUIDs.append(curIpyPersonalData.GUID)
            
        for guid in delGUIDs:
            ClearPersonalCompensation(PlayerID, guid)
            ClearPersonalCompensation(PlayerID, guid, "MaxCountLimiit")
            GameWorld.Log("    DeletePersonalCompensation GUID = %s" % guid, PlayerID)
            
            if curPlayer:
@@ -464,15 +728,39 @@
    mailType = moneySource - ChConfig.Def_GiveMoney_Unknown # type类型为byte,存值时需要处理下
    GameWorld.GetCompensationMgr().AddPersonalCompensation(GUID, PlayerID, CreateTime,
                                                           LimitTime, Text, mailType, gold, goldPaper, silver)
    if PlayerID in PyGameData.g_playerDelMailGUIDDict:
        guidList = PyGameData.g_playerDelMailGUIDDict[PlayerID]
        guidList.append(GUID)
    return
def GetPlayerDelMailGUIDList(playerID):
    ## 获取待删除的个人邮件GUID列表
    if playerID not in PyGameData.g_playerDelMailGUIDDict:
        timeGUIDList = []
        curPersonalCount = GameWorld.GetCompensationMgr().GetPersonalCompensationCount(playerID)
        for i in xrange(curPersonalCount):
            curMail = GameWorld.GetCompensationMgr().PersonalCompensationAt(playerID, i)
            timeGUIDList.append([curMail.CreateTime, curMail.GUID])
        timeGUIDList.sort() # 按创建时间升序排序
        delGUIDList = []
        for _, guid in timeGUIDList:
            if guid not in delGUIDList:
                delGUIDList.append(guid)
        PyGameData.g_playerDelMailGUIDDict[playerID] = delGUIDList
    return PyGameData.g_playerDelMailGUIDDict[playerID]
#  此处货币playerIDList发放统一,如根据玩家不同而变,则应需修改
## 添加个人补偿
#  @param addItemDict, PlayerIDList, LimitTime, Text 
#  @return None
def AddPersonalItem(GUID, addItemDictList, PlayerIDList, LimitTime, Text, gold = 0, goldPaper = 0, silver = 0, detail="", moneySource=ChConfig.Def_GiveMoney_Mail):
def AddPersonalItem(GUID, addItemDictList, PlayerIDList, LimitTime, Text, gold = 0, goldPaper = 0, silver = 0,
                    detail="", moneySource=ChConfig.Def_GiveMoney_Mail, crossMail=False):
    if GameWorld.IsCrossServer():
        if crossMail:
            __AddPlayerCrossMail(addItemDictList, PlayerIDList, LimitTime, Text, gold, goldPaper, silver, detail, moneySource)
        return
    GameWorld.DebugLog("Compensation### AddPersonalItem GUID:%s ItemDict:\n%s "%(GUID, addItemDictList))
    
@@ -590,18 +878,27 @@
# 提取接收邮件下发
def NotifyPlayerCompensation(curPlayer):
    RequestToGetCrossMail(curPlayer)
    notifyList = SeekPlayerCompensation(curPlayer)
    SyncQueryCompensationResult(curPlayer, notifyList)
    return
# 删除个人邮件表 状态表,同一个GUID 可以多人领取,不能同时删除物品
def ClearPersonalCompensation(curPlayerID, curGUID):
def ClearPersonalCompensation(curPlayerID, curGUID, eventName=""):
    GameWorld.GetCompensationMgr().DeletePersonalCompensation(curPlayerID, curGUID)
    GameWorld.GetCompensationMgr().DeletePlayerCompensationRec(curPlayerID, curGUID)
    
    #存在多人邮件的情况,故删除物品需检查是否最后一个领取者才可删除
    if GameWorld.GetCompensationMgr().GetPersonalCountByGUID(curGUID) == 0:
        GameWorld.GetCompensationMgr().DeleteCompensationItem(curGUID)
    if curPlayerID in PyGameData.g_playerDelMailGUIDDict:
        guidList = PyGameData.g_playerDelMailGUIDDict[curPlayerID]
        if curGUID in guidList:
            guidList.remove(curGUID)
    if eventName:
        # 有特殊操作删除的才记录,常规领取删除的默认无eventName,不记录
        DataRecordPack.DR_DelPersonalCompensation(curPlayerID, curGUID, eventName)
    return
# 设置领取状态
@@ -926,11 +1223,25 @@
#  @return None
def ClearUpPersonalCompensation():
    #校验过期补偿
    tempSign = "<MailTemplate>"
    tempSignEnd = "</MailTemplate>"
    curTime = datetime.datetime.today()
    needClearGUIDList = []
    allCnt = GameWorld.GetCompensationMgr().GetAllPersonalCompensationCount()
    for i in xrange(allCnt):
        curMail = GameWorld.GetCompensationMgr().AtAllPersonalCompensation(i)
        mailText = curMail.Text
        # 通知类模板邮件,过天可直接删除
        if tempSign in mailText and tempSignEnd in mailText and CheckCanDelCompensation(curMail, curMail.GUID):
            #tempKey = mailText[mailText.index(tempSign) + len(tempSign):mailText.index(tempSignEnd)]
            #GameWorld.DebugLog("过天删除无附件通知类邮件模板! tempKey=%s %s, %s" % (tempKey, curMail.PlayerID, curMail.GUID))
            needClearGUIDList.append([curMail.PlayerID, curMail.GUID])
            player = GameWorld.GetPlayerManager().FindPlayerByID(curMail.PlayerID)
            if player and player.GetInitOK():
                NotifyCompensationResult(player, curMail.GUID, 1)
            continue
        # 超过接收邮件30天则完全删除此邮件
        limitTime = datetime.datetime.strptime(curMail.CreateTime, ChConfig.TYPE_Time_Format) + datetime.timedelta(days = 30)
        if limitTime < curTime:
@@ -940,7 +1251,7 @@
        
    #删除过期补偿信息, 没有主动通知在线玩家
    for playerID, GUID in needClearGUIDList:
        ClearPersonalCompensation(playerID, GUID)
        ClearPersonalCompensation(playerID, GUID, "Timeout")
    return
## 清理超时补偿, 个人邮件在超过上限后才会自动删除
@@ -1009,7 +1320,7 @@
            NotifyCompensationResult(curPlayer, GUID, 0)
            return
        
        ClearPersonalCompensation(curPlayerID, GUID)
        ClearPersonalCompensation(curPlayerID, GUID, "ClientDel")
        NotifyCompensationResult(curPlayer, GUID, 1)
        #GameWorld.DebugLog("个人补偿中OnDelCompensation:%s"%GUID)
        return