hxp
5 天以前 2b34924e06c0c36d77d9ccec4c4f10f1ebd16e84
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/PlayerArena.py
@@ -4,371 +4,346 @@
#
##@package Player.PlayerArena
#
# @todo:竞技场 - 本服
# @todo:演武场
# @author hxp
# @date 2020-12-07
# @date 2025-09-12
# @version 1.0
#
# 详细描述: 竞技场 - 本服
# 详细描述: 演武场
#
#-------------------------------------------------------------------------------
#"""Version = 2020-12-07 19:30"""
#"""Version = 2025-09-12 10:30"""
#-------------------------------------------------------------------------------
import DBDataMgr
import PlayerMail
import ShareDefine
import GameFuncComm
import PlayerControl
import IpyGameDataPY
import ChPyNetSendPack
import PlayerViewCache
import NetPackCommon
import PyGameData
import GameWorld
import ChConfig
import FBCommon
import IPY_GameWorld
import ItemControler
import EventShell
import PlayerActTask
import PlayerWeekParty
import PlayerFeastTravel
import PlayerActivity
import PlayerSuccess
import PlayerGubao
import random
def DoArenaOpen(curPlayer):
    ## 竞技场功能开启
    __DoArenaSeasonReset(curPlayer)
# 记录攻击类型
RecAtkType_Atk = 1 # 发起攻击
RecAtkType_Def = 2 # 被攻击的
def GetRecUpdScore(recData): return recData.GetValue1() # 更新积分
def SetRecUpdScore(recData, score): return recData.SetValue1(score)
def GetRecAtkType(recData): return recData.GetValue2() # 攻击类型 1-发起攻击的,2-被攻击的
def SetRecAtkType(recData, atkType): return recData.SetValue2(atkType)
def GetRecTagPlayerID(recData): return recData.GetValue3() # 相对攻击类型的目标玩家ID
def SetRecTagPlayerID(recData, tagPlayerID): return recData.SetValue3(tagPlayerID)
def GetRecIsWin(recData): return recData.GetValue4() # 是否获胜 1-获胜;2-失败
def SetRecIsWin(recData, isWin): return recData.SetValue4(1 if isWin else 0)
def GetRecFace(recData): return recData.GetValue5() # 目标头像
def SetRecFace(recData, face): return recData.SetValue5(face)
def GetRecFacePic(recData): return recData.GetValue6()
def SetRecFacePic(recData, facePic): return recData.SetValue6(facePic)
def GetRecRealmLV(recData): return recData.GetValue7()
def SetRecRealmLV(recData, realmLV): return recData.SetValue7(realmLV)
def GetRecLV(recData): return recData.GetValue8()
def SetRecLV(recData, tagLV): return recData.SetValue8(tagLV)
#SetUserData 名字、变更积分 +-、战力
def OnWeek():
    DoArenaReset()
    return
def OnDay():
    __DoGiveBillboardAward("Day")
    return
def DoArenaReset():
    ''' 赛季重置
    '''
    GameWorld.Log("=============== 重置竞技场 ===============")
    PyGameData.g_arenaPlayerMatchDict = {}
    # 结算上赛季排行奖励
    __DoGiveBillboardAward("Week")
    # 重置排行榜
    DBDataMgr.GetBillboardMgr().RemoveBillboard(ShareDefine.Def_BT_Arena)
    # 重置记录
    #DBDataMgr.GetGameRecMgr().DelDataByType(ShareDefine.Def_GameRecType_ArenaRecord) # 挑战记录不重置
    GameWorld.Log("==========================================")
    return True
