526 【挑战】PVP群英榜-后端(本服群英榜;优化机器人表支持按功能加载不同的机器人;)
20个文件已修改
2个文件已添加
1473 ■■■■■ 已修改文件
PySysDB/PySysDBPY.h 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/PyNetPack.ini 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/ChConfig.py 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/ChPyNetPack.py 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/ChPyNetSendPack.py 255 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/DB/StructData/DBBillboard.py 71 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/GM/Commands/Billboard.py 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/GM/Commands/Qunying.py 82 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/GameWorldLogic/FBLogic.py 51 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/GameWorldLogic/FBProcess/FBCommon.py 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/GameWorldLogic/FBProcess/GameLogic_Qunying.py 599 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/GameWorldLogic/GameWorldEvent.py 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/IpyGameDataPY.py 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/ChPlayer.py 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/GameFuncComm.py 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/PlayerBillboard.py 171 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/PlayerControl.py 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/PlayerEventCounter.py 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/PlayerState.py 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/PlayerViewCache.py 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/PyGameData.py 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/ShareDefine.py 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PySysDB/PySysDBPY.h
@@ -2008,6 +2008,17 @@
    dict        Attr;    //属性
};
//群英榜分区表
struct    QunyingCross
{
    char        _AppID;    //AppID
    WORD        _ZoneID;    //分区ID
    DWORD        CrossServerID;    //跨服服务器ID
    list        ServerIDList;    //互通服务器ID列表
    BYTE        SplitServerCnt;    //按X个相邻服分割
    BYTE        MatchServerCnt;    //分割区服内X个服随机匹配一组
};
//跨服公会表
struct    FamilyCross
{
@@ -2195,5 +2206,9 @@
{
    DWORD        _ID;    //机器人ID,同玩家ID
    char        RobotName;
    BYTE        RealmLV;    //指定官职
    DWORD        TempNum;    //功能模版
    DWORD        TempValue1;    //功能值1
    DWORD        TempValue2;    //功能值2
    char        ViewCache;    //机器人缓存
};
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/PyNetPack.ini
@@ -710,6 +710,18 @@
PacketSubCMD_1=0x09
PacketCallFunc_1=OnArenaMatch
;群英榜
[GameLogic_Qunying]
ScriptName = Player\GameLogic_Qunying.py
Writer = hxp
Releaser = hxp
RegType = 0
RegisterPackCount = 1
PacketCMD_1=0xB2
PacketSubCMD_1=0x10
PacketCallFunc_1=OnQunyingMatch
;古宝
[PlayerGubao]
ScriptName = Player\PlayerGubao.py
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/ChConfig.py
@@ -1894,6 +1894,8 @@
Def_FBMapID_Tianzi = 30020 # 天子考验
Def_FBMapID_Dingjunge = 30030 # 定军阁
Def_FBMapID_Qunying = 32000 # 群英榜
#线路未过关时免费的地图
UnPassFreeMapIDList = [Def_FBMapID_Zhanchui, Def_FBMapID_Dingjunge]
#按星级记录过关的地图
@@ -1908,7 +1910,9 @@
ExclusiveBatAttrMapIDList = [Def_FBMapID_Dingjunge]
#地图功能专用预设方案,如果没有配置的功能则默认使用主线方案
MapAtkBatPresetTypeDict = {}
MapDefBatPresetTypeDict = {Def_FBMapID_ArenaBattle:ShareDefine.BatPreset_ArenaDef}
MapDefBatPresetTypeDict = {Def_FBMapID_ArenaBattle:ShareDefine.BatPreset_ArenaDef,
                           Def_FBMapID_Qunying:ShareDefine.BatPreset_QunyingDef,
                           }
#注册上传跨服服务器数据后直接进入跨服服务器的地图
RegisterEnter_CrossServerMapIDList = []
@@ -1983,6 +1987,7 @@
                'Zhanchui':[Def_FBMapID_Zhanchui],
                'Tianzi':[Def_FBMapID_Tianzi],
                'Dingjunge':[Def_FBMapID_Dingjunge],
                'Qunying':[Def_FBMapID_Qunying],
                }
