hxp
昨天 9d5ec7599f3abe0cebb76ce1df3c3b8c4e0aa51e
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/PlayerArena.py
@@ -4,371 +4,436 @@
#
##@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 PlayerGoldInvest
import PlayerViewCache
import NetPackCommon
import TurnAttack
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
    billBoard.SortDelayDo()
    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):
    storeMax = IpyGameDataPY.GetFuncCfg("ArenaSet", 3)
    PlayerControl.GiveMoney(curPlayer, ShareDefine.TYPE_Price_ArenaTicket, storeMax, "ArenaOpen")
    __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):
    ## 获取挑战券存储上限
    storeMax = IpyGameDataPY.GetFuncCfg("ArenaSet", 3)
    # 其他特权提升上限
    storeMax += PlayerGoldInvest.GetArenaTicketMax(curPlayer)
    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)
    needMatchCount = len(matchScoreList)
    
    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("needMatchCount=%s,matchScoreList=%s" % (needMatchCount, matchScoreList), playerID)
    # 匹配对象缓存
    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
    matchIDList = [] # 最终匹配的玩家ID列表
    matchOrderList = [] # 匹配到的名次
    viewCacheDict = {}
    
    recTagPlayerID = curPlayer.NomalDictGetProperty(ChConfig.Def_PDict_ArenaBattleTagID)
    if tagPlayerID != recTagPlayerID:
        GameWorld.ErrLog("竞技场结算时对手ID不一致! tagPlayerID(%s) != recTagPlayerID(%s)" % (tagPlayerID, recTagPlayerID), playerID)
        __DoArenaBattleOver(curPlayer)
        return
    # 失败结算入口: 前端同步
    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通知战斗结算
    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)
    # 更新积分
    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)
    # 胜利给额外奖励
    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)
    robotFPCoefficient =  IpyGameDataPY.GetFuncCfg("ArenaMatchRobot", 1) # 必定匹配机器人的玩家战力系数,玩家战力*该系数<=机器人最高战力时必定匹配机器人
    playerFightPower = PlayerControl.GetFightPower(curPlayer)
    robotFPSortList = PlayerViewCache.GetRobotFightPowerSortList()
    robotFightPowerMax = robotFPSortList[0][0] if robotFPSortList else 0
    robotCntTotal = len(robotFPSortList)
    GameWorld.DebugLog("玩家战力=%s,机器人最大战力=%s,匹配机器人战力系数=%s,机器人总数=%s"
                       % (playerFightPower, robotFightPowerMax, robotFPCoefficient, robotCntTotal), playerID)
    reFightPower = playerFightPower * robotFPCoefficient # 参考战力
    if reFightPower <= robotFightPowerMax:
        # 匹配机器人相对玩家战力百分比范围列表  [[相对玩家战力百分比A, B], ..],支持负数,如-30就是机器人比玩家低30%战力,10就是机器人比玩家高10%战力
        matchRobotFPPerList = IpyGameDataPY.GetFuncEvalCfg("ArenaMatchRobot", 2)
        GameWorld.DebugLog("全部匹配机器人: %s, reFightPower=%s <= %s" % (matchRobotFPPerList, reFightPower, robotFightPowerMax), playerID)
        loopIndex = 0
        for fpPerRange in matchRobotFPPerList[::-1]: # 从高战力往低战力匹配
            fpPerA, fpPerB = fpPerRange
            GameWorld.DebugLog("    处理匹配机器人战力段: fpPerA=%s,fpPerB=%s,loopIndex=%s" % (fpPerA, fpPerB, loopIndex), playerID)
            if loopIndex >= robotCntTotal:
                continue
            robotFPIDList = []
            for robotIndex in range(loopIndex, robotCntTotal):
                robotFightPower, robotID = robotFPSortList[robotIndex]
                fpPer = (robotFightPower / float(playerFightPower) - 1) * 100
                if fpPer < fpPerA:
                    GameWorld.DebugLog("        比低百分比还小的战力跳出,进入下一段匹配逻辑: %s,robotID=%s,robotFightPower=%s,fpPer=%s < %s"
                                       % (robotIndex, robotID, robotFightPower, fpPer, fpPerA), playerID)
                    break
                loopIndex = robotIndex + 1
                if fpPer > fpPerB:
                    #GameWorld.DebugLog("        比高百分比还大的战力跳过,不匹配: %s,robotID=%s,robotFightPower=%s,fpPer=%s > %s"
                    #                   % (robotIndex, robotID, robotFightPower, fpPer, fpPerB), playerID)
                    continue
                robotFPIDList.append([robotFightPower, robotID])
                #GameWorld.DebugLog("        战力在匹配段范围内,可匹配: %s,robotID=%s,robotFightPower=%s,%s <= fpPer(%s) <= %s"
                #                   % (robotIndex, robotID, robotFightPower, fpPerA, fpPer, fpPerB), playerID)
            if not robotFPIDList:
                GameWorld.DebugLog("        该战力范围段没有符合的机器人!", playerID)
                continue
            random.shuffle(robotFPIDList)
            robotFightPower, robotID = robotFPIDList[0]
            matchIDList.append(robotID)
            GameWorld.DebugLog("        匹配机器人: robotID=%s,robotFightPower=%s" % (robotID, robotFightPower), playerID)
            
    # 主动对战结果
    elif cmd == "BattleResult":
        __DoArenaBattleOver(curPlayer, retDict)
        if len(matchIDList) < needMatchCount:
            GameWorld.DebugLog("    可匹配的机器人还不够,从剩下未遍历的机器人内匹配: loopIndex=%s" % loopIndex, playerID)
            if loopIndex < robotCntTotal:
                randIndexList = range(loopIndex, robotCntTotal)
                random.shuffle(randIndexList)
                matchIndexList = randIndexList[:needMatchCount-len(matchIDList)]
                matchIndexList.sort()
                for robotIndex in matchIndexList:
                    robotFightPower, robotID = robotFPSortList[robotIndex]
                    matchIDList.append(robotID)
                    GameWorld.DebugLog("        匹配机器人: robotID=%s,robotFightPower=%s" % (robotID, robotFightPower), playerID)
    else:
        # 匹配真人
        GameWorld.DebugLog("匹配真人: reFightPower=%s > %s" % (reFightPower, robotFightPowerMax), playerID)
        billboardMgr = DBDataMgr.GetBillboardMgr()
        billBoard = billboardMgr.GetBillboard(ShareDefine.Def_BT_Arena)
        if not billBoard:
            return
        
    # 被动挑战更新积分
    elif cmd == "UpdScore":
        __DoUpdateArenaScore(curPlayer, cmdDict)
        maxOrder = billBoard.GetCount()
        playerOrder = billBoard.IndexOfByID(playerID) + 1  # 玩家在排行榜中的名次,没有名次为-1
        leastRobotCnt = IpyGameDataPY.GetFuncCfg("ArenaMatchRobot", 3)
        matchPlayerCnt = needMatchCount - leastRobotCnt
        GameWorld.DebugLog("    maxOrder=%s,playerOrder=%s,matchPlayerCnt=%s,leastRobotCnt=%s" % (maxOrder, playerOrder, matchPlayerCnt, leastRobotCnt), playerID)
        
        fromLowerCnt, matchPerRank = IpyGameDataPY.GetFuncEvalCfg("ArenaMatch", 2)
        toOrder = playerOrder + fromLowerCnt * matchPerRank # 从低名次往高名次匹配
        GameWorld.DebugLog("    fromLowerCnt=%s,matchPerRank=%s,toOrder=%s" % (fromLowerCnt, matchPerRank, toOrder), playerID)
        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
            for order in orderList:
                if order > maxOrder:
                    continue
                billData = billBoard.At(order - 1)
                tagID = billData.GetID()
                viewCache = PlayerViewCache.FindBattleViewCache(tagID)
                if not viewCache:
                    GameWorld.DebugLog("    无战斗缓存的玩家不匹配: tagID=%s,order=%s,fromOrder=%s,toOrder=%s" % (tagID, order, fromOrder, toOrder), playerID)
                    continue
                matchOrderList.append(order)
                viewCacheDict[tagID] = viewCache
                if fromOrder == 1:
                    if len(matchOrderList) >= matchPlayerCnt:
                        break
                else:
                    break
            GameWorld.DebugLog("    匹配玩家: fromOrder=%s,toOrder=%s,matchOrderList=%s" % (fromOrder, toOrder, matchOrderList), playerID)
            toOrder = fromOrder - 1
        matchOrderList.sort()
        for matchOrder in matchOrderList:
            if matchOrder > maxOrder or matchOrder <= 0:
                break
            billData = billBoard.At(matchOrder - 1)
            tagID = billData.GetID()
            matchIDList.append(tagID)
        GameWorld.DebugLog("    匹配榜单结果: matchIDList=%s,matchOrderList=%s" % (matchIDList, matchOrderList), playerID)
    # GM指定匹配测试
    if gmMatchIDList != None and curPlayer.GetGMLevel():
        for gmMatchID in gmMatchIDList:
            if gmMatchID == playerID:
                GameWorld.DebugAnswer(curPlayer, "不能匹配自己!%s" % gmMatchID)
                continue
            if gmMatchID in matchIDList:
                continue
            viewCache = PlayerViewCache.FindBattleViewCache(gmMatchID)
            if not viewCache:
                GameWorld.DebugAnswer(curPlayer, "没有战斗缓存目标不匹配!%s" % gmMatchID)
                continue
            GameWorld.DebugAnswer(curPlayer, "指定匹配ID(%s)" % (gmMatchID))
            matchIDList.append(gmMatchID)
            viewCacheDict[gmMatchID] = viewCache
        matchOrderList = matchOrderList[:needMatchCount]
        GameWorld.DebugLog("    指定匹配结果: matchIDList=%s" % (matchIDList), playerID)
    needRobotCnt = needMatchCount - len(matchIDList)
    GameWorld.DebugLog("    最终还需机器人数=%s" % (needRobotCnt), playerID)
    robotFPIDList = []
    doCnt = 100
    while doCnt > 0 and needRobotCnt > 0 and robotCntTotal:
        doCnt -= 1
        robotIndex = random.randint(0, robotCntTotal - 1)
        robotFightPower, robotID = robotFPSortList[robotIndex]
        if robotID not in matchIDList:
            matchIDList.append(robotID)
            robotFPIDList.append([robotFightPower, robotID])
            needRobotCnt -= 1
    if robotFPIDList:
        robotFPIDList.sort(reverse=True)
        #GameWorld.DebugLog("    补充机器人战力ID排序=%s" % (robotFPIDList), playerID)
        for _, robotID in robotFPIDList:
            if robotID in matchIDList:
                matchIDList.remove(robotID)
                matchIDList.append(robotID)
    GameWorld.DebugLog("    最终匹配结果: matchIDList=%s" % matchIDList, playerID)
    PyGameData.g_arenaPlayerMatchDict[playerID] = matchIDList
    __SyncMatchList(curPlayer, matchIDList, viewCacheDict)
    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, viewCacheDict={}):
    ## 同步匹配列表
    clientPack = ChPyNetSendPack.tagSCArenaMatchList()
    clientPack.MatchList = []
    for matchID in matchIDList:
        if matchID in viewCacheDict:
            viewCache = viewCacheDict[matchID]
        else:
            viewCache = PlayerViewCache.FindBattleViewCache(matchID)
        if not viewCache:
            continue
        matchInfo = ChPyNetSendPack.tagSCArenaMatchInfo()
        matchInfo.PlayerID = matchID
        if viewCache:
            fightPower = TurnAttack.GetCacheLineupFightPower(viewCache, ShareDefine.BatPreset_ArenaDef)
            matchInfo.PlayerName = viewCache.GetPlayerName()
            matchInfo.RealmLV = viewCache.GetRealmLV()
            matchInfo.LV = viewCache.GetLV()
            matchInfo.Face = viewCache.GetFace()
            matchInfo.FacePic = viewCache.GetFacePic()
            matchInfo.FightPower = fightPower % ChConfig.Def_PerPointValue
            matchInfo.FightPowerEx = fightPower / ChConfig.Def_PerPointValue
            matchInfo.TitleID = viewCache.GetTitleID()
        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)
    clientPack.WinCnt = curPlayer.NomalDictGetProperty(ChConfig.Def_PDict_ArenaWinCnt)
    NetPackCommon.SendFakePack(curPlayer, clientPack)
    return