def __DoGiveBillboardAward(awardType):
    ## 竞技场结算排行奖励, 每日、赛季通用
    GameWorld.Log("=== 竞技场结算排行奖励! === %s" % awardType)
    billboardMgr = DBDataMgr.GetBillboardMgr()
    billBoard = billboardMgr.GetBillboard(ShareDefine.Def_BT_Arena)
    if not billBoard:
        return
    if awardType == "Day":
        billboradAwardDict = IpyGameDataPY.GetFuncEvalCfg("ArenaBillboradAward", 1, {})
    elif awardType == "Week":
        billboradAwardDict = IpyGameDataPY.GetFuncEvalCfg("ArenaBillboradAward", 2, {})
    else:
        return
    orderList = [int(orderStr) for orderStr in billboradAwardDict.keys()]
    orderList.sort()
    GameWorld.Log("    奖励名次列表: %s" % orderList)
    awardOrder = orderList[0]
    orderPlayerIDDict = {}
    billboardCount, billboardMaxCount = billBoard.GetCount(), billBoard.GetMaxCount()
    GameWorld.Log("    榜单数据数! billboardCount=%s,billboardMaxCount=%s" % (billboardCount, billboardMaxCount))
    for index in xrange(billboardCount):
        billBoardData = billBoard.At(index)
        if not billBoardData:
            continue
        order = index + 1
        if order > awardOrder:
            nextOrderIndex = orderList.index(awardOrder) + 1
            if nextOrderIndex >= len(orderList):
                break
            awardOrder = orderList[nextOrderIndex]
        playerID = billBoardData.GetID()
        if playerID < ShareDefine.RealPlayerIDStart:
            # 非真人不处理
            continue
        orderPlayerIDDict[playerID] = [order, awardOrder]
        paramList = [order]
        awardList = billboradAwardDict[str(awardOrder)]
        PlayerMail.SendMailByKey("ArenaBillboardAward%s" % awardType, playerID, awardList, paramList)
    GameWorld.Log("    奖励玩家名次信息: %s" % orderPlayerIDDict)
    return
def OnLogin(curPlayer):
    if not GameFuncComm.GetFuncCanUse(curPlayer, ShareDefine.GameFuncID_Arena):
        return
    OSSeasonState = curPlayer.NomalDictGetProperty(ChConfig.Def_PDict_ArenaOSSeasonState)
    if not OSSeasonState:
        __DoArenaSeasonReset(curPlayer)
    else:
        Sync_ArenaInfo(curPlayer)
    __loginUpdPlayerScore(curPlayer)
    Sync_ArenaInfo(curPlayer)
    return
def __loginUpdPlayerScore(curPlayer):
    playerID = curPlayer.GetPlayerID()
    recMgr = DBDataMgr.GetGameRecMgr()
    recIDMgr = recMgr.GetRecTypeIDMgr(ShareDefine.Def_GameRecType_ArenaRecord, playerID)
    if not recIDMgr.GetCount():
        return
    finalRecData = recIDMgr.At(recIDMgr.GetCount() - 1)
    recTime = finalRecData.GetTime()
    if not GameWorld.CheckTimeIsSameWeek(recTime):
        GameWorld.Log("玩家上线演武场记录积分非本周不更新! recTime=%s" % GameWorld.ChangeTimeNumToStr(recTime), playerID)
        return
    updScore = GetRecUpdScore(finalRecData)
    befScore = curPlayer.NomalDictGetProperty(ChConfig.Def_PDict_ArenaScore)
    PlayerControl.NomalDictSetProperty(curPlayer, ChConfig.Def_PDict_ArenaScore, updScore)
    GameWorld.Log("玩家上线更新演武场积分: befScore=%s,updScore=%s,recTime=%s" % (befScore, updScore, GameWorld.ChangeTimeNumToStr(recTime)), playerID)
    return
def OnDayEx(curPlayer):
    if not GameFuncComm.GetFuncCanUse(curPlayer, ShareDefine.GameFuncID_Arena):
        return
    PlayerControl.NomalDictSetProperty(curPlayer, ChConfig.Def_PDict_ArenaItemAddCount, 0)
    PlayerControl.NomalDictSetProperty(curPlayer, ChConfig.Def_PDict_ArenaBattleCountDay, 0)
    PlayerControl.NomalDictSetProperty(curPlayer, ChConfig.Def_PDict_ArenaMatchRefreshCount, 0)
    Sync_ArenaInfo(curPlayer)
    openServerDay = GameWorld.GetGameWorld().GetGameWorldDictByKey(ShareDefine.Def_Notify_WorldKey_ServerDay) + 1
    customMaxServerDay = IpyGameDataPY.GetFuncCfg("OperationAction", 1)
    if openServerDay <= customMaxServerDay:
        GameWorld.DebugLog("OnDayEx时竞技场开服定制赛季进行中,不处理! openServerDay=%s <= %s" % (openServerDay, customMaxServerDay))
        return
    OSSeasonState = curPlayer.NomalDictGetProperty(ChConfig.Def_PDict_ArenaOSSeasonState)
    if OSSeasonState > 1:
        GameWorld.DebugLog("OnDayEx时竞技场开服定制赛季已结算过,不处理! OSSeasonState=%s" % (OSSeasonState))
        return
    __DoArenaSeasonReset(curPlayer)
    return