#特殊副本ID, 由系统分配, 进入时候不验证IsMapCopyFull
@@ -3632,6 +3637,12 @@
Def_PDict_ArenaScore = "ArenaScore" # 当前积分
Def_PDict_ArenaWinCnt = "ArenaWinCnt" # 累计胜利次数
#群英榜
Def_PDict_QunyingRefreshCnt = "QunyingRefreshCnt" # 本周已刷新匹配次数
Def_PDict_QunyingRecoverTime = "QunyingRecoverTime" # 上次恢复挑战令时间戳
Def_PDict_QunyingRankHighest = "QunyingRankHighest" # 历史最高名次
Def_PDict_QunyingRankSuccAward = "QunyingRankSuccAward" # 历史最高名次成就领奖记录
#古宝
Def_PDict_GubaoInfo = "Gubao_%s"  # 古宝信息,参数(古宝ID),特殊效果层*100000 + 等级*100 + 星级
@@ -4562,6 +4573,11 @@
ntMax
) = range(27)
# 机器人功能模版
(
RobotTempNum_Comm, # 通用模版 0
RobotTempNum_Qunying, # 群英榜专用 1
) = range(2)
# 回合卡牌
(
@@ -4685,7 +4701,7 @@
Def_RewardType_LineupRecommend,  # 阵容推荐奖励 4
Def_RewardType_LVAward,  # 玩家等级奖励5
Def_RewardType_BeautyLVAward, # 红颜等级奖励 6
Def_RewardType_7, # 每日任务修行点奖励7
Def_RewardType_QunyingRankHighest, # 群英榜历史最高名次奖励 7
Def_RewardType_FirstCharge, # 首充礼包奖励8
Def_RewardType_OSACelebrationPointAward, # 开服庆典积分阶段奖励 9
Def_RewardType_ActHeroAppearStarFreeAward, # 武将登场升星计划免费奖励 10
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/ChPyNetPack.py
@@ -10955,6 +10955,58 @@
#------------------------------------------------------
# B2 10 群英榜匹配玩家 #tagCSQunyingMatch
class  tagCSQunyingMatch(Structure):
    _pack_ = 1
    _fields_ = [
                  ("Cmd", c_ubyte),
                  ("SubCmd", c_ubyte),
                  ("IsRefresh", c_ubyte),    # 0-打开界面无匹配数据时查询,1-强制刷新匹配列表
                  ]
    def __init__(self):
        self.Clear()
        self.Cmd = 0xB2
        self.SubCmd = 0x10
        return
    def ReadData(self, stringData, _pos=0, _len=0):
        self.Clear()
        memmove(addressof(self), stringData[_pos:], self.GetLength())
        return _pos + self.GetLength()
    def Clear(self):
        self.Cmd = 0xB2
        self.SubCmd = 0x10
        self.IsRefresh = 0
        return
    def GetLength(self):
        return sizeof(tagCSQunyingMatch)
    def GetBuffer(self):
        return string_at(addressof(self), self.GetLength())
    def OutputString(self):
        DumpString = '''// B2 10 群英榜匹配玩家 //tagCSQunyingMatch:
                                Cmd:%s,
                                SubCmd:%s,
                                IsRefresh:%d
                                '''\
                                %(
                                self.Cmd,
                                self.SubCmd,
                                self.IsRefresh
                                )
        return DumpString
m_NAtagCSQunyingMatch=tagCSQunyingMatch()
ChNetPackDict[eval("0x%02x%02x"%(m_NAtagCSQunyingMatch.Cmd,m_NAtagCSQunyingMatch.SubCmd))] = m_NAtagCSQunyingMatch
#------------------------------------------------------
# B2 07 重置加点 #tagCMResetAttrPoint
class  tagCMResetAttrPoint(Structure):
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/ChPyNetSendPack.py
@@ -14398,6 +14398,261 @@
#------------------------------------------------------
# A9 24 群英榜匹配玩家列表 #tagSCQunyingMatchList
class  tagSCQunyingMatchInfo(Structure):
    Rank = 0    #(WORD Rank)//排名,从1开始
    PlayerID = 0    #(DWORD PlayerID)//目标玩家ID
    PlayerName = ""    #(char PlayerName[33])
    LV = 0    #(WORD LV)// 玩家等级
    RealmLV = 0    #(WORD RealmLV)//境界,机器人读境界表取等级对应境界
    FightPower = 0    #(DWORD FightPower)//战力求余亿部分
    FightPowerEx = 0    #(DWORD FightPowerEx)//战力整除亿部分
    Face = 0    #(DWORD Face)//基本脸型
    FacePic = 0    #(DWORD FacePic)//头像框
    TitleID = 0    #(DWORD TitleID)//称号
    ModelMark = 0    #(DWORD ModelMark)//变形模型mark
    EquipShowSwitch = 0    #(DWORD EquipShowSwitch)//其他外观信息
    ServerID = 0    #(DWORD ServerID)
    data = None
    def __init__(self):
        self.Clear()
        return
    def ReadData(self, _lpData, _pos=0, _Len=0):
        self.Clear()
        self.Rank,_pos = CommFunc.ReadWORD(_lpData, _pos)
        self.PlayerID,_pos = CommFunc.ReadDWORD(_lpData, _pos)
        self.PlayerName,_pos = CommFunc.ReadString(_lpData, _pos,33)
        self.LV,_pos = CommFunc.ReadWORD(_lpData, _pos)
        self.RealmLV,_pos = CommFunc.ReadWORD(_lpData, _pos)
        self.FightPower,_pos = CommFunc.ReadDWORD(_lpData, _pos)
        self.FightPowerEx,_pos = CommFunc.ReadDWORD(_lpData, _pos)
        self.Face,_pos = CommFunc.ReadDWORD(_lpData, _pos)
        self.FacePic,_pos = CommFunc.ReadDWORD(_lpData, _pos)
        self.TitleID,_pos = CommFunc.ReadDWORD(_lpData, _pos)
        self.ModelMark,_pos = CommFunc.ReadDWORD(_lpData, _pos)
        self.EquipShowSwitch,_pos = CommFunc.ReadDWORD(_lpData, _pos)
        self.ServerID,_pos = CommFunc.ReadDWORD(_lpData, _pos)
        return _pos
    def Clear(self):
        self.Rank = 0
        self.PlayerID = 0
        self.PlayerName = ""
        self.LV = 0
        self.RealmLV = 0
        self.FightPower = 0
        self.FightPowerEx = 0
        self.Face = 0
        self.FacePic = 0
        self.TitleID = 0
        self.ModelMark = 0
        self.EquipShowSwitch = 0
        self.ServerID = 0
        return
    def GetLength(self):
        length = 0
        length += 2
        length += 4
        length += 33
        length += 2
        length += 2
        length += 4
        length += 4
        length += 4
        length += 4
        length += 4
        length += 4
        length += 4
        length += 4
        return length
    def GetBuffer(self):
        data = ''
        data = CommFunc.WriteWORD(data, self.Rank)
        data = CommFunc.WriteDWORD(data, self.PlayerID)
        data = CommFunc.WriteString(data, 33, self.PlayerName)
        data = CommFunc.WriteWORD(data, self.LV)
        data = CommFunc.WriteWORD(data, self.RealmLV)
        data = CommFunc.WriteDWORD(data, self.FightPower)
        data = CommFunc.WriteDWORD(data, self.FightPowerEx)
        data = CommFunc.WriteDWORD(data, self.Face)
        data = CommFunc.WriteDWORD(data, self.FacePic)
        data = CommFunc.WriteDWORD(data, self.TitleID)
        data = CommFunc.WriteDWORD(data, self.ModelMark)
        data = CommFunc.WriteDWORD(data, self.EquipShowSwitch)
        data = CommFunc.WriteDWORD(data, self.ServerID)
        return data
    def OutputString(self):
        DumpString = '''
                                Rank:%d,
                                PlayerID:%d,
                                PlayerName:%s,
                                LV:%d,
                                RealmLV:%d,
                                FightPower:%d,
                                FightPowerEx:%d,
                                Face:%d,
                                FacePic:%d,
                                TitleID:%d,
                                ModelMark:%d,
                                EquipShowSwitch:%d,
                                ServerID:%d
                                '''\
                                %(
                                self.Rank,
                                self.PlayerID,
                                self.PlayerName,
                                self.LV,
                                self.RealmLV,
                                self.FightPower,
                                self.FightPowerEx,
                                self.Face,
                                self.FacePic,
                                self.TitleID,
                                self.ModelMark,
                                self.EquipShowSwitch,
                                self.ServerID
                                )
        return DumpString
class  tagSCQunyingMatchList(Structure):
    Head = tagHead()
    MatchCount = 0    #(BYTE MatchCount)
    MatchList = list()    #(vector<tagSCQunyingMatchInfo> MatchList)// 匹配列表,从高分到低分
    data = None
    def __init__(self):
        self.Clear()
        self.Head.Cmd = 0xA9
        self.Head.SubCmd = 0x24
        return
    def ReadData(self, _lpData, _pos=0, _Len=0):
        self.Clear()
        _pos = self.Head.ReadData(_lpData, _pos)
        self.MatchCount,_pos = CommFunc.ReadBYTE(_lpData, _pos)
        for i in range(self.MatchCount):
            temMatchList = tagSCQunyingMatchInfo()
            _pos = temMatchList.ReadData(_lpData, _pos)
            self.MatchList.append(temMatchList)
        return _pos
    def Clear(self):
        self.Head = tagHead()
        self.Head.Clear()
        self.Head.Cmd = 0xA9
        self.Head.SubCmd = 0x24
        self.MatchCount = 0
        self.MatchList = list()
        return
    def GetLength(self):
        length = 0
        length += self.Head.GetLength()
        length += 1
        for i in range(self.MatchCount):
            length += self.MatchList[i].GetLength()
        return length
    def GetBuffer(self):
        data = ''
        data = CommFunc.WriteString(data, self.Head.GetLength(), self.Head.GetBuffer())
        data = CommFunc.WriteBYTE(data, self.MatchCount)
        for i in range(self.MatchCount):
            data = CommFunc.WriteString(data, self.MatchList[i].GetLength(), self.MatchList[i].GetBuffer())
        return data
    def OutputString(self):
        DumpString = '''
                                Head:%s,
                                MatchCount:%d,
                                MatchList:%s
                                '''\
                                %(
                                self.Head.OutputString(),
                                self.MatchCount,
                                "..."
                                )
        return DumpString
m_NAtagSCQunyingMatchList=tagSCQunyingMatchList()
ChNetPackDict[eval("0x%02x%02x"%(m_NAtagSCQunyingMatchList.Head.Cmd,m_NAtagSCQunyingMatchList.Head.SubCmd))] = m_NAtagSCQunyingMatchList
#------------------------------------------------------
# A9 25 群英榜玩家信息 #tagSCQunyingPlayerInfo
class  tagSCQunyingPlayerInfo(Structure):
    _pack_ = 1
    _fields_ = [
                  ("Cmd", c_ubyte),
                  ("SubCmd", c_ubyte),
                  ("RefreshCnt", c_int),    # 本周已刷新匹配次数
                  ("LastRecoverTime", c_int),    # 上次免费恢复挑战令时间戳,为0时可不用倒计时
                  ("RankHighest", c_ushort),    # 历史最高名次,第1名为最高
                  ("RankSuccAward", c_int),    # 历史最高名次成就领奖记录,按奖励记录索引位运算记录是否已领取
                  ]
    def __init__(self):
        self.Clear()
        self.Cmd = 0xA9
        self.SubCmd = 0x25
        return
    def ReadData(self, stringData, _pos=0, _len=0):
        self.Clear()
        memmove(addressof(self), stringData[_pos:], self.GetLength())
        return _pos + self.GetLength()
    def Clear(self):
        self.Cmd = 0xA9
        self.SubCmd = 0x25
        self.RefreshCnt = 0
        self.LastRecoverTime = 0
        self.RankHighest = 0
        self.RankSuccAward = 0
        return
    def GetLength(self):
        return sizeof(tagSCQunyingPlayerInfo)
    def GetBuffer(self):
        return string_at(addressof(self), self.GetLength())
    def OutputString(self):
        DumpString = '''// A9 25 群英榜玩家信息 //tagSCQunyingPlayerInfo:
                                Cmd:%s,
                                SubCmd:%s,
                                RefreshCnt:%d,
                                LastRecoverTime:%d,
                                RankHighest:%d,
                                RankSuccAward:%d
                                '''\
                                %(
                                self.Cmd,
                                self.SubCmd,
                                self.RefreshCnt,
                                self.LastRecoverTime,
                                self.RankHighest,
                                self.RankSuccAward
                                )
        return DumpString
m_NAtagSCQunyingPlayerInfo=tagSCQunyingPlayerInfo()
ChNetPackDict[eval("0x%02x%02x"%(m_NAtagSCQunyingPlayerInfo.Cmd,m_NAtagSCQunyingPlayerInfo.SubCmd))] = m_NAtagSCQunyingPlayerInfo
#------------------------------------------------------
# A9 21 角色改名结果 #tagSCRenameResult
class  tagSCRenameResult(Structure):
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/DB/StructData/DBBillboard.py
@@ -24,6 +24,7 @@
import CommFunc
import json
import time
class BillboardData():
    ## 榜单数据
@@ -110,6 +111,8 @@
        copyData.SetTime(self.GetTime())
        return copyData
    
TempBillData = BillboardData()
class Billboard():
    ## 某个排行榜类
    
@@ -120,8 +123,13 @@
        self.__billboardList = [] # [BillboardData, ...] 
        self.__idIndexDict = {} # {id:index, ...}
        self.__idOrderDict = {} # {id:名次, ...}
        self.__orderRuleList = None # 指定排名所需值规则列表 [[order, needCmpValue], ...]
        self.__sortDelay = False # 是否需要延迟排序
        self.__orderRuleList = None # 指定排名所需值规则列表 [[order, needCmpValue], ...]
        # 【层级模式】机器人填充数据模式,一般如果机器人数据量比较大的情况下实用该模式,如群英榜
        # 层级模式下,因为机器人数量一般比较大,比如1万,为了节约内存占用及遍历排序等效率,填充的机器人数据不插入实际的 __billboardList 里
        self.__orderRuleByLayer = False # 按固定层级模式,第1层即第1名,CmpValue直接使用名次值,方便排序用,由功能自行管理好CmpValue值
        self.__layerIDList = [] # 层级ID列表,顺序即排名,索引0为第1名,ID包含填充的机器人ID,初始的数据由功能按规则设置填充的机器人ID
        return
    
    def GetType(self): return self.__billboardType
@@ -143,10 +151,15 @@
    def SortData(self):
        GameWorld.DebugLog("榜单排序: billboardType=%s,groupValue1=%s,groupValue2=%s,dataCount=%s" 
                      % (self.__billboardType, self.__groupValue1, self.__groupValue2, len(self.__billboardList)))
        if self.__orderRuleByLayer:
            self.__billboardList.sort(key=lambda b: (-b.GetCmpValue(), -b.GetTime()), reverse=True)
        else:
        self.__billboardList.sort(key=lambda b: (b.GetCmpValue(), b.GetCmpValue2(), b.GetCmpValue3(), -b.GetTime()), reverse=True)
        self.__idOrderDict = {} # 排序后重置,下次查询时更新并缓存
        self.__idIndexDict = {}
        self.__sortDelay = False
        if self.__orderRuleByLayer:
            self.__fixAndFillLayer()
        return
    
    def SetSortDelay(self):
@@ -161,7 +174,7 @@
        if not self.__sortDelay:
            return
        self.SortData()
        return
        return True
    
    def AddNewBillboardData(self, dataID):
        newData = None
@@ -273,12 +286,66 @@
                        billboardIndex += 1
                for order, billboardData in enumerate(self.__billboardList, 1):
                    self.__idIndexDict[billboardData.GetID()] = order - 1
            # 按层级固定排位的
            elif self.__orderRuleByLayer:
                if not self.SortDelayDo():
                    self.__fixAndFillLayer()
            else:
                for order, billboardData in enumerate(self.__billboardList, 1):
                    self.__idOrderDict[billboardData.GetID()] = order
                    self.__idIndexDict[billboardData.GetID()] = order - 1
        return self.__idOrderDict
    
    def IsOrderRuleByLayer(self): return self.__orderRuleByLayer
    def SetOrderRuleByLayer(self, layerRobotIDList):
        '''设置排名规则为层级模式,即一个名次一个坑位层级,该模式支持机器人填充数据
        该模式下 CmpValue 固定为  MaxCount - 名次 + 1
        @param layerRobotIDList: 填充的机器人ID排名列表
        '''
        self.__orderRuleByLayer = True
        self.__layerIDList = layerRobotIDList
        self.SortData() # 强制排序处理
        return
    def SetLayerIDList(self, layerIDList): self.__layerIDList = layerIDList
    def GetLayerIDList(self): return self.__layerIDList
    def __fixAndFillLayer(self):
        ## 检查修正并填充满最终层级ID顺序
        maxCount = self.GetMaxCount()
        layerIDCnt = len(self.__layerIDList)
        if layerIDCnt < maxCount:
            self.__layerIDList.extend([0]*(maxCount-layerIDCnt)) # 先用0填充,确保长度一致
        # 先检查修正真实榜单数据的排名值,确保唯一
        orderIDDict = {}
        for index, billboardData in enumerate(self.__billboardList):
            dataID = billboardData.GetID()
            order = billboardData.GetCmpValue()
            if order > maxCount:
                # 超过的默认不上榜,后面的数据不再处理,外层的数据使用替换的模式,故理论上不会有多余的数据,这里暂做个防范
                break
            if order <= 0:
                # 没有名次数据的也不上榜,但先跳过继续处理后面的数据
                continue
            while 1 <= order <= maxCount:
                if order not in orderIDDict:
                    orderIDDict[order] = dataID
                    self.__idOrderDict[dataID] = order
                    self.__idIndexDict[dataID] = index
                    break
                # 重复的数据修正层级比较值,确保唯一
                order += 1
                billboardData.SetCmpValue(order)
        for index, layerID in enumerate(self.__layerIDList):
            order = index + 1
            dataID = layerID
            if order in orderIDDict:
                dataID = orderIDDict[order]
            self.__layerIDList[index] = dataID
        return
    def SetOrderRuleList(self, orderRuleList):
        ## 排名所需值规则列表
        # @param orderRuleList: 排名所需值规则列表 [[order, needCmpValue], ...]
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/GM/Commands/Billboard.py
@@ -29,7 +29,7 @@
    GameWorld.DebugAnswer(curPlayer, "输出排行榜数据: Billboard p 类型 [分组值1 值2 从x名 到x名]")
    GameWorld.DebugAnswer(curPlayer, "注:如果没有特殊说明,本服榜单分组值均为0,跨服榜单分组值1为分区ID,分组2为0")
    
    for billboardType in ShareDefine.BillboardTypeList:
    for billboardType in ShareDefine.BillboardTypeAllList:
        bName = ShareDefine.BillboardNameDict.get(billboardType, billboardType)
        GameWorld.DebugAnswer(curPlayer, "(%s) %s" % (billboardType, bName))
        
@@ -89,7 +89,9 @@
    billboardObj = billboardMgr.GetBillboard(billboardType, groupValue1, groupValue2)
    curDataCount = billboardObj.GetCount()
    maxDataCount = billboardObj.GetMaxCount()
    isLayerMode = billboardObj.IsOrderRuleByLayer()
    if isLayerMode:
        cmpValue1 = min(cmpValue1, maxDataCount)
    count = min(count, maxDataCount - curDataCount)
    FakeName = GameWorld.GbkToCode("主公")
    
@@ -103,6 +105,9 @@
        name1 = dataPlayerName
        cmpValue = dataCmpValue1
        cmpValue2 = dataCmpValue2
        if isLayerMode:
            PlayerBillboard.UpdateBillboardLayer(dataID, billboardType, cmpValue, groupValue1, False)
        else:
        PlayerBillboard.UpdateBillboard(billboardType, groupValue1, dataID, name1, name2, type2, value1, value2, 
                                        cmpValue, cmpValue2, cmpValue3, groupValue2, id2, False, 
                                        value3=value3, value4=value4, value5=value5)
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/GM/Commands/Qunying.py
New file
@@ -0,0 +1,82 @@
#!/usr/bin/python
# -*- coding: GBK -*-
#-------------------------------------------------------------------------------
#
##@package GM.Commands.Qunying
#
# @todo:群英榜
# @author hxp
# @date 2026-03-11
# @version 1.0
#
# 详细描述: 群英榜
#
#-------------------------------------------------------------------------------
#"""Version = 2026-03-11 19:00"""
#-------------------------------------------------------------------------------
import GameWorld
import ShareDefine
import GameLogic_Qunying
import PlayerBillboard
import PlayerControl
import DBDataMgr
import ChConfig
def OnExec(curPlayer, msgList):
    if not msgList:
        GameWorld.DebugAnswer(curPlayer, "重置群英数据: Qunying 0")
        GameWorld.DebugAnswer(curPlayer, "设置所在名次: Qunying 名次")
        GameWorld.DebugAnswer(curPlayer, "设置历史最高: Qunying h 名次 [重置奖励]")
        GameWorld.DebugAnswer(curPlayer, "设置刷新次数: Qunying r 次数")
        GameWorld.DebugAnswer(curPlayer, "输出名次数据: Qunying p [从x名开始 数量 ]")
        return
    playerID = curPlayer.GetPlayerID()
    value1 = msgList[0]
    if value1 == "p":
        billboardMgr = DBDataMgr.GetBillboardMgr()
        billBoard = billboardMgr.GetBillboard(ShareDefine.Def_BT_Qunying)
        if not billBoard:
            return
        billBoard.SortDelayDo()
        layerIDList = billBoard.GetLayerIDList()
        fromRank = max(1, msgList[1] if len(msgList) > 1 else 1)
        dataCnt = min(msgList[2] if len(msgList) > 2 else 10, 20)
        toRank = min(len(layerIDList), fromRank + dataCnt)
        GameWorld.DebugAnswer(curPlayer, "榜单总数据:%s/%s,idCnt=%s" % (billBoard.GetCount(), billBoard.GetMaxCount(), len(layerIDList)))
        for rank in range(fromRank, toRank + 1):
            dataID = layerIDList[rank - 1]
            GameWorld.DebugAnswer(curPlayer, "名次%s, ID:%s" % (rank, dataID))
        return
    if value1 <= 0:
        PlayerControl.NomalDictSetProperty(curPlayer, ChConfig.Def_PDict_QunyingRefreshCnt, 0)
        PlayerControl.NomalDictSetProperty(curPlayer, ChConfig.Def_PDict_QunyingRankHighest, 0)
        PlayerControl.NomalDictSetProperty(curPlayer, ChConfig.Def_PDict_QunyingRankSuccAward, 0)
        GameWorld.DebugAnswer(curPlayer, "重置成功!")
    elif value1 == "h":
        rankHighest = msgList[1] if len(msgList) > 1 else 0
        resetAward = msgList[2] if len(msgList) > 2 else 0
        PlayerControl.NomalDictSetProperty(curPlayer, ChConfig.Def_PDict_QunyingRankHighest, rankHighest)
        if resetAward:
            PlayerControl.NomalDictSetProperty(curPlayer, ChConfig.Def_PDict_QunyingRankSuccAward, 0)
        GameWorld.DebugAnswer(curPlayer, "设置历史最高: %s" % rankHighest)
    elif value1 == "r":
        refreshCnt = msgList[1] if len(msgList) > 1 else 0
        PlayerControl.NomalDictSetProperty(curPlayer, ChConfig.Def_PDict_QunyingRefreshCnt, refreshCnt)
        GameWorld.DebugAnswer(curPlayer, "设置刷新次数: %s" % refreshCnt)
    elif value1 > 0:
        cmpValue = value1
        if PlayerBillboard.UpdateBillboardLayer(playerID, ShareDefine.Def_BT_Qunying, cmpValue):
            GameWorld.DebugAnswer(curPlayer, "设置所在名次成功: %s" % cmpValue)
        else:
            GameWorld.DebugAnswer(curPlayer, "设置所在名次只能为1~最大名次")
        return
    GameLogic_Qunying.Sync_QunyingInfo(curPlayer)
    return
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/GameWorldLogic/FBLogic.py
@@ -993,37 +993,32 @@
        PlayerControl.PlayerLeaveFB(curPlayer)
    return
## 副本每周逻辑
#  @param tick 当前时间
#  @return None or False
#  @remarks 函数详细说明.
def OnWeek(tick):
    do_FBLogic_ID = __GetFBLogic_MapID(GameWorld.GetMap().GetMapID())
    callFunc = GameWorld.GetExecFunc(FBProcess, "GameLogic_%s.%s" % (do_FBLogic_ID, "OnWeek"))
    if callFunc == None:
        return False
    #执行副本逻辑
    callFunc(tick)
def OnWeek():
    for key, mapIDList in ChConfig.Def_FB_MapID.items():
        if not mapIDList:
            continue
        callFunc = GameWorld.GetExecFunc(FBProcess, "GameLogic_%s.%s" % (key, "OnWeek"))
        if callFunc:
            callFunc()
    return
## 副本每日逻辑
#  @param tick 当前时间
#  @return None or False
#  @remarks 函数详细说明.
def OnDay(tick):
    do_FBLogic_ID = __GetFBLogic_MapID(GameWorld.GetMap().GetMapID())
def OnDay():
    for key, mapIDList in ChConfig.Def_FB_MapID.items():
        if not mapIDList:
            continue
        callFunc = GameWorld.GetExecFunc(FBProcess, "GameLogic_%s.%s" % (key, "OnDay"))
        if callFunc:
            callFunc()
    return
    
    callFunc = GameWorld.GetExecFunc(FBProcess, "GameLogic_%s.%s" % (do_FBLogic_ID, "OnDay"))
    if callFunc == None:
        #GameWorld.Log("副本逻辑不可使用   GameLogic_%d"%(mapID))
        return False
    #执行副本逻辑
    callFunc(tick)
## 副本每周逻辑-玩家, 不管玩家是否在该副本地图中都会触发
def OnFBPlayerOnWeek(curPlayer, onWeekType):
    for key, mapIDList in ChConfig.Def_FB_MapID.items():
        if not mapIDList:
            continue
        callFunc = GameWorld.GetExecFunc(FBProcess, "GameLogic_%s.%s" % (key, "OnFBPlayerOnWeek"))
        if callFunc:
            callFunc(curPlayer)
    return
## 副本每日逻辑-玩家, 不管玩家是否在该副本地图中都会触发
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/GameWorldLogic/FBProcess/FBCommon.py
@@ -1559,6 +1559,9 @@
    return True
def FBOnWeek(curPlayer, onWeekType):
    if onWeekType != ShareDefine.Def_OnEventType:
        return
    FBLogic.OnFBPlayerOnWeek(curPlayer, onWeekType)
    return
def FBOnDay(curPlayer, onDayType):
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/GameWorldLogic/FBProcess/GameLogic_Qunying.py
New file
@@ -0,0 +1,599 @@
#!/usr/bin/python
# -*- coding: GBK -*-
#-------------------------------------------------------------------------------
#
##@package GameWorldLogic.FBProcess.GameLogic_Qunying
#
# @todo:群英榜
# @author hxp
# @date 2026-03-11
# @version 1.0
#
# 详细描述: 群英榜
#
#-------------------------------------------------------------------------------
#"""Version = 2026-03-11 19:00"""
#-------------------------------------------------------------------------------
import DBDataMgr
import GameWorld
import TurnAttack
import ShareDefine
import PlayerControl
import IpyGameDataPY
import PlayerBillboard
import PlayerViewCache
import ChPyNetSendPack
import NetPackCommon
import ItemControler
import GameFuncComm
import PyGameData
import FBCommon
import ChConfig
import random
import time
# 记录攻击类型
RecAtkType_Atk = 1 # 发起攻击
RecAtkType_Def = 2 # 被攻击的
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():
    serverDay = DBDataMgr.GetEventTrigMgr().GetValue(ShareDefine.Def_ServerDay) + 1
    serverDayNeed = IpyGameDataPY.GetFuncCfg("QunyingSet", 1)
    if serverDayNeed and serverDay < serverDayNeed:
        GameWorld.DebugLog("开服天不足本周不重置群英榜! serverDay=%s < %s" % (serverDay, serverDayNeed))
        return
    DoQunyingReset()
    return
def OnDay():
    billboardAwardDict = IpyGameDataPY.GetFuncEvalCfg("QunyingAward", 1, {})
    PlayerBillboard.DoGiveBillboardAwardByMail(ShareDefine.Def_BT_Qunying, "QunyingDay", billboardAwardDict, "QunyingDay", isClearData=False)
    return
def OnServerStart():
    __fillRobot()
    return
def DoQunyingReset():
    ''' 赛季重置
    '''
    GameWorld.Log("=============== 重置群英榜 ===============")
    PyGameData.g_qunyingPlayerMatchDict = {}
    # 结算上赛季排行奖励
    billboardAwardDict = IpyGameDataPY.GetFuncEvalCfg("QunyingAward", 2, {})
    PlayerBillboard.DoGiveBillboardAwardByMail(ShareDefine.Def_BT_Qunying, "QunyingWeek", billboardAwardDict, "QunyingWeek", isClearData=False)
    # 重置排行榜
    DBDataMgr.GetBillboardMgr().RemoveBillboard(ShareDefine.Def_BT_Qunying)
    __fillRobot()
    GameWorld.Log("==========================================")
    return True
def __fillRobot():
    ## 填充满机器人
    billboardMgr = DBDataMgr.GetBillboardMgr()
    billBoard = billboardMgr.GetBillboard(ShareDefine.Def_BT_Qunying)
    maxCount = billBoard.GetMaxCount() # 机器人填充数默认使用榜单最大数
    robotFPSortList = PlayerViewCache.GetRobotFightPowerSortList(ChConfig.RobotTempNum_Qunying)
    robotDict = {} # {(rankA, rankB):[robotID], ...}
    for _, robotID in robotFPSortList:
        robotIpyData = IpyGameDataPY.GetIpyGameData("Robot", robotID)
        if not robotIpyData:
            continue
        tempValue1 = robotIpyData.GetTempValue1()
        tempValue2 = robotIpyData.GetTempValue2()
        rankKey = (tempValue1, tempValue2)
        if rankKey not in robotDict:
            robotDict[rankKey] = []
        robotList = robotDict[rankKey]
        robotList.append(robotIpyData.GetID())
    layerRobotIDList = []
    robotKeyList = robotDict.keys()
    robotKeyList.sort() # 按名次排序处理
    for robotKey in robotKeyList:
        rankA, rankB = robotKey
        robotIDList = robotDict[robotKey]
        random.shuffle(robotIDList)
        robotCnt = len(robotIDList)
        needRobotCnt = rankB - rankA + 1
        for _ in range(needRobotCnt / robotCnt):
            layerRobotIDList += robotIDList
        for index in range(needRobotCnt % robotCnt):
            layerRobotIDList.append(robotIDList[index])
    layerRobotIDList = layerRobotIDList[:maxCount]
    GameWorld.DebugLog("填充群英榜机器人: maxCount=%s,fillRobotIDLen=%s" % (maxCount, len(layerRobotIDList)))
    billBoard.SetOrderRuleByLayer(layerRobotIDList)
    return
## ------------------------------------------------------------------------------------------------
def DoQunyingOpen(curPlayer):
    storeMax = IpyGameDataPY.GetFuncCfg("QunyingChallenge", 1)
    PlayerControl.GiveMoney(curPlayer, ShareDefine.TYPE_Price_QunyingTicket, storeMax, "QunyingOpen")
    Sync_QunyingInfo(curPlayer) # 开启功能
    return
def OnFBPlayerOnWeek(curPlayer):
    if not GameFuncComm.GetFuncCanUse(curPlayer, ShareDefine.GameFuncID_Qunying):
        return
    serverDay = DBDataMgr.GetEventTrigMgr().GetValue(ShareDefine.Def_ServerDay) + 1
    serverDayNeed = IpyGameDataPY.GetFuncCfg("QunyingSet", 1)
    # 玩家重置有个问题,玩家的过周并不一定是周一触发,具体看玩家什么时候登录
    # 那么可能存在周二玩家才登录,然后满足了重置条件,导致触发了玩家个人的赛季重置
    # 目前只有刷新次数需要重置,暂时不考虑这个问题,这种情况一般只有新服才可能出现
    # 【注】如之后有需要重置比较严谨的数据,则需要按赛季ID对比不一样才能重置
    if serverDayNeed and serverDay < serverDayNeed:
        GameWorld.DebugLog("开服天不足本周不重置玩家群英榜! serverDay=%s < %s" % (serverDay, serverDayNeed), curPlayer.GetPlayerID())
        return
    PlayerControl.NomalDictSetProperty(curPlayer, ChConfig.Def_PDict_QunyingRefreshCnt, 0)
    Sync_QunyingInfo(curPlayer) # 赛季重置
    return
def OnFBPlayerOnLogin(curPlayer):
    if not GameFuncComm.GetFuncCanUse(curPlayer, ShareDefine.GameFuncID_Qunying):
        return
    Sync_QunyingInfo(curPlayer) # 登录
    return
def __checkAtkQunyingPlayer(curPlayer, tagID, tagRank):
    '''检查可否攻击目标
    '''
    playerID = curPlayer.GetPlayerID()
    if not tagID or playerID == tagID:
        return
    matchRankList = PyGameData.g_qunyingPlayerMatchDict.get(playerID, [])
    if tagRank not in matchRankList:
        GameWorld.DebugLog("群英榜不可攻击不在匹配列表里的目标名次! tagID=%s not in matchRankList=%s" % (tagID, matchRankList), playerID)
        return
    # 验证名次是否还与前端的一致
    billboardMgr = DBDataMgr.GetBillboardMgr()
    billBoard = billboardMgr.GetBillboard(ShareDefine.Def_BT_Qunying)
    if not billBoard:
        return
    layerIDList = billBoard.GetLayerIDList()
    tagIndex = tagRank - 1
    tagIDNow = 0
    if 0 <= tagIndex < len(layerIDList):
        tagIDNow = layerIDList[tagIndex]
    if tagID != tagIDNow:
        GameWorld.DebugLog("群英榜攻击时目标名次玩家ID不一致! tagRank=%s,tagID=%s != tagIDNow=%s" % (tagRank, tagID, tagIDNow), playerID)
        PlayerControl.NotifyCode(curPlayer, "QunyingTagRankIDErr") # 目标玩家名次已改变
        DoQunyingMatchRefresh(curPlayer, True, isSys=True)
        return
    return True
def OnTurnFightRequest(curPlayer, mapID, funcLineID, tagType, tagID, valueList):
    ## 回合战斗请求
    if not valueList:
        return
    tagRank = valueList[0] # 目标名次
    quickCnt = valueList[1] if len(valueList) > 1 else 0 # 速战次数
    # 速战
    if quickCnt > 0:
        __doQuickFight(curPlayer, mapID, tagID, quickCnt)
        return
    if not __checkAtkQunyingPlayer(curPlayer, tagID, tagRank):
        return
    if not PlayerControl.HaveMoney(curPlayer, ShareDefine.TYPE_Price_QunyingTicket, 1):
        return
    return True, funcLineID
def __doQuickFight(curPlayer, mapID, tagID, quickCnt):
    '''执行速战,必胜、只给固定战斗奖励,名次不变
    '''
    GameWorld.DebugLog("群英榜速战: tagID=%s,quickCnt=%s" % (tagID, quickCnt))
    if quickCnt <= 0:
        return
    if not PlayerControl.PayMoney(curPlayer, ShareDefine.TYPE_Price_QunyingTicket, quickCnt, "Qunying"):
        return
    awardItemDict = {}
    for item in IpyGameDataPY.GetFuncEvalCfg("QunyingChallenge", 3):
        itemID, itemCount = item[:2]
        awardItemDict[itemID] = awardItemDict.get(itemID, 0) + itemCount * quickCnt
    awardItemList = [[itemID, itemCount] for itemID, itemCount in awardItemDict.items()]
    ItemControler.GivePlayerItemOrMail(curPlayer, awardItemList, event=["Qunying", False, {}], isNotifyAward=False)
    lineID = 0
    isPass = 1
    jsonItemList = []
    for itemInfo in awardItemList:
        itemDict = {"ItemID":itemInfo[0], "Count":itemInfo[1]}
        jsonItemList.append(itemDict)
    overDict = {FBCommon.Over_itemInfo:jsonItemList, FBCommon.Over_isSweep:1, "tagID":tagID, "quickCnt":quickCnt}
    FBCommon.NotifyFBOver(curPlayer, mapID, lineID, isPass, overDict)
    return
def OnTurnFightAward(curPlayer, guid, mapID, funcLineID, winFaction, statMsg, dateStr, reqData, awardDict):
    ## 回合战斗结算奖励
    if not curPlayer:
        return
    playerID = curPlayer.GetPlayerID()
    tagPlayerID = reqData[1]
    valueList = reqData[2]
    tagRank = valueList[0] # 目标名次
    isWin = winFaction == ChConfig.Def_FactionA
    GameWorld.DebugLog("结算群英榜战斗! isWin=%s,tagPlayerID=%s,tagRank=%s" % (isWin, tagPlayerID, tagRank), playerID)
    canAtkRet = __checkAtkQunyingPlayer(curPlayer, tagPlayerID, tagRank)
    if not canAtkRet:
        return
    billboardMgr = DBDataMgr.GetBillboardMgr()
    billBoard = billboardMgr.GetBillboard(ShareDefine.Def_BT_Qunying)
    idRankDict = billBoard.GetIDOrderDict()
    curRank = idRankDict.get(playerID, 0)
    updRank = 0
    awardItemList = []
    if isWin:
        # 胜利时固定奖励
        awardItemList = IpyGameDataPY.GetFuncEvalCfg("QunyingChallenge", 3)
        cmpValue = tagRank
        if PlayerBillboard.UpdateBillboardLayer(playerID, ShareDefine.Def_BT_Qunying, cmpValue):
            updRank = tagRank
        else:
            GameWorld.DebugLog("    更新榜单失败", playerID)
    awardDict.update({FBCommon.Over_itemInfo:FBCommon.GetJsonItemList(awardItemList), "tagPlayerID":tagPlayerID, "updRank":updRank, "curRank":curRank})
    GameWorld.DebugLog("    tagPlayerID=%s,isWin=%s,awardItemList=%s" % (tagPlayerID, isWin, awardItemList), playerID)
    if not PlayerControl.PayMoney(curPlayer, ShareDefine.TYPE_Price_QunyingTicket, 1):
        return
    if isWin:
        ItemControler.GivePlayerItemOrMail(curPlayer, awardItemList, event=["Qunying", False, {}], isNotifyAward=False)
        rankHighest = curPlayer.NomalDictGetProperty(ChConfig.Def_PDict_QunyingRankHighest)
        if not rankHighest or (1 <= tagRank < rankHighest):
            PlayerControl.NomalDictSetProperty(curPlayer, ChConfig.Def_PDict_QunyingRankHighest, tagRank)
            GameWorld.DebugLog("    更新历史最高排名! tagRank=%s" % tagRank, playerID)
            Sync_QunyingInfo(curPlayer) # 更新最高排名
    __updQunyingBatRecord(curPlayer, tagPlayerID, isWin, curRank, tagRank)
    # 战斗结束系统强制刷新匹配
    DoQunyingMatchRefresh(curPlayer, True, isSys=True)
    return
def __updQunyingBatRecord(curPlayer, tagPlayerID, isWin, curRank, tagRank):
    ## 更新战斗相关日志、榜单等
    playerID = curPlayer.GetPlayerID()
    maxCount = 20
    tagViewCache = PlayerViewCache.FindViewCache(tagPlayerID)
    gameRecMgr = DBDataMgr.GetGameRecMgr()
    atkRecMgr = gameRecMgr.GetRecTypeIDMgr(ShareDefine.Def_GameRecType_QunyingRecord, playerID)
    recData = atkRecMgr.AddRecData(maxCount)
    SetRecAtkType(recData, RecAtkType_Atk)
    SetRecTagPlayerID(recData, tagPlayerID)
    SetRecIsWin(recData, isWin)
    SetRecFace(recData, tagViewCache.GetFace() if tagViewCache else 0)
    SetRecFacePic(recData, tagViewCache.GetFacePic() if tagViewCache else 0)
    SetRecRealmLV(recData, tagViewCache.GetRealmLV() if tagViewCache else 1)
    SetRecLV(recData, tagViewCache.GetLV() if tagViewCache else 1)
    # 名字、自己名次、对方名次
    tagPlayerName = tagViewCache.GetPlayerName() if tagViewCache else ""
    recData.SetUserData({"Name":tagPlayerName, "CurRank":curRank, "TagRank":tagRank})
    # 被击方
    if tagPlayerID < ShareDefine.RealPlayerIDStart:
        #GameWorld.DebugLog("目标非真实玩家不处理! tagPlayerID=%s" % tagPlayerID, playerID)
        return
    # 由于非榜单上的玩家不会被匹配到,所以被匹配到的可以理解为一定有战斗记录,因为要上榜必须战斗过
    defRecMgr = gameRecMgr.GetRecTypeIDMgr(ShareDefine.Def_GameRecType_QunyingRecord, tagPlayerID)
    if not defRecMgr.GetCount():
        GameWorld.DebugLog("目标没有对战记录不处理! tagPlayerID=%s" % tagPlayerID, playerID)
        return
    finalRecData = defRecMgr.At(defRecMgr.GetCount() - 1)
    if not GameWorld.CheckTimeIsSameWeek(finalRecData.GetTime()):
        GameWorld.DebugLog("目标本周没有对战记录不处理! tagPlayerID=%s" % tagPlayerID, playerID)
        return
    recData = defRecMgr.AddRecData(maxCount)
    SetRecAtkType(recData, RecAtkType_Def)
    SetRecTagPlayerID(recData, playerID)
    SetRecIsWin(recData, not isWin)
    SetRecFace(recData, curPlayer.GetFace())
    SetRecFacePic(recData, curPlayer.GetFacePic())
    SetRecRealmLV(recData, curPlayer.GetOfficialRank())
    SetRecLV(recData, curPlayer.GetLV())
    # 名字、自己名次、对方名次
    recData.SetUserData({"Name":curPlayer.GetPlayerName(), "CurRank":tagRank, "TagRank":curRank})
    return
def GetQunyingRankHighestAward(curPlayer):
    rankHighest = curPlayer.NomalDictGetProperty(ChConfig.Def_PDict_QunyingRankHighest)
    awardRecord = curPlayer.NomalDictGetProperty(ChConfig.Def_PDict_QunyingRankSuccAward)
    if not rankHighest:
        return
    GameWorld.DebugLog("领取群英榜历史最高排名成就奖励! rankHighest=%s,awardRecord=%s" % (rankHighest, awardRecord))
    # 首次达到x名成就奖励 {"名次":[[物品ID, 个数], ...], ...}
    rankAwardDict = IpyGameDataPY.GetFuncEvalCfg("QunyingAward", 3, {})
    # 首次达到x名成就奖励记录索引 {"名次":记录索引, ...},记录索引从0开始递增,上线后不能修改
    rankIndexDict = IpyGameDataPY.GetFuncEvalCfg("QunyingAward", 4, {})
    awardItemDict = {}
    for rankStr, itemList in rankAwardDict.items():
        rank = int(rankStr)
        if rankHighest > rank:
            GameWorld.DebugLog("    未达到: rank=%s" % rank)
            continue
        if rankStr not in rankIndexDict:
            continue
        index = rankIndexDict[rankStr]
        if awardRecord&pow(2, index):
            GameWorld.DebugLog("    已领取: rank=%s" % rank)
            continue
        awardRecord |= pow(2, index)
        for itemInfo in itemList:
            itemID, itemCount = itemInfo[:2]
            awardItemDict[itemID] = awardItemDict.get(itemID, 0) + itemCount
        GameWorld.DebugLog("    可领取: rank=%s,%s,%s" % (rank, itemList, awardItemDict))
    awardItemList = [[itemID, itemCount] for itemID, itemCount in awardItemDict.items()]
    GameWorld.DebugLog("    awardRecord=%s,awardRecord=%s" % (awardRecord, awardItemList))
    PlayerControl.NomalDictSetProperty(curPlayer, ChConfig.Def_PDict_QunyingRankSuccAward, awardRecord)
    ItemControler.GivePlayerItemOrMail(curPlayer, awardItemList, event=["QunyingRankHighest", False, {}])
    Sync_QunyingInfo(curPlayer) # 领奖
    return
def OnProcess(curPlayer):
    CheckQunyingTicketRecover(curPlayer)
    return
def CheckQunyingTicketRecover(curPlayer):
    ## 检查挑战令恢复
    storeMax = IpyGameDataPY.GetFuncCfg("QunyingChallenge", 1)
    curTicket = PlayerControl.GetMoney(curPlayer, ShareDefine.TYPE_Price_QunyingTicket)
    lastRecoverTime = curPlayer.NomalDictGetProperty(ChConfig.Def_PDict_QunyingRecoverTime)
    if curTicket >= storeMax:
        if lastRecoverTime:
            GameWorld.DebugLog("群英挑战令已满!")
            PlayerControl.NomalDictSetProperty(curPlayer, ChConfig.Def_PDict_QunyingRecoverTime, 0)
            Sync_QunyingInfo(curPlayer) # 挑战令恢复满
        return
    curTime = int(time.time())
    passSeconds = curTime - lastRecoverTime
    if not lastRecoverTime or passSeconds < 0:
        GameWorld.DebugLog("重设群英挑战令恢复时间!")
        PlayerControl.NomalDictSetProperty(curPlayer, ChConfig.Def_PDict_QunyingRecoverTime, curTime)
        Sync_QunyingInfo(curPlayer)
        return
    cdSeconds = IpyGameDataPY.GetFuncCfg("QunyingChallenge", 2) * 60
    if passSeconds < cdSeconds:
        #GameWorld.DebugLog("群英挑战令恢复CD中: passSeconds=%s < %s, lastRecoverTime=%s" % (passSeconds, cdSeconds, GameWorld.ChangeTimeNumToStr(lastRecoverTime)))
        return
    recoverCnt = passSeconds / cdSeconds
    recoverCnt = min(recoverCnt, storeMax - curTicket)
    updRecoverTime = curTime - passSeconds % cdSeconds
    PlayerControl.NomalDictSetProperty(curPlayer, ChConfig.Def_PDict_QunyingRecoverTime, updRecoverTime)
    GameWorld.DebugLog("恢复群英挑战令: %s,passSeconds=%s,上次恢复:%s" % (recoverCnt, passSeconds, GameWorld.ChangeTimeNumToStr(lastRecoverTime)))
    PlayerControl.GiveMoney(curPlayer, ShareDefine.TYPE_Price_QunyingTicket, recoverCnt)
    if PlayerControl.GetMoney(curPlayer, ShareDefine.TYPE_Price_QunyingTicket) >= storeMax:
        GameWorld.DebugLog("已满!")
        PlayerControl.NomalDictSetProperty(curPlayer, ChConfig.Def_PDict_QunyingRecoverTime, 0)
    Sync_QunyingInfo(curPlayer)
    return
#// B2 10 群英榜匹配玩家 #tagCSQunyingMatch
#
#struct    tagCSQunyingMatch
#{
#    tagHead         Head;
#    BYTE        IsRefresh;    // 0-打开界面时查询,1-强制刷新匹配列表
#};
def OnQunyingMatch(index, clientData, tick):
    curPlayer = GameWorld.GetPlayerManager().GetPlayerByIndex(index)
    isRefresh = clientData.IsRefresh
    DoQunyingMatchRefresh(curPlayer, isRefresh)
    return
def DoQunyingMatchRefresh(curPlayer, isRefresh, isSys=False):
    ## 玩家刷新匹配对手
    playerID = curPlayer.GetPlayerID()
    GameWorld.DebugLog("群英榜玩家刷新匹配列表: isRefresh=%s,isSys=%s" % (isRefresh, isSys), playerID)
    billboardMgr = DBDataMgr.GetBillboardMgr()
    billBoard = billboardMgr.GetBillboard(ShareDefine.Def_BT_Qunying)
    if not billBoard:
        return
    layerIDList = billBoard.GetLayerIDList()
    idRankDict = billBoard.GetIDOrderDict()
    layerIDCnt = len(layerIDList)
    playerRank = idRankDict.get(playerID, layerIDCnt + 1) # 未上榜默认最大名次 + 1
    # 匹配对象缓存
    if playerID not in PyGameData.g_qunyingPlayerMatchDict:
        PyGameData.g_qunyingPlayerMatchDict[playerID] = []
    matchRankList = PyGameData.g_qunyingPlayerMatchDict[playerID]
    if not isRefresh and matchRankList:
        # 非刷新的并且已经有记录的直接同步
        GameWorld.DebugLog("    非刷新且有数据,直接同步! matchRankList=%s" % matchRankList, playerID)
        __SyncQunyingMatchList(curPlayer, matchRankList, layerIDList)
        return
    # 匹配可挑战目标规则 {玩家名次:[[名次差值, 匹配x个], ...], ...} 名次差值支持正负
    matchRuleDict = IpyGameDataPY.GetFuncEvalCfg("QunyingMatch", 1, {})
    rankList = matchRuleDict.keys()
    rankList.sort()
    matchRuleList = []
    for rank in rankList:
        if playerRank <= rank:
            matchRuleList = matchRuleDict[rank]
            break
    if not matchRuleList:
        matchRuleList = matchRuleDict[rankList[-1]] # 没有默认取最后一档
    matchRuleList.sort()
    GameWorld.DebugLog("    layerIDCnt=%s,playerRank=%s,matchRuleList=%s" % (layerIDCnt, playerRank, matchRuleList), playerID)
    if not matchRuleList:
        return
    if isRefresh and not isSys:
        refreshMax = IpyGameDataPY.GetFuncCfg("QunyingMatch", 3)
        if refreshMax:
            refreshCnt = curPlayer.NomalDictGetProperty(ChConfig.Def_PDict_QunyingRefreshCnt)
            if refreshCnt >= refreshMax:
                GameWorld.DebugLog("    已达最大刷新次数! refreshCnt=%s >= %s" % (refreshCnt, refreshMax), playerID)
                return
        costMoney, moneyValue = IpyGameDataPY.GetFuncEvalCfg("QunyingMatch", 2)
        if not costMoney or not moneyValue or not PlayerControl.PayMoney(curPlayer, costMoney, moneyValue, "Qunying"):
            return
        if refreshMax:
            refreshCnt += 1
            PlayerControl.NomalDictSetProperty(curPlayer, ChConfig.Def_PDict_QunyingRefreshCnt, refreshCnt)
            Sync_QunyingInfo(curPlayer) # 刷新匹配
    matchRankList = [] # 因为有填充机器人,机器人ID可能重复,固按名次位置匹配 [rank, ...]
    if 1 <= playerRank <= layerIDCnt:
        matchRankList.append(playerRank) # 玩家有上榜时默认要包含玩家自己
    # [[名次差值, 匹配x个], ...]
    # 第一名时匹配示例: [[50, 4]]
    # 未上榜时匹配示例:[[-500, 5]]
    # 榜单中时匹配示例:[[-300, 1], [-200, 1], [-100, 1],[100, 1]]
    for mIndex, matchRule in enumerate(matchRuleList):
        rankDiff, matchCnt = matchRule
        nextDiff = None
        if mIndex < len(matchRuleList) - 1:
            nextDiff = matchRuleList[mIndex + 1][0]
        if mIndex == 0:
            if rankDiff >= 0: # 第1个就开始匹配后面的,直接从玩家后一名开始,否则直接从与玩家名次的差值开始
                fromRank = playerRank + 1
            else:
                fromRank = playerRank + rankDiff
        # 非最后一档的情况
        if nextDiff != None:
            if rankDiff < 0 and nextDiff >= 0:
                toRank = playerRank - 1 # 差值正负变更,默认以玩家名次分档
            else:
                toRank = fromRank + (nextDiff - rankDiff) - 1 # ~ 与下一档的差值
        # 最后一档
        else:
            if rankDiff < 0:
                toRank = layerIDCnt
            else:
                toRank = fromRank + rankDiff - 1
        fromRank = max(fromRank, 1)
        toRank = min(toRank, layerIDCnt)
        if fromRank > toRank:
            GameWorld.ErrLog("匹配规则异常! mIndex=%s,fromRank=%s,toRank=%s,%s" % (mIndex, fromRank, toRank, matchRuleList))
            break
        randRankList = range(fromRank, toRank + 1)
        random.shuffle(randRankList)
        rankCnt = len(randRankList)
        mCnt = 0
        # 不用验证目标ID,允许匹配重复的ID,只要匹配的目标名次不重复即可
        for randRank in randRankList:
            if randRank in matchRankList:
                continue
            tagID = layerIDList[randRank - 1]
            GameWorld.DebugLog("    匹配名次: %s ~ %s,%s名,randRank=%s,tagID=%s" % (fromRank, toRank, rankCnt, randRank, tagID), playerID)
            matchRankList.append(randRank)
            mCnt += 1
            if mCnt >= matchCnt:
                break
        if toRank == playerRank - 1:
            fromRank = toRank + 2
        else:
            fromRank = toRank + 1
        if fromRank > layerIDCnt:
            break
    matchRankList.sort()
    GameWorld.DebugLog("    匹配名次结果: matchRankList=%s" % (matchRankList), playerID)
    PyGameData.g_qunyingPlayerMatchDict[playerID] = matchRankList
    __SyncQunyingMatchList(curPlayer, matchRankList, layerIDList)
    return
def __SyncQunyingMatchList(curPlayer, matchRankList, layerIDList):
    ## 同步匹配列表
    layerIDCnt = len(layerIDList)
    clientPack = ChPyNetSendPack.tagSCQunyingMatchList()
    clientPack.MatchList = []
    for rank in matchRankList:
        if rank <= 0 or rank > layerIDCnt:
            continue
        matchID = layerIDList[rank - 1]
        viewCache = PlayerViewCache.FindBattleViewCache(matchID)
        if not viewCache:
            continue
        matchInfo = ChPyNetSendPack.tagSCQunyingMatchInfo()
        matchInfo.Rank = rank
        matchInfo.PlayerID = matchID
        if viewCache:
            fightPower = TurnAttack.GetCacheLineupFightPower(viewCache, ShareDefine.BatPreset_QunyingDef)
            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()
            matchInfo.ModelMark = viewCache.GetModelMark()
            matchInfo.EquipShowSwitch = viewCache.GetEquipShowSwitch()
            matchInfo.ServerID = viewCache.GetServerID()
        else:
            matchInfo.PlayerName = "p%s" % matchID
        clientPack.MatchList.append(matchInfo)
    clientPack.MatchCount = len(clientPack.MatchList)
    NetPackCommon.SendFakePack(curPlayer, clientPack)
    return
def Sync_QunyingInfo(curPlayer):
    clientPack = ChPyNetSendPack.tagSCQunyingPlayerInfo()
    clientPack.RefreshCnt = curPlayer.NomalDictGetProperty(ChConfig.Def_PDict_QunyingRefreshCnt)
    clientPack.LastRecoverTime = curPlayer.NomalDictGetProperty(ChConfig.Def_PDict_QunyingRecoverTime)
    clientPack.RankHighest = curPlayer.NomalDictGetProperty(ChConfig.Def_PDict_QunyingRankHighest)
    clientPack.RankSuccAward = curPlayer.NomalDictGetProperty(ChConfig.Def_PDict_QunyingRankSuccAward)
    NetPackCommon.SendFakePack(curPlayer, clientPack)
    return
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/GameWorldLogic/GameWorldEvent.py
@@ -25,8 +25,8 @@
import IPY_GameWorld
import DataRecordPack
import PlayerEventCounter
import GameLogic_Qunying
import GameWorldAction
import PlayerViewCache
import PlayerControl
import NetPackCommon
import PlayerOnline
@@ -90,7 +90,7 @@
    DBDataMgr.OnServerStart() # 优先加载公共数据
    #ItemCommon.InitPyItem() # 改为放 InitItem 加载
    LoadDBPlayer()
    PlayerViewCache.LoadRobot()
    #PlayerViewCache.LoadRobot()
    PyGameData.g_initGameTime = int(time.time()) # 放到加载数据之后
    
    # 检查跨服中心唯一性
@@ -111,7 +111,7 @@
    __DoMixServerInit()
    
    # 其他功能初始化
    GameLogic_Qunying.OnServerStart()
    
    # 最后触发检查是否完全启动成功
    PyGameData.g_initGameWorldOK = True
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/IpyGameDataPY.py
@@ -1613,6 +1613,15 @@
                        ("dict", "Attr", 0),
                        ),
                "QunyingCross":(
                        ("char", "AppID", 1),
                        ("WORD", "ZoneID", 1),
                        ("DWORD", "CrossServerID", 0),
                        ("list", "ServerIDList", 0),
                        ("BYTE", "SplitServerCnt", 0),
                        ("BYTE", "MatchServerCnt", 0),
                        ),
                "FamilyCross":(
                        ("char", "AppID", 1),
                        ("WORD", "ZoneID", 1),
@@ -1749,6 +1758,10 @@
                "Robot":(
                        ("DWORD", "ID", 1),
                        ("char", "RobotName", 0),
                        ("BYTE", "RealmLV", 0),
                        ("DWORD", "TempNum", 0),
                        ("DWORD", "TempValue1", 0),
                        ("DWORD", "TempValue2", 0),
                        ("char", "ViewCache", 0),
                        ),
                }
