From 11c9a3b5846401523e4dafc17f2a074a712730da Mon Sep 17 00:00:00 2001
From: hxp <ale99527@vip.qq.com>
Date: 星期三, 11 三月 2026 18:27:10 +0800
Subject: [PATCH] 526 【挑战】PVP群英榜-后端(本服群英榜;优化机器人表支持按功能加载不同的机器人;)

---
 ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/GameWorldLogic/FBLogic.py                     |   53 -
 ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/GM/Commands/Qunying.py                        |   82 +++
 ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/ShareDefine.py                                |   34 
 ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/ChPyNetPack.py                                |   52 ++
 ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/ChPyNetSendPack.py                            |  255 +++++++++
 ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/PlayerViewCache.py                     |   53 -
 ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/PlayerBillboard.py                     |  179 +++++-
 ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/PyGameData.py                                 |    1 
 ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/DB/StructData/DBBillboard.py                  |   73 ++
 ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/PlayerState.py                         |    3 
 ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/GameWorldLogic/FBProcess/FBCommon.py          |    3 
 ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/GM/Commands/Billboard.py                      |   15 
 ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/PyNetPack.ini                                        |   12 
 ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/GameWorldLogic/GameWorldEvent.py              |    6 
 ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/IpyGameDataPY.py                              |   41 +
 ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/ChPlayer.py                            |    6 
 PySysDB/PySysDBPY.h                                                                                               |   15 
 ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/GameFuncComm.py                        |    2 
 ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/PlayerControl.py                       |    2 
 ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/PlayerEventCounter.py                  |    9 
 ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/GameWorldLogic/FBProcess/GameLogic_Qunying.py |  599 +++++++++++++++++++++++
 ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/ChConfig.py                                   |   20 
 22 files changed, 1,387 insertions(+), 128 deletions(-)

diff --git a/PySysDB/PySysDBPY.h b/PySysDB/PySysDBPY.h
index adf6e63..05b66d7 100644
--- a/PySysDB/PySysDBPY.h
+++ b/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;	//机器人缓存
 };
diff --git a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/PyNetPack.ini b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/PyNetPack.ini
index a70516d..035e03a 100644
--- a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/PyNetPack.ini
+++ b/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
diff --git a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/ChConfig.py b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/ChConfig.py
index 3297e46..b06ccd3 100644
--- a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/ChConfig.py
+++ b/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
diff --git a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/ChPyNetPack.py b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/ChPyNetPack.py
index 2ef3d6c..90f9699 100644
--- a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/ChPyNetPack.py
+++ b/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):
diff --git a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/ChPyNetSendPack.py b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/ChPyNetSendPack.py
index 6071124..d0f7884 100644
--- a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/ChPyNetSendPack.py
+++ b/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):
diff --git a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/DB/StructData/DBBillboard.py b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/DB/StructData/DBBillboard.py
index 2818ac5..64d5ff9 100644
--- a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/DB/StructData/DBBillboard.py
+++ b/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)))
-        self.__billboardList.sort(key=lambda b: (b.GetCmpValue(), b.GetCmpValue2(), b.GetCmpValue3(), -b.GetTime()), reverse=True)
+        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], ...]
diff --git a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/GM/Commands/Billboard.py b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/GM/Commands/Billboard.py
index ceb798d..cf018cb 100644
--- a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/GM/Commands/Billboard.py
+++ b/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,9 +105,12 @@
         name1 = dataPlayerName
         cmpValue = dataCmpValue1
         cmpValue2 = dataCmpValue2
-        PlayerBillboard.UpdateBillboard(billboardType, groupValue1, dataID, name1, name2, type2, value1, value2, 
-                                        cmpValue, cmpValue2, cmpValue3, groupValue2, id2, False, 
-                                        value3=value3, value4=value4, value5=value5)
+        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)
         
     billboardObj.SortData()
     
diff --git a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/GM/Commands/Qunying.py b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/GM/Commands/Qunying.py
new file mode 100644
index 0000000..455b503
--- /dev/null
+++ b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/GM/Commands/Qunying.py
@@ -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
diff --git a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/GameWorldLogic/FBLogic.py b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/GameWorldLogic/FBLogic.py
index d62bc5b..bcc222c 100644
--- a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/GameWorldLogic/FBLogic.py
+++ b/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())
-    
-    callFunc = GameWorld.GetExecFunc(FBProcess, "GameLogic_%s.%s" % (do_FBLogic_ID, "OnDay"))
-    
-    if callFunc == None:
-        #GameWorld.Log("副本逻辑不可使用   GameLogic_%d"%(mapID))
-        return False
-    
-    #执行副本逻辑
-    callFunc(tick)
+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
+
+## 副本每周逻辑-玩家, 不管玩家是否在该副本地图中都会触发
+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
 
 ## 副本每日逻辑-玩家, 不管玩家是否在该副本地图中都会触发