def OnWeekEx(curPlayer):
    if not GameFuncComm.GetFuncCanUse(curPlayer, ShareDefine.GameFuncID_Arena):
        return
    openServerDay = GameWorld.GetGameWorld().GetGameWorldDictByKey(ShareDefine.Def_Notify_WorldKey_ServerDay) + 1
    customMaxServerDay = IpyGameDataPY.GetFuncCfg("OperationAction", 1)
    if openServerDay <= customMaxServerDay:
        GameWorld.DebugLog("OnWeekEx时在开服定制天内,不处理竞技场赛季重置! openServerDay=%s <= %s" % (openServerDay, customMaxServerDay))
        return
    OSSeasonState = curPlayer.NomalDictGetProperty(ChConfig.Def_PDict_ArenaOSSeasonState)
    if not OSSeasonState or OSSeasonState == 1 or OSSeasonState == openServerDay:
        GameWorld.DebugLog("OnWeekEx时竞技场开服定制赛季进行中或同一天结算,不处理重置! openServerDay=%s,OSSeasonState=%s" % (openServerDay, OSSeasonState))
        return
    __DoArenaSeasonReset(curPlayer)
    return
def DoArenaOpen(curPlayer):
    __DoArenaSeasonReset(curPlayer)
    return
def __DoArenaSeasonReset(curPlayer):
    ## 玩家重置竞技场
    OSSeasonState = curPlayer.NomalDictGetProperty(ChConfig.Def_PDict_ArenaOSSeasonState)
    openServerDay = GameWorld.GetGameWorld().GetGameWorldDictByKey(ShareDefine.Def_Notify_WorldKey_ServerDay) + 1
    customMaxServerDay = IpyGameDataPY.GetFuncCfg("OperationAction", 1)
    if openServerDay <= customMaxServerDay and OSSeasonState != 0:
        GameWorld.DebugLog("开服定制天内不能重置!")
        return
    setScoreMin, setScoreMax, refScoreMax = IpyGameDataPY.GetFuncEvalCfg("ArenaSet", 2)
    setScore = setScoreMin
    if openServerDay <= customMaxServerDay and OSSeasonState == 0:
        PlayerControl.NomalDictSetProperty(curPlayer, ChConfig.Def_PDict_ArenaOSSeasonState, 1)
        GameWorld.DebugLog("竞技场开服定制赛季! setScore=%s" % setScore)
    else:
        PlayerControl.NomalDictSetProperty(curPlayer, ChConfig.Def_PDict_ArenaOSSeasonState, customMaxServerDay + 1)
        preSeasonscore = curPlayer.NomalDictGetProperty(ChConfig.Def_PDict_ArenaScore)
        if preSeasonscore <= setScoreMin:
            setScore = setScoreMin
        elif preSeasonscore >= refScoreMax:
            setScore = setScoreMax
        else:
            # 按比例降低积分,都减去最低分的差值算比例
            calcScore = preSeasonscore - setScoreMin
            setScore = setScoreMin + int(calcScore * (setScoreMax - setScoreMin) / float(refScoreMax - setScoreMin))
        GameWorld.DebugLog("竞技场赛季重置! preSeasonscore=%s,setScore=%s" % (preSeasonscore, setScore))
    PlayerControl.NomalDictSetProperty(curPlayer, ChConfig.Def_PDict_ArenaScore, setScore)
    PlayerControl.NomalDictSetProperty(curPlayer, ChConfig.Def_PDict_ArenaItemAddCount, 0)
    PlayerControl.NomalDictSetProperty(curPlayer, ChConfig.Def_PDict_ArenaBattleCountDay, 0)
    PlayerControl.NomalDictSetProperty(curPlayer, ChConfig.Def_PDict_ArenaMatchRefreshCount, 0)
    Sync_ArenaInfo(curPlayer, True)
    initScore = IpyGameDataPY.GetFuncCfg("ArenaSet", 1)
    GameWorld.DebugLog("竞技场赛季重置!initScore=%s" % (initScore))
    PlayerControl.NomalDictSetProperty(curPlayer, ChConfig.Def_PDict_ArenaScore, initScore)
    Sync_ArenaInfo(curPlayer)
    return
def CheckArenaBattleCount(curPlayer):
    ## 验证是否还有对战次数
    todayBattleCount = curPlayer.NomalDictGetProperty(ChConfig.Def_PDict_ArenaBattleCountDay)
    itemAddCount = curPlayer.NomalDictGetProperty(ChConfig.Def_PDict_ArenaItemAddCount)
    dayFreeCount = IpyGameDataPY.GetFuncCfg("ArenaSet", 3)
    return todayBattleCount < (dayFreeCount + itemAddCount)