@@ -4087,6 +4100,20 @@
    def GetCostItem(self): return self.attrTuple[3] # 消耗材料 dict
    def GetAttr(self): return self.attrTuple[4] # 属性 dict
# 群英榜分区表
class IPY_QunyingCross():
    def __init__(self):
        self.attrTuple = None
        return
    def GetAppID(self): return self.attrTuple[0] # AppID char
    def GetZoneID(self): return self.attrTuple[1] # 分区ID WORD
    def GetCrossServerID(self): return self.attrTuple[2] # 跨服服务器ID DWORD
    def GetServerIDList(self): return self.attrTuple[3] # 互通服务器ID列表 list
    def GetSplitServerCnt(self): return self.attrTuple[4] # 按X个相邻服分割 BYTE
    def GetMatchServerCnt(self): return self.attrTuple[5] # 分割区服内X个服随机匹配一组 BYTE
# 跨服公会表
class IPY_FamilyCross():
    
@@ -4319,7 +4346,11 @@
        
    def GetID(self): return self.attrTuple[0] # 机器人ID,同玩家ID DWORD
    def GetRobotName(self): return self.attrTuple[1] # char
    def GetViewCache(self): return self.attrTuple[2] # 机器人缓存 char
    def GetRealmLV(self): return self.attrTuple[2] # 指定官职 BYTE
    def GetTempNum(self): return self.attrTuple[3] # 功能模版 DWORD
    def GetTempValue1(self): return self.attrTuple[4] # 功能值1 DWORD
    def GetTempValue2(self): return self.attrTuple[5] # 功能值2 DWORD
    def GetViewCache(self): return self.attrTuple[6] # 机器人缓存 char