diff --git a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/GameWorldLogic/FBProcess/FBCommon.py b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/GameWorldLogic/FBProcess/FBCommon.py
index 48e7665..9519666 100644
--- a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/GameWorldLogic/FBProcess/FBCommon.py
+++ b/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):
diff --git a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/GameWorldLogic/FBProcess/GameLogic_Qunying.py b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/GameWorldLogic/FBProcess/GameLogic_Qunying.py
new file mode 100644
index 0000000..cb18592
--- /dev/null
+++ b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/GameWorldLogic/FBProcess/GameLogic_Qunying.py
@@ -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
diff --git a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/GameWorldLogic/GameWorldEvent.py b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/GameWorldLogic/GameWorldEvent.py
index 75862a7..4e8750f 100644
--- a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/GameWorldLogic/GameWorldEvent.py
+++ b/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
diff --git a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/IpyGameDataPY.py b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/IpyGameDataPY.py
index 710ec5e..8bab621 100644
--- a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/IpyGameDataPY.py
+++ b/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
diff --git a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/ChPlayer.py b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/ChPlayer.py
index 7223172..b604704 100644
--- a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/ChPlayer.py
+++ b/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)
         
         
     # 首充礼包奖励
diff --git a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/GameFuncComm.py b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/GameFuncComm.py
index 3538cfe..96a507b 100644
--- a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/GameFuncComm.py
+++ b/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),
diff --git a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/PlayerBillboard.py b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/PlayerBillboard.py
index 72fdac1..ccacc87 100644
--- a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/PlayerBillboard.py
+++ b/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,7 +383,12 @@
     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)
     viewCnt = 20 if not viewCnt else min(viewCnt, 100) # 默认20,最多100
@@ -320,7 +399,13 @@
     # 查看viewID前后名次
     if viewID:
         viewBFCnt = 3 # 查看ViewID返回前后数据条数,一般设置为奇数
-        viewIDIndex = billboardObj.IndexOfByID(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
             viewIDStartIndex = max(0, viewIDIndex - viewBFCnt / 2)
@@ -334,10 +419,24 @@
     clientPack.DataTotal = count
     clientPack.PageDataList = []
     for index in viewRange:
-        billboardData = billboardObj.At(index)
+        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 = billboardObj.At(index)
+        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()
diff --git a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/PlayerControl.py b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/PlayerControl.py
index 0b9de40..b219a6d 100644
--- a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/PlayerControl.py
+++ b/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)
diff --git a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/PlayerEventCounter.py b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/PlayerEventCounter.py
index d0181ea..cc038a9 100644
--- a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/PlayerEventCounter.py
+++ b/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):
diff --git a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/PlayerState.py b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/PlayerState.py
index 1f41466..2a06406 100644
--- a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/PlayerState.py
+++ b/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):
diff --git a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/PlayerViewCache.py b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/PlayerViewCache.py
index ab10733..2891192 100644
--- a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/PlayerViewCache.py
+++ b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/PlayerViewCache.py
@@ -452,36 +452,31 @@
                  }
     return robotDict
 
-def LoadRobot():
-    ## 加载机器人缓存,在服务器启动、重读配置时加载
-    robotFPSortList = []
-    viewCacheMgr = DBDataMgr.GetPlayerViewCacheMgr()
-    ipyDataMgr = IpyGameDataPY.IPY_Data()
-    for index in range(ipyDataMgr.GetRobotCount()):
-        ipyData = ipyDataMgr.GetRobotByIndex(index)
-        robotPlayerID = ipyData.GetID()
-        curCache = viewCacheMgr.GetPlayerViewCache(robotPlayerID)
-        if not curCache:
-            curCache = viewCacheMgr.AddPlayerViewCache(robotPlayerID)
-        if not curCache:
-            continue
-        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
-
-def GetRobotFightPowerSortList():
+def GetRobotFightPowerSortList(tempNum=0):
     ## 机器人战力倒序排序列表
     # @return: 倒序排序列表 [[战力, 机器人ID], ...],外部使用可随机修改,不会打乱原始排序
-    sortList = []
-    robotFPSortList = IpyGameDataPY.GetConfigEx("robotFPSortList")
+    robotFPSortList = IpyGameDataPY.GetConfigEx("robotFPSortList%s" % tempNum)
     if not robotFPSortList:
-        robotFPSortList = LoadRobot()
-    if robotFPSortList:
-        sortList += robotFPSortList
-    return sortList
+        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:
+                curCache = viewCacheMgr.AddPlayerViewCache(robotPlayerID)
+            if not curCache:
+                continue
+            UpdRobotViewCache(curCache, robotPlayerID, ipyData)
+            robotFPSortList.append([curCache.GetFightPowerTotal(), robotPlayerID])
+        robotFPSortList.sort(reverse=True) # 战力倒序排序
+        IpyGameDataPY.SetConfigEx("robotFPSortList%s" % tempNum, robotFPSortList)
+        GameWorld.Log("加载机器人战力排序: tempNum=%s,%s, %s" % (tempNum, len(robotFPSortList), robotFPSortList))
+        
+    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))
diff --git a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/PyGameData.py b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/PyGameData.py
index baa0492..a6e1d69 100644
--- a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/PyGameData.py
+++ b/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], ...}, ...}
 
diff --git a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/ShareDefine.py b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/ShareDefine.py
index d8c6a8a..d319445 100644
--- a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/ShareDefine.py
+++ b/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 = (

--
Gitblit v1.8.0