def GetArenaTicketStoreMax(curPlayer):
    ## 获取挑战券存储上限
    baseCnt = IpyGameDataPY.GetFuncCfg("ArenaSet", 3)
    # 其他特权提升上限
    storeMax = baseCnt
    return storeMax
#// B2 09 竞技场匹配玩家 #tagCMArenaMatch
#// B2 09 演武场匹配玩家 #tagCSArenaMatch
#
#struct    tagCMArenaMatch
#struct    tagCSArenaMatch
#{
#    tagHead         Head;
#    BYTE        IsRefresh;    // 0-打开界面无匹配数据时时查询,1-强制刷新匹配列表
#    BYTE        IsRefresh;    // 0-打开界面无匹配数据时查询,1-强制刷新匹配列表
#};
def OnArenaMatch(index, clientData, tick):
    curPlayer = GameWorld.GetPlayerManager().GetPlayerByIndex(index)
    isRefresh = clientData.IsRefresh
    playerID = curPlayer.GetPlayerID()
    refreshCountLimit = IpyGameDataPY.GetFuncCfg("ArenaSet", 5)
    if isRefresh and refreshCountLimit:
        refreshCount = curPlayer.NomalDictGetProperty(ChConfig.Def_PDict_ArenaMatchRefreshCount)
        if refreshCount >= refreshCountLimit:
            GameWorld.DebugLog("竞技场刷新匹配玩家次数已满!refreshCount=%s >= %s" % (refreshCount, refreshCountLimit), playerID)
            return
    if not GameWorld.SetPlayerTickTime(curPlayer, ChConfig.TYPE_Player_Tick_Arena, tick):
        GameWorld.DebugLog("竞技场匹配操作CD中...", playerID)
        PlayerControl.NotifyCode(curPlayer, "RequestLater")
        return
    playerLV = curPlayer.GetLV()
    playerScore = curPlayer.NomalDictGetProperty(ChConfig.Def_PDict_ArenaScore)
    msgInfo = str(["MatchRefresh", {"isRefresh":isRefresh, "playerLV":playerLV, "playerScore":playerScore}])
    GameWorld.DebugLog("竞技场发送GameServer匹配: %s" % msgInfo, playerID)
    GameWorld.GetPlayerManager().GameServer_QueryPlayerResult(curPlayer.GetID(), 0, 0, "Arena", msgInfo, len(msgInfo))
    DoArenaMatchRefresh(curPlayer, isRefresh)
    return
def GMArenaMatch(curPlayer, gmMatchIDList):
    playerID = curPlayer.GetPlayerID()
    playerLV = curPlayer.GetLV()
    playerScore = curPlayer.NomalDictGetProperty(ChConfig.Def_PDict_ArenaScore)
    msgInfo = str(["MatchRefresh", {"isRefresh":1, "playerLV":playerLV, "playerScore":playerScore, "gmMatchIDList":gmMatchIDList}])
    GameWorld.DebugLog("竞技场发送GameServer匹配: %s" % msgInfo, playerID)
    GameWorld.GetPlayerManager().GameServer_QueryPlayerResult(curPlayer.GetID(), 0, 0, "Arena", msgInfo, len(msgInfo))
    ## GM直接匹配
    isRefresh = True
    DoArenaMatchRefresh(curPlayer, isRefresh, gmMatchIDList)
    return
#// B2 10 竞技场挑战玩家 #tagCMArenaBattle
#
#struct    tagCMArenaBattle
#{
#    tagHead         Head;
#    DWORD        TagPlayerID;    // 目标玩家ID或机器人ID
#    BYTE        Result;    // 0-进入自定义场景发送通知后端;1-胜利(后端处理,暂时不需要发送此状态);2-失败(前端被对手击杀需要发送此状态)
#};
def OnArenaBattle(index, clientData, tick):
    curPlayer = GameWorld.GetPlayerManager().GetPlayerByIndex(index)