def Log(msg, playerID=0, par=0):
@@ -4527,6 +4558,7 @@
        self.__LoadFileData("EquipShenEvolve", onlyCheck)
        self.__LoadFileData("EquipStarUp", onlyCheck)
        self.__LoadFileData("EquipPlusEvolve", onlyCheck)
        self.__LoadFileData("QunyingCross", onlyCheck)
        self.__LoadFileData("FamilyCross", onlyCheck)
        self.__LoadFileData("Family", onlyCheck)
        self.__LoadFileData("FamilyEmblem", onlyCheck)
@@ -5821,6 +5853,13 @@
        self.CheckLoadData("EquipPlusEvolve")
        return self.ipyEquipPlusEvolveCache[index]
    def GetQunyingCrossCount(self):
        self.CheckLoadData("QunyingCross")
        return self.ipyQunyingCrossLen
    def GetQunyingCrossByIndex(self, index):
        self.CheckLoadData("QunyingCross")
        return self.ipyQunyingCrossCache[index]
    def GetFamilyCrossCount(self):
        self.CheckLoadData("FamilyCross")
        return self.ipyFamilyCrossLen
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/ChPlayer.py
@@ -56,6 +56,7 @@
import PlayerActManyDayRecharge
import PlayerActSingleRecharge
import OpenServerActivity
import GameLogic_Qunying
import ChNetSendPack
import PlayerArena
import PyGameData
@@ -770,7 +771,7 @@
    PlayerControl.SendMailByKey("MixServer1", [playerID], addItemList, gold=gold, silver=silver, detail=detailDict)
    
    # 同步排行榜
    PlayerBillboard.UpdatePlayerBillboardOnLeaveServer(curPlayer, isAll=True)
    #PlayerBillboard.UpdatePlayerBillboardOnLeaveServer(curPlayer, isAll=True)
    return