def DoArenaMatchRefresh(curPlayer, isRefresh, gmMatchIDList=None, isSys=False):
    ## 玩家刷新匹配对手
    playerID = curPlayer.GetPlayerID()
    tagPlayerID = clientData.TagPlayerID
    result = clientData.Result
    playerScore = curPlayer.NomalDictGetProperty(ChConfig.Def_PDict_ArenaScore)
    matchScoreList = IpyGameDataPY.GetFuncEvalCfg("ArenaMatch", 1)
    
    GameWorld.DebugLog("竞技场挑战玩家! tagPlayerID=%s,result=%s" % (tagPlayerID, result), playerID)
    if not tagPlayerID:
    GameWorld.DebugLog("竞技场玩家刷新匹配列表: isRefresh=%s,playerScore=%s,gmMatchIDList=%s,isSys=%s" % (isRefresh, playerScore, gmMatchIDList, isSys), playerID)
    GameWorld.DebugLog("    matchScoreList=%s" % (matchScoreList), playerID)
    # 匹配对象缓存
    needMatchCount = len(matchScoreList)
    if playerID not in PyGameData.g_arenaPlayerMatchDict:
        PyGameData.g_arenaPlayerMatchDict[playerID] = []
    matchIDList = PyGameData.g_arenaPlayerMatchDict[playerID]
    if len(matchIDList) > needMatchCount:
        matchIDList = matchIDList[:needMatchCount] # 删除多余的个数,一般都是相同的,除非修改匹配数重读配置
    if not isRefresh and len(matchIDList) == needMatchCount:
        # 非刷新的并且已经有记录的直接同步
        GameWorld.DebugLog("    非刷新且有数据,直接同步!", playerID)
        __SyncMatchList(curPlayer, matchIDList)
        return
    if not result:
        GameWorld.DebugLog("更新竞技场对战对手ID! tagPlayerID=%s" % tagPlayerID, playerID)
        # 记录对手ID
        PlayerControl.NomalDictSetProperty(curPlayer, ChConfig.Def_PDict_ArenaBattleTagID, tagPlayerID)
        return
    if tagPlayerID >= 10000:
        if result:
            GameWorld.DebugLog("真人由后端镜像PK决定胜负! tagPlayerID=%s" % tagPlayerID, playerID)
    if isRefresh and not gmMatchIDList and not isSys:
        costMoney, moneyValue = IpyGameDataPY.GetFuncEvalCfg("ArenaMatch", 4)
        if not costMoney or not moneyValue or not PlayerControl.PayMoney(curPlayer, costMoney, moneyValue, "Arena"):
            return
        
    isWin = 1 if result == 1 else 0
#    # 木桩被击杀,后端判断,其他前端同步
#    if isWin:
#        GameWorld.ErrLog("前端不能同步竞技场胜利状态!", playerID)
#        return
    recTagPlayerID = curPlayer.NomalDictGetProperty(ChConfig.Def_PDict_ArenaBattleTagID)
    if tagPlayerID != recTagPlayerID:
        GameWorld.ErrLog("竞技场结算时对手ID不一致! tagPlayerID(%s) != recTagPlayerID(%s)" % (tagPlayerID, recTagPlayerID), playerID)
        __DoArenaBattleOver(curPlayer)
    billboardMgr = DBDataMgr.GetBillboardMgr()
    billBoard = billboardMgr.GetBillboard(ShareDefine.Def_BT_Arena)
    if not billBoard:
        return
    maxOrder = billBoard.GetCount()
    playerOrder = billBoard.IndexOfByID(playerID) + 1  # 玩家在排行榜中的名次,没有名次为-1
    
    # 失败结算入口: 前端同步
    SendGameServer_ArenaBattleOver(curPlayer, isWin)
    return
def OnKillBattleNPC(curPlayer, curNPC):
#    ## 击杀对手,前端本,使用木桩NPC作为对手
#
#    if curNPC.GetGameObjType() != IPY_GameWorld.gotNPC or curNPC.GetType() not in [ChConfig.ntPriWoodPilePVE, ChConfig.ntPriWoodPilePVP]:
#        GameWorld.DebugLog("击杀非木桩NPC,不结算!")
#        return
#
#    # 胜利结算入口:后端验证击杀对手
#    tagPlayerID = curPlayer.NomalDictGetProperty(ChConfig.Def_PDict_ArenaBattleTagID)
#    GameWorld.DebugLog("竞技场击杀对手! tagPlayerID=%s" % tagPlayerID, curPlayer.GetPlayerID())
#    isWin = 1
#    SendGameServer_ArenaBattleOver(curPlayer, isWin)
    return
def SendGameServer_ArenaBattleOver(curPlayer, isWin):
    ## 发送GameServer通知战斗结算
    matchRobotCntDict = IpyGameDataPY.GetFuncEvalCfg("ArenaMatch", 3)
    if playerOrder > 0:
        matchRobotRange = GameWorld.GetOrderValueByDict(matchRobotCntDict, playerOrder)
        matchRobotCnt = random.randint(matchRobotRange[0], matchRobotRange[1])
    else:
        matchRobotCnt = needMatchCount
    matchPlayerCnt = needMatchCount - matchRobotCnt
    GameWorld.DebugLog("    maxOrder=%s,playerOrder=%s,matchRobotCnt=%s,matchPlayerCnt=%s" % (maxOrder, playerOrder, matchRobotCnt, matchPlayerCnt), playerID)
    
    playerID = curPlayer.GetPlayerID()
    tagPlayerID = curPlayer.NomalDictGetProperty(ChConfig.Def_PDict_ArenaBattleTagID)
    if not tagPlayerID:
        GameWorld.ErrLog("竞技场结算时没有对手ID!", playerID)
        __DoArenaBattleOver(curPlayer)
        return
    if not CheckArenaBattleCount(curPlayer):
        GameWorld.ErrLog("竞技场已经没有对战次数!", playerID)
        __DoArenaBattleOver(curPlayer)
        return
    tick = GameWorld.GetGameWorld().GetTick()
    if not GameWorld.SetPlayerTickTime(curPlayer, ChConfig.TYPE_Player_Tick_Arena, tick):
        GameWorld.ErrLog("结算竞技场CD中!tagPlayerID=%s" % tagPlayerID, playerID)
        return
    playerLV = curPlayer.GetLV()
    playerScore = curPlayer.NomalDictGetProperty(ChConfig.Def_PDict_ArenaScore)
    msgInfo = str(["BattleResult", {"tagPlayerID":tagPlayerID, "isWin":isWin, "playerLV":playerLV, "playerScore":playerScore,
                                    "realmLV":curPlayer.GetOfficialRank(), "fightPower":PlayerControl.GetFightPower(curPlayer)}])
    GameWorld.DebugLog("竞技场发送GameServer结算: %s" % msgInfo, playerID)
    GameWorld.GetPlayerManager().GameServer_QueryPlayerResult(curPlayer.GetID(), 0, 0, "Arena", msgInfo, len(msgInfo))
    return