#---------------------------------------------------------------------
        
@@ -3147,6 +3148,9 @@
    # 领取活动签到奖励 70
    elif rewardType == ChConfig.Def_RewardType_ActSignAward:
        PlayerActSign.OnGetActSignAward(curPlayer, dataEx, dataExStr)
    # 群英榜历史最高名次奖励 7
    elif rewardType == ChConfig.Def_RewardType_QunyingRankHighest:
        GameLogic_Qunying.GetQunyingRankHighestAward(curPlayer)
        
        
    # 首充礼包奖励
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/GameFuncComm.py
@@ -30,6 +30,7 @@
import FunctionNPCCommon
import PlayerActBuyCountGift
import OpenServerActivity
import GameLogic_Qunying
import PlayerActTask
import ItemControler
import PlayerMingge
@@ -43,6 +44,7 @@
# 功能开启需执行的函数{功能ID:执行函数, ...} 函数需返回是否激活成功, 功能开启有需要处理功能逻辑的这里增加函数调用配置即可
FuncOpenLogicDict = {
                     ShareDefine.GameFuncID_Arena:lambda curObj:PlayerArena.DoArenaOpen(curObj),
                     ShareDefine.GameFuncID_Qunying:lambda curObj:GameLogic_Qunying.DoQunyingOpen(curObj),
                     ShareDefine.GameFuncID_Shop:lambda curObj:FunctionNPCCommon.DoShopOpen(curObj),
                     ShareDefine.GameFuncID_Horse:lambda curObj:PlayerHorse.DoHorseOpen(curObj),
                     ShareDefine.GameFuncID_Travel:lambda curObj:PlayerTravel.DoTravelOpen(curObj),
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/PlayerBillboard.py
@@ -19,8 +19,10 @@
import ShareDefine
import PlayerControl
import ChPyNetSendPack
import PlayerViewCache
import NetPackCommon
import IpyGameDataPY
import DBBillboard
import PlayerMail
import DBDataMgr
@@ -54,7 +56,7 @@
def BillboardOnLogin(curPlayer):
    # 上线默认同步排行榜
    UpdatePlayerBillboardOnLeaveServer(curPlayer) #排行榜已实时更新,故上线不再同步
    #UpdatePlayerBillboardOnLeaveServer(curPlayer) #排行榜已实时更新,故上线不再同步
    return
def GetBillboardOperateInfo(curPlayer):
@@ -63,14 +65,6 @@
    return platform
def GetBillboardJob(curPlayer): return curPlayer.GetJob()
def UpdatePlayerBillboardOnLeaveServer(curPlayer, isAll=False):
    ##下线更新玩家排行榜
    if GameWorld.IsCrossServer():
        # 跨服服务器不用更新本服榜
        return
    return
def UpdatePlayerBillboardName(curPlayer):
    ## 更新排行榜中的玩家名字记录
@@ -92,20 +86,6 @@
    # 跨服榜更新, 待处理
    return
def UpdatePlayerFPTotalBillboard(curPlayer, isForceUpdate=False, isCheckRule=True):
    ##更新玩家总战斗力
    return
#def __CanPlayerBillboardComm(curPlayer):
#    ## 玩家可否上榜通用检查
#    if not GameWorld.IsNormalPlayer(curPlayer):
#        return False
#    #if not GameFuncComm.GetFuncCanUse(curPlayer, ShareDefine.GameFuncID_Billboard):
#    #    GameWorld.DebugLog("排行榜未开启,无法上榜!curLV=%s" % (curPlayer.GetLV()), curPlayer.GetPlayerID())
#    #    return False
#
#    return True
def UpdatePlayerBillboard(curPlayer, bType, cmpValue, cmpValue2=0, cmpValue3=0, autoSort=False, groupValue1=0, **kwargs):
    ## 更新玩家排行榜
    
@@ -126,6 +106,102 @@
    UpdateBillboard(bType, groupValue1, playerID, playerName, playerOpInfo, playerJob, value1, value2, 
                    cmpValue, cmpValue2, cmpValue3, autoSort=autoSort, **kwargs)
    return
def __updPlayerBillViewInfo(billData):
    playerID = billData.GetID()
    curCache = PlayerViewCache.FindViewCache(playerID)
    if not curCache:
        return
    billData.SetName1(curCache.GetPlayerName())
    billData.SetName2(curCache.GetAccID())
    billData.SetType2(curCache.GetJob())
    billData.SetValue1(curCache.GetRealmLV())
    billData.SetValue2(curCache.GetTitleID())
    billData.SetValue3(curCache.GetFace())
    billData.SetValue4(curCache.GetFacePic())
    billData.SetValue5(curCache.GetModelMark())
    billData.SetValue6(curCache.GetEquipShowSwitch())
    return
def UpdateBillboardLayer(dataID, billboardType, cmpValue, groupValue1=0, autoSort=True):
    ## 更新榜单所在的层级,一般用于层级模式的榜单,层级模式默认与目标交换名次
    if GameWorld.IsCrossServer():
        if billboardType not in ShareDefine.CrossBillboardTypeList:
            return
    else:
        if billboardType not in ShareDefine.BillboardTypeList:
            return
    if not dataID:
        return
    groupValue2 = 0
    billboardMgr = DBDataMgr.GetBillboardMgr()
    billboardObj = billboardMgr.GetBillboard(billboardType, groupValue1, groupValue2)
    layerIDList = billboardObj.GetLayerIDList()
    layerIDCnt = len(layerIDList)
    if cmpValue <= 0 or cmpValue > layerIDCnt:
        GameWorld.ErrLog("更新层级榜单名次异常! dataID=%s,billboardType=%s,cmpValue=%s,layerIDCnt=%s" % (dataID, billboardType, cmpValue, layerIDCnt), dataID)
        return
    tagIndex = cmpValue - 1
    tagID = layerIDList[tagIndex]
    if dataID == tagID:
        return True
    tagBillData = billboardObj.FindByID(tagID) # 当是填充的机器人时不存在数据
    curBillData = billboardObj.FindByID(dataID)
    curTime = int(time.time())
    # 未上榜的替换上榜的
    if not curBillData:
        if tagBillData:
            curBillData = tagBillData
            curBillData.Clear()
            tagBillData = None
        else:
            curBillData = billboardObj.AddNewBillboardData(dataID)
    # 上榜的直接交换
    else:
        curCmpValue = curBillData.GetCmpValue()
        if curCmpValue <= cmpValue:
            GameWorld.DebugLog("层级榜单名次值没有提高不更新榜单! curCmpValue=%s <= %s" % (curCmpValue, cmpValue), dataID)
            return
        layerIDList[curCmpValue - 1] = tagID
        if tagBillData:
            tagBillData.SetCmpValue(curCmpValue)
            tagBillData.SetTime(curTime)
    layerIDList[cmpValue - 1] = dataID
    billboardObj.SetLayerIDList(layerIDList)
    curBillData.SetID(dataID)
    curBillData.SetCmpValue(cmpValue)
    curBillData.SetTime(curTime)
    __updPlayerBillViewInfo(curBillData)
    GameWorld.DebugLog("更新排行层值: billboardType=%s,groupValue1=%s,dataID=%s,cmpValue=%s,tagID=%s"
                       % (billboardType, groupValue1, dataID, cmpValue, tagID), dataID)
    if autoSort:
        billboardObj.SortData()
    return True
def SetTempDataByViewCache(playerID, billType, groupValue1=0, groupValue2=0, cmpValue=0):
    ## 根据玩家缓存更新临时榜单数据,一般用于填充机器人
    TempBillData = DBBillboard.TempBillData
    TempBillData.Clear()
    TempBillData.SetType(billType)
    TempBillData.SetGroupValue1(groupValue1)
    TempBillData.SetGroupValue2(groupValue2)
    if not playerID:
        return TempBillData
    TempBillData.SetID(playerID)
    TempBillData.SetCmpValue(cmpValue)
    __updPlayerBillViewInfo(TempBillData)
    return TempBillData
def UpdateBillboardByID(dataID, billboardType, cmpValue, cmpValue2=0, cmpValue3=0, autoSort=False):
    ## 直接根据榜单ID修改榜单排行相关值,其他值不修改
@@ -197,6 +273,9 @@
    
    billboardMgr = DBDataMgr.GetBillboardMgr()
    billboardObj = billboardMgr.GetBillboard(billboardType, groupValue1, groupValue2)
    if billboardObj.IsOrderRuleByLayer():
        # 该模式不处理,请使用 UpdateBillboardLayer
        return
    billboardData = billboardObj.FindByID(dataID)
    isNewData = False
    if not billboardData:
@@ -269,11 +348,6 @@
                return
    return lastBillBoardData
def UpdatePlayerCrossBillboard(curPlayer, bType, groupValue1, cmpValue, cmpValue2=0, cmpValue3=0, value1=0, value2=0,
                               groupValue2=0, **kwargs):
    ## 在本服直接更新玩家跨服排行榜,发送到跨服,之后扩展
    return
#// A1 30 查看榜单 #tagCMViewBillboard
#
#struct    tagCMViewBillboard
@@ -309,6 +383,11 @@
    billboardObj.SortDelayDo()
    idOrderDict = billboardObj.GetIDOrderDict()
    count = billboardObj.GetCount()
    isLayerMode = billboardObj.IsOrderRuleByLayer() # 层级模式有机器人填充,默认为最大,实际榜单数据中没有机器人数据
    layerIDList = []
    if isLayerMode:
        layerIDList = billboardObj.GetLayerIDList()
        count = len(layerIDList)
    
    maxIndex = count - 1
    startIndex = max(min(startIndex, maxIndex), 0)
@@ -320,6 +399,12 @@
    # 查看viewID前后名次
    if viewID:
        viewBFCnt = 3 # 查看ViewID返回前后数据条数,一般设置为奇数
        viewIDIndex = -1
        if isLayerMode:
            billboardData = billboardObj.FindByID(viewID)
            if billboardData:
                viewIDIndex = billboardData.GetCmpValue() - 1
        else:
        viewIDIndex = billboardObj.IndexOfByID(viewID)
        if viewIDIndex != -1:
            # 前x后x
@@ -334,10 +419,24 @@
    clientPack.DataTotal = count
    clientPack.PageDataList = []
    for index in viewRange:
        if index >= count:
            break
        billboardData = None
        if isLayerMode:
            rank = index + 1
            dataID = layerIDList[index]
            billboardData = billboardObj.FindByID(dataID)
            if not billboardData:
                cmpValue = rank
                billboardData = SetTempDataByViewCache(dataID, bbType, groupValue1, groupValue2, cmpValue)
        else:
        billboardData = billboardObj.At(index)
            rank = idOrderDict.get(billboardData.GetID(), 0)
        if not billboardData:
            continue
        viewData = ChPyNetSendPack.tagMCViewBillboardData()
        viewData.Index = index
        viewData.Rank = idOrderDict.get(billboardData.GetID(), 0)
        viewData.Rank = rank
        viewData.ID = billboardData.GetID()
        viewData.ID2 = billboardData.GetID2()
        viewData.Name1 = billboardData.GetName1()
@@ -362,10 +461,22 @@
    clientPack.ViewID = viewID
    clientPack.ViewIDDataList = []
    for index in viewIDRange:
        billboardData = None
        if isLayerMode:
            rank = index + 1
            dataID = layerIDList[index]
            billboardData = billboardObj.FindByID(dataID)
            if not billboardData:
                cmpValue = rank
                billboardData = SetTempDataByViewCache(dataID, bbType, groupValue1, groupValue2, cmpValue)
        else:
        billboardData = billboardObj.At(index)
            rank = idOrderDict.get(billboardData.GetID(), 0)
        if not billboardData:
            continue
        viewData = ChPyNetSendPack.tagMCViewBillboardData()
        viewData.Index = index
        viewData.Rank = idOrderDict.get(billboardData.GetID(), 0)
        viewData.Rank = rank
        viewData.ID = billboardData.GetID()
        viewData.ID2 = billboardData.GetID2()
        viewData.Name1 = billboardData.GetName1()
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/PlayerControl.py
@@ -1113,7 +1113,7 @@
    playerControl.RefreshAllState()
            
    #玩家下线更新排行榜
    PlayerBillboard.UpdatePlayerBillboardOnLeaveServer(curPlayer) #排行榜已实时更新,故下线不再同步
    #PlayerBillboard.UpdatePlayerBillboardOnLeaveServer(curPlayer) #排行榜已实时更新,故下线不再同步
    
    #玩家下线通知gameserver记录缓存(放在下线更新排行榜之后,方便Gameserver判断是否需要存入数据库中)
    PlayerViewCache.OnPlayerLogout(curPlayer)
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/PlayerEventCounter.py
@@ -33,7 +33,6 @@
import PlayerActivity
import FBCommon
import ChItem
import GameLogic_Tianzi
import PlayerGoldInvest
import PlayerActTask
import PlayerActBuyCountGift
@@ -75,14 +74,11 @@
def DoLogic_OnDay(tick):
    GameWorld.Log("MapServer -> OnDay!")
    #副本OnDay事件响应
    #FBLogic.OnDay(tick)
    
    OpenServerActivity.OnDay()
    #仙盟
    FBLogic.OnDay()
    PlayerFamily.FamilyOnDay()
    PlayerArena.OnDay()
    GameLogic_Tianzi.OnDay()
    
    PlayerOfflineSupport.OnDay()
    playerManager = GameWorld.GetPlayerManager()
@@ -128,6 +124,7 @@
def DoLogic_OnWeek(tick):
    GameWorld.Log("MapServer -> OnWeek!")
    
    FBLogic.OnWeek()
    PlayerArena.OnWeek()
    
    playerManager = GameWorld.GetPlayerManager()
@@ -139,8 +136,6 @@
        
        PlayerOnWeek(curPlayer)
        
    #副本OnWeek事件响应
    FBLogic.OnWeek(tick)
    return
def DoLogic_OnMonth(tick):
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/PlayerState.py
@@ -23,6 +23,7 @@
import ChItem
import PlayerGMOper
import OperControlManager
import GameLogic_Qunying
import ShareDefine
import ReadChConfig
import IpyGameDataPY
@@ -1043,6 +1044,8 @@
    PlayerGoldRush.OnProcess(curPlayer)
    #游历
    PlayerTravel.OnProcess(curPlayer)
    #群英榜
    GameLogic_Qunying.OnProcess(curPlayer)
    return
def ProcessPlayerMinute(curPlayer, tick):
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/PlayerViewCache.py
@@ -452,13 +452,18 @@
                 }
    return robotDict
def LoadRobot():
    ## 加载机器人缓存,在服务器启动、重读配置时加载
def GetRobotFightPowerSortList(tempNum=0):
    ## 机器人战力倒序排序列表
    # @return: 倒序排序列表 [[战力, 机器人ID], ...],外部使用可随机修改,不会打乱原始排序
    robotFPSortList = IpyGameDataPY.GetConfigEx("robotFPSortList%s" % tempNum)
    if not robotFPSortList:
    robotFPSortList = []
    viewCacheMgr = DBDataMgr.GetPlayerViewCacheMgr()
    ipyDataMgr = IpyGameDataPY.IPY_Data()
    for index in range(ipyDataMgr.GetRobotCount()):
        ipyData = ipyDataMgr.GetRobotByIndex(index)
            if ipyData.GetTempNum() != tempNum:
                continue
        robotPlayerID = ipyData.GetID()
        curCache = viewCacheMgr.GetPlayerViewCache(robotPlayerID)
        if not curCache:
@@ -468,20 +473,10 @@
        UpdRobotViewCache(curCache, robotPlayerID, ipyData)
        robotFPSortList.append([curCache.GetFightPowerTotal(), robotPlayerID])
    robotFPSortList.sort(reverse=True) # 战力倒序排序
    IpyGameDataPY.SetConfigEx("robotFPSortList", robotFPSortList)
    GameWorld.Log("加载机器人战力排序: %s, %s" % (len(robotFPSortList), robotFPSortList))
    return robotFPSortList
        IpyGameDataPY.SetConfigEx("robotFPSortList%s" % tempNum, robotFPSortList)
        GameWorld.Log("加载机器人战力排序: tempNum=%s,%s, %s" % (tempNum, len(robotFPSortList), robotFPSortList))