def __DoArenaBattleOver(curPlayer, retDict={}):
    ## 主动战斗结算奖励
    # @param isOK: True时才结算奖励,防止某些异常情况无法结算通知前端FBOver,导致卡副本
    GameWorld.DebugLog("结算竞技场对战奖励! retDict=%s" % retDict)
    PlayerControl.NomalDictSetProperty(curPlayer, ChConfig.Def_PDict_ArenaBattleTagID, 0)
    isOK = retDict.get("isOK", False)
    isWin = retDict.get("isWin", 0)
    if not isOK:
        # 一直异常的情况直接同步结束包,防止不结算卡副本
        FBCommon.NotifyFBOver(curPlayer, ChConfig.Def_FBMapID_ArenaBattle, 0, isWin)
        return
    #GameServer MapServer 同步有一定时间差,本功能存在被动挑战引发积分变动的情况,
    #curScore = curPlayer.NomalDictGetProperty(ChConfig.Def_PDict_ArenaScore)
    addScore = retDict["addScore"]
    updScore = retDict["updScore"]
    curOrder = retDict["curOrder"]
    updOrder = retDict["updOrder"]
    offlineRecTime = retDict.get("offlineRecTime", 0)
    # 扣次数
    if not offlineRecTime or GameWorld.CheckTimeIsSameServerDayEx(offlineRecTime):
        todayBattleCount = curPlayer.NomalDictGetProperty(ChConfig.Def_PDict_ArenaBattleCountDay) + 1
        PlayerControl.NomalDictSetProperty(curPlayer, ChConfig.Def_PDict_ArenaBattleCountDay, todayBattleCount)
    PlayerControl.NomalDictSetProperty(curPlayer, ChConfig.Def_PDict_ArenaMatchRefreshCount, 0)
    fromLowerCnt, matchPerRank = IpyGameDataPY.GetFuncEvalCfg("ArenaMatch", 2)
    toOrder = playerOrder + fromLowerCnt * matchPerRank # 从低名次往高名次匹配
    GameWorld.DebugLog("    fromLowerCnt=%s,matchPerRank=%s,toOrder=%s" % (fromLowerCnt, matchPerRank, toOrder), playerID)
    matchOrderList = [] # 匹配到的名次
    for _ in range(matchPlayerCnt):
        fromOrder = max(1, toOrder - matchPerRank)
        if toOrder <= fromOrder:
            break
        orderList = range(fromOrder, toOrder)
        random.shuffle(orderList)
        if playerOrder in orderList:
            orderList.remove(playerOrder) # 不包含自己
        if not orderList:
            break
        
    # 更新积分
    PlayerControl.NomalDictSetProperty(curPlayer, ChConfig.Def_PDict_ArenaScore, updScore)
    highestScore = curPlayer.NomalDictGetProperty(ChConfig.Def_PDict_ArenaHighestScore)
    if updScore > highestScore:
        highestScore = updScore
        PlayerControl.NomalDictSetProperty(curPlayer, ChConfig.Def_PDict_ArenaHighestScore, highestScore)
        GameWorld.DebugLog("    更新竞技场历史最高分! %s" % highestScore)
        if fromOrder == 1:
            for order in orderList:
                if order > maxOrder:
                    continue
                matchOrderList.append(order)
                if len(matchOrderList) >= matchPlayerCnt:
                    break
        else:
            order = orderList[0]
            if order <= maxOrder:
                matchOrderList.append(order)
        GameWorld.DebugLog("    匹配玩家: fromOrder=%s,toOrder=%s,matchOrderList=%s" % (fromOrder, toOrder, matchOrderList), playerID)
        toOrder = fromOrder - 1
        
    # 胜利给额外奖励
    itemList = retDict.get("awardItemList", [])
    ItemControler.GivePlayerItemOrMail(curPlayer, itemList)
    jsonItemList = FBCommon.GetJsonItemList(itemList)
    overDict = {FBCommon.Over_itemInfo:jsonItemList, "addScore":addScore, "updScore":updScore, "curOrder":curOrder, "updOrder":updOrder}
    FBCommon.NotifyFBOver(curPlayer, ChConfig.Def_FBMapID_ArenaBattle, 0, isWin, overDict)
    Sync_ArenaInfo(curPlayer)
    # 触发任务
    EventShell.EventRespons_ArenaBattleOver(curPlayer)
    EventShell.EventRespons_ArenaHighestScore(curPlayer)
    PlayerWeekParty.AddWeekPartyActionCnt(curPlayer, ChConfig.Def_WPAct_Arena, 1)
    PlayerFeastTravel.AddFeastTravelTaskValue(curPlayer, ChConfig.Def_FeastTravel_Arena, 1)
    PlayerActivity.AddDailyActionFinishCnt(curPlayer, ShareDefine.DailyActionID_Arena, 1)
    PlayerSuccess.DoAddSuccessProgress(curPlayer, ShareDefine.SuccType_Arena, 1)
    PlayerGubao.AddGubaoItemEffValue(curPlayer, PlayerGubao.GubaoEffType_Arena, 1)
    PlayerActTask.AddActTaskValue(curPlayer, ChConfig.ActTaskType_Arena)
    return
def __DoUpdateArenaScore(curPlayer, cmdDict={}):
    ''' 玩家直接更新积分,有以下几种情况,都是被挑战的,只更新积分
    1. 被动挑战在线时直接更新积分
    2. 离线/脱机时被挑战,上线后同步最新积分
    '''
    playerScore = curPlayer.NomalDictGetProperty(ChConfig.Def_PDict_ArenaScore)
    updScore = cmdDict.get("updScore", playerScore)
    if updScore == playerScore:
        return
    PlayerControl.NomalDictSetProperty(curPlayer, ChConfig.Def_PDict_ArenaScore, updScore)
    Sync_ArenaInfo(curPlayer)
    return