def GetRobotFightPowerSortList():
    ## 机器人战力倒序排序列表
    # @return: 倒序排序列表 [[战力, 机器人ID], ...],外部使用可随机修改,不会打乱原始排序
    sortList = []
    robotFPSortList = IpyGameDataPY.GetConfigEx("robotFPSortList")
    if not robotFPSortList:
        robotFPSortList = LoadRobot()
    if robotFPSortList:
        sortList += robotFPSortList
    return sortList
    return robotFPSortList
def UpdRobotViewCache(curCache, robotID, robotIpyData=None):
    ## 更新机器人查看缓存
@@ -495,11 +490,11 @@
        return
    
    #curCache.SetAccID(dbPlayer.AccID)
    realmLV = robotIpyData.GetRealmLV()
    curCache.SetPlayerName(GameWorld.GbkToCode(robotIpyData.GetRobotName()))
    curCache.SetLV(robotInfo.get("LV", 1))
    curCache.SetJob(robotInfo.get("Job", 1))
    curCache.SetRealmLV(robotInfo.get("RealmLV", 0))
    curCache.SetRealmLV(realmLV if realmLV else robotInfo.get("RealmLV", 0))
    curCache.SetFace(robotInfo.get("Face", 0))
    curCache.SetFacePic(robotInfo.get("FacePic", 0))
    curCache.SetTitleID(robotInfo.get("TitleID", 0))
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/PyGameData.py
@@ -46,6 +46,7 @@
g_broadCastList = [] # 后台gm广播
g_arenaPlayerMatchDict = {} # 本服竞技场玩家匹配记录缓存 {playerID:[tagPlayerID, ...], ...}
g_qunyingPlayerMatchDict = {} # 群英榜玩家匹配记录缓存,该功能允许重复的机器人ID,所以记录匹配名次 {playerID:[rank, ...], ...}
g_beautyEffTypeDict = {} # 红颜特殊效果缓存 {playerID:{effType:[effValue, effTypeValue], ...}, ...}
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/ShareDefine.py
@@ -329,7 +329,8 @@
Def_BT_OSA_HeroTrain,    # 开服武将冲榜 7
Def_BT_OSA_BeautyTrain,    # 开服红颜冲榜 8
Def_BT_OSA_MinggeTrain,    # 开服命格冲榜 9
) = range(0, 10)
Def_BT_Qunying,    # 群英榜积分周榜 10
) = range(0, 11)
''' 跨服排行榜类型, 从 150 开始,最大条数在功能配置表 CrossBillboardSet 配置,没配默认100
与本服榜单存储的是不一样的数据库表格,理论上类型可以和本服榜单类型重复,为了做下区分防误导,跨服榜单从 150 开始
@@ -337,7 +338,7 @@
本服榜单表(tagDBBillboard)
'''
CrossBillboardTypeList = (
Def_CBT_ActCTG, # 跨服运营活动 - 充值榜 150
Def_CBT_Qunying, # 跨服群英榜积分周榜 - 150
) = range(150, 150 + 1)
BillboardTypeAllList = BillboardTypeList + CrossBillboardTypeList
@@ -345,7 +346,8 @@
BillboardNameDict = {Def_BT_MainLevel:"主线过关榜", Def_BT_Arena:"演武场积分周榜", Def_BT_Tianzi:"天子考验伤害榜", 
                     Def_BT_OSA_MainLevel:"开服关卡榜", Def_BT_OSA_HeroCall:"开服招募榜", Def_BT_Dingjunge:"定军阁过关榜", 
                     Def_BT_OSA_HeroTrain:"开服武将冲榜", Def_BT_OSA_BeautyTrain:"开服红颜冲榜", Def_BT_OSA_MinggeTrain:"开服命格冲榜", 
                     Def_BT_ActHeroAppear:"武将登场招募榜(分组值1-ActNum)",
                     Def_BT_ActHeroAppear:"武将登场招募榜(分组值1-ActNum)", Def_BT_Qunying:"群英榜积分周榜",
                     Def_CBT_Qunying:"跨服群英榜积分周榜(分组值1-zoneID)",
                     }
#仙盟榜单类型
@@ -596,7 +598,9 @@
CDBPlayerRefresh_ArenaTicket, # 挑战券 286
CDBPlayerRefresh_TehuiPoint, # 特惠印绶 287
CDBPlayerRefresh_OSAPoint, # 开服庆典积分 288
) = range(146, 289)
CDBPlayerRefresh_QunyingTicket, # 群英榜挑战令 289
CDBPlayerRefresh_QunyingPoint, # 群英榜积分 290
) = range(146, 291)
TYPE_Price_Gold_Paper_Money = 5    # 金钱类型,(先用礼券,再用金子)
TYPE_Price_FamilyExp = 6 # 战盟经验
@@ -640,13 +644,15 @@
TYPE_Price_ArenaTicket = 53    # 演武场挑战券
TYPE_Price_TehuiPoint = 54    # 特惠印绶
TYPE_Price_OSAPoint = 55    # 开服庆典积分
TYPE_Price_QunyingTicket = 56    # 群英榜挑战令
TYPE_Price_QunyingPoint = 57    # 群英榜积分
TYPE_Price_PayCoinDay = 98    # 代币时效,每日过天重置
TYPE_Price_PayCoin = 99    # 代币
#key可用于遍历所有货币,value仅GM相关会用到
MoneyNameDict = {
                 1:"金币", 15:"公会贡献币", 41:"战锤", 42:"将星玉髓", 43:"将魂", 51:"招募积分", 52:"淘金令", 53:"挑战券", 54:"特惠印绶", 
                 55:"可用开服庆典积分",
                 55:"可用开服庆典积分", 56:"群英榜挑战令", 57:"群英榜积分",
                 98:"代币时效", 99:"代币"
                 }
#MoneyNameDict = {
@@ -672,6 +678,8 @@
                           TYPE_Price_ArenaTicket:CDBPlayerRefresh_ArenaTicket,
                           TYPE_Price_TehuiPoint:CDBPlayerRefresh_TehuiPoint,
                           TYPE_Price_OSAPoint:CDBPlayerRefresh_OSAPoint,
                           TYPE_Price_QunyingTicket:CDBPlayerRefresh_QunyingTicket,
                           TYPE_Price_QunyingPoint:CDBPlayerRefresh_QunyingPoint,
                           TYPE_Price_PayCoinDay:CDBPlayerRefresh_PayCoinDay,
                           #TYPE_Price_Rune:CDBPlayerRefresh_Rune,
                           #TYPE_Price_RuneSplinters:CDBPlayerRefresh_RuneSplinters,
@@ -714,9 +722,7 @@
GameFuncID_OSA_HeroTrain = 59   # 开服武将冲榜
GameFuncID_OSA_BeautyTrain = 60 # 开服红颜冲榜
GameFuncID_OSA_MinggeTrain = 61 # 开服命格冲榜
# 以下为暂时无用的
GameFuncID_Pet = -1             # 宠物,灵宠 6
GameFuncID_Qunying = 62         # 群英榜
#背包类型
(
@@ -785,13 +791,14 @@
                       Def_GameRecType_BatPreset, # 战斗方案预设额外存储信息,playerID 309
                       Def_GameRecType_Setting, # 前端自定义存储的设置内容,playerID 310
                       Def_GameRecType_Treasure, # 寻宝记录,treasureType 311
                       ) = range(300, 1 + 311)
                       Def_GameRecType_QunyingRecord, # 群英榜玩家挑战记录,playerID 312
                       ) = range(300, 1 + 312)
#通用信息记录新 - 字典key配置,如果有配置,则可额外按对应记录Value值存储字典,方便快速取值,可配置Value编号 1~8,配空默认 Value1
Def_GameRecValueKeyDict = {
                           Def_GameRecType_Xiangong:[1],
                           }
#仅查看自己的记录
Def_ViewGameRecSelfList = [Def_GameRecType_ArenaRecord, Def_GameRecType_BatPreset, Def_GameRecType_Setting]
Def_ViewGameRecSelfList = [Def_GameRecType_ArenaRecord, Def_GameRecType_BatPreset, Def_GameRecType_Setting, Def_GameRecType_QunyingRecord]
#UserData不使用json的记录类型
UserDataNOJsonRecTypeList = []
@@ -1199,12 +1206,13 @@
BatPresetList = (
BatPreset_Main, # 主线战斗 1
BatPreset_ArenaDef, # 演武场防守 2
) = range(1, 1 + 2)
BatPreset_QunyingDef, # 群英榜防守 3
) = range(1, 1 + 3)
# 需要缓存的战斗功能预设,一般只要主线+防守功能预设,主动攻击的PVE功能可以不用,如某个副本的主动攻击预设
NeedCacheBatPresetList = [BatPreset_Main, BatPreset_ArenaDef]
NeedCacheBatPresetList = [BatPreset_Main, BatPreset_ArenaDef, BatPreset_QunyingDef]
BatPresetName = {BatPreset_Main:"主线", BatPreset_ArenaDef:"演武场防守"}
BatPresetName = {BatPreset_Main:"主线", BatPreset_ArenaDef:"演武场防守", BatPreset_QunyingDef:"群英榜防守"}
# 功能预设定义
FuncPresetList = (