def GameServer_ArenaResult(curPlayer, msgList, tick):
    if not msgList:
        return
    cmd = msgList[0]
    cmdDict = msgList[1] if len(msgList) > 1 else {}
    retDict = msgList[2] if len(msgList) > 2 else {}
    # 刷新匹配
    if cmd == "MatchRefresh":
        isRefresh = cmdDict.get("isRefresh", False)
        refreshCountLimit = IpyGameDataPY.GetFuncCfg("ArenaSet", 5)
        if isRefresh and refreshCountLimit:
            updRefreshCount = min(250, curPlayer.NomalDictGetProperty(ChConfig.Def_PDict_ArenaMatchRefreshCount) + 1)
            PlayerControl.NomalDictSetProperty(curPlayer, ChConfig.Def_PDict_ArenaMatchRefreshCount, updRefreshCount)
            GameWorld.DebugLog("更新竞技场刷新匹配次数! updRefreshCount=%s" % updRefreshCount)
            Sync_ArenaInfo(curPlayer)
    # GM指定匹配测试
    if gmMatchIDList != None and curPlayer.GetGMLevel():
        for gmMatchID in gmMatchIDList:
            if gmMatchID == playerID:
                GameWorld.DebugAnswer(curPlayer, "目标ID不能是自己!无法匹配!%s" % gmMatchID)
                continue
            
    # 主动对战结果
    elif cmd == "BattleResult":
        __DoArenaBattleOver(curPlayer, retDict)
            gmMatchOrder = billBoard.IndexOfByID(gmMatchID) + 1
            if gmMatchOrder <= 0:
                GameWorld.DebugAnswer(curPlayer, "目标ID不在榜单上!无法匹配!%s" % gmMatchID)
                continue
            GameWorld.DebugAnswer(curPlayer, "指定匹配ID(%s),order(%s)" % (gmMatchID, gmMatchOrder))
            if gmMatchID not in matchOrderList:
                matchOrderList.insert(0, gmMatchOrder)
        matchOrderList = matchOrderList[:needMatchCount]
        
    # 被动挑战更新积分
    elif cmd == "UpdScore":
        __DoUpdateArenaScore(curPlayer, cmdDict)
    matchOrderList.sort()
    matchIDList = [] # 最终匹配的玩家ID列表
    for matchOrder in matchOrderList:
        if matchOrder > maxOrder or matchOrder <= 0:
            break
        billData = billBoard.At(matchOrder - 1)
        matchIDList.append(billData.GetID())
        
    needRobotCnt = needMatchCount - len(matchIDList)
    GameWorld.DebugLog("    匹配榜单结果: matchIDList=%s,matchOrderList=%s,needRobotCnt=%s" % (matchIDList, matchOrderList, needRobotCnt), playerID)
    ipyDataMgr = IpyGameDataPY.IPY_Data()
    robotMax = ipyDataMgr.GetRobotCount()
    doCnt = 100
    while doCnt > 0 and needRobotCnt > 0 and robotMax:
        doCnt -= 1
        robotIndex = random.randint(0, robotMax - 1)
        robotIpyData = ipyDataMgr.GetRobotByIndex(robotIndex)
        robotID = robotIpyData.GetID()
        if robotID not in matchIDList:
            matchIDList.append(robotID)
            needRobotCnt -= 1
    GameWorld.DebugLog("    最终匹配结果: matchIDList=%s" % matchIDList, playerID)
    PyGameData.g_arenaPlayerMatchDict[playerID] = matchIDList
    __SyncMatchList(curPlayer, matchIDList)
    return
def Sync_ArenaInfo(curPlayer, isReset=False):
    clientPack = ChPyNetSendPack.tagMCArenaPlayerInfo()
    clientPack.IsReset = 1 if isReset else 0
    clientPack.Score = curPlayer.NomalDictGetProperty(ChConfig.Def_PDict_ArenaScore)
    clientPack.BattleCountToday = curPlayer.NomalDictGetProperty(ChConfig.Def_PDict_ArenaBattleCountDay)
    clientPack.MatchRefreshCount = curPlayer.NomalDictGetProperty(ChConfig.Def_PDict_ArenaMatchRefreshCount)
    clientPack.ItemAddBattleCountToday = curPlayer.NomalDictGetProperty(ChConfig.Def_PDict_ArenaItemAddCount)
def __SyncMatchList(curPlayer, matchIDList):
    ## 同步匹配列表
    clientPack = ChPyNetSendPack.tagSCArenaMatchList()
    clientPack.MatchList = []
    for matchID in matchIDList:
        viewCache = PlayerViewCache.FindViewCache(matchID)
        matchInfo = ChPyNetSendPack.tagSCArenaMatchInfo()
        matchInfo.PlayerID = matchID
        if viewCache:
            matchInfo.PlayerName = viewCache.GetPlayerName()
            matchInfo.RealmLV = viewCache.GetRealmLV()
            matchInfo.Face = viewCache.GetFace()
            matchInfo.FacePic = viewCache.GetFacePic()
            matchInfo.FightPower = viewCache.GetFightPower()
            matchInfo.FightPowerEx = viewCache.GetFightPowerEx()
        else:
            matchInfo.PlayerName = "p%s" % matchID
        clientPack.MatchList.append(matchInfo)
    clientPack.MatchCount = len(clientPack.MatchList)
    NetPackCommon.SendFakePack(curPlayer, clientPack)
    return
def Sync_ArenaInfo(curPlayer):
    clientPack = ChPyNetSendPack.tagSCArenaPlayerInfo()
    clientPack.Score = curPlayer.NomalDictGetProperty(ChConfig.Def_PDict_ArenaScore)
    NetPackCommon.SendFakePack(curPlayer, clientPack)
    return