From bd61f5e92fad5dc02f693747fde8fdb86ee01c5c Mon Sep 17 00:00:00 2001
From: hxp <ale99527@vip.qq.com>
Date: 星期五, 06 十二月 2019 20:46:52 +0800
Subject: [PATCH] 8346 【恺英】【后端】协助系统(初版,可完成协助完整流程,增加新NPC伤血管理,支持协助、支持超过20亿伤害)

---
 ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/PyGameData.py                                 |    2 
 ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/NPC/NPCHurtManager.py                         |  953 +++++++++++++++++++++++
 ServerPython/CoreServerGroup/GameServer/Script/Player/PlayerAssist.py                                             |  668 ++++++++++++++++
 ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/GM/Commands/SetWorldPos.py                    |    3 
 ServerPython/CoreServerGroup/GameServer/Script/GM/Commands/Assist.py                                              |   59 +
 ServerPython/CoreServerGroup/GameServer/Script/ShareDefine.py                                                     |    3 
 ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/PlayerControl.py                       |   23 
 ServerPython/CoreServerGroup/GameServer/PyNetPack.ini                                                             |   23 
 ServerPython/CoreServerGroup/GameServer/Script/Player/PlayerControl.py                                            |   10 
 ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/ShareDefine.py                                |    3 
 ServerPython/CoreServerGroup/GameServer/Script/Player/PlayerQuery.py                                              |   11 
 ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/PlayerAssist.py                        |  151 +++
 ServerPython/CoreServerGroup/GameServer/Script/Player/ChPlayer.py                                                 |   18 
 ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/PlayerTeam.py                          |   15 
 ServerPython/CoreServerGroup/GameServer/Script/PyDataManager.py                                                   |   14 
 ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Attack/AttackLogic/AttackCommon.py            |   21 
 ServerPython/CoreServerGroup/GameServer/Script/Player/PlayerFamily.py                                             |   23 
 ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/GM/Commands/PrintNPCHurt.py                   |   34 
 ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/PyNetPack.ini                                        |   16 
 ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/NPC/NPCCommon.py                              |  338 -------
 ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/RemoteQuery/GY_Query_PlayerAssist.py   |   51 +
 ServerPython/CoreServerGroup/GameServer/Script/Player/PlayerTeam.py                                               |   10 
 ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Attack/AttackLogic/NormalNPC_Attack_Player.py |    2 
 ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/PlayerEventCounter.py                  |    7 
 ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/ChConfig.py                                   |    2 
 25 files changed, 2,113 insertions(+), 347 deletions(-)

diff --git a/ServerPython/CoreServerGroup/GameServer/PyNetPack.ini b/ServerPython/CoreServerGroup/GameServer/PyNetPack.ini
index a05a72c..06be58e 100644
--- a/ServerPython/CoreServerGroup/GameServer/PyNetPack.ini
+++ b/ServerPython/CoreServerGroup/GameServer/PyNetPack.ini
@@ -390,6 +390,29 @@
 PacketSubCMD_3=0x03
 PacketCallFunc_3=OnQueryXMZZInfo
 
+[PlayerAssist]
+ScriptName = Player\PlayerAssist.py
+Writer = hxp
+Releaser = hxp
+RegType = 0
+RegisterPackCount = 4
+
+PacketCMD_1=0xB0
+PacketSubCMD_1=0x12
+PacketCallFunc_1=OnStartAssistBoss
+
+PacketCMD_2=0xB0
+PacketSubCMD_2=0x13
+PacketCallFunc_2=OnCancelAssistBoss
+
+PacketCMD_3=0xB0
+PacketSubCMD_3=0x14
+PacketCallFunc_3=OnUseAssistThanksGift
+
+PacketCMD_4=0xB0
+PacketSubCMD_4=0x15
+PacketCallFunc_4=OnGetAssistThanksGift
+
 [PlayerTalk]
 ScriptName = Player\PlayerTalk.py
 Writer = alee
diff --git a/ServerPython/CoreServerGroup/GameServer/Script/GM/Commands/Assist.py b/ServerPython/CoreServerGroup/GameServer/Script/GM/Commands/Assist.py
new file mode 100644
index 0000000..d9ead76
--- /dev/null
+++ b/ServerPython/CoreServerGroup/GameServer/Script/GM/Commands/Assist.py
@@ -0,0 +1,59 @@
+#!/usr/bin/python
+# -*- coding: GBK -*-
+#-------------------------------------------------------------------------------
+#
+##@package GM.Commands.Assist
+#
+# @todo:显示当前协助列表
+# @author hxp
+# @date 2019-12-06
+# @version 1.0
+#
+# 详细描述: 显示当前协助列表
+#
+#-------------------------------------------------------------------------------
+#"""Version = 2019-12-06 21:00"""
+#-------------------------------------------------------------------------------
+
+import GameWorld
+import PyDataManager
+
+#---------------------------------------------------------------------
+#全局变量
+#---------------------------------------------------------------------
+
+#---------------------------------------------------------------------
+#逻辑实现
+## 执行逻辑
+#  @param curPlayer 当前玩家
+#  @param gmList [cmdIndex gmAccID msg]
+#  @return None
+#  @remarks 函数详细说明.
+def OnExec(curPlayer, gmList):
+    
+    assistMgr = PyDataManager.GetPlayerAssistPyManager()
+    GameWorld.Log("---------- 当前总协助条数: %s" % len(assistMgr.allAssistDict))
+    for assist in assistMgr.allAssistDict.values():
+        GameWorld.DebugLog("%s" % assist.outputString())
+        GameWorld.DebugLog("IsSaveDB=%s,ObjID=%s,AssistType=%s,AssistPlayerIDList=%s" % (assist.IsSaveDB, assist.ObjID, assist.AssistType, assist.AssistPlayerIDList))
+        GameWorld.DebugLog("--- ")
+        
+    GameWorld.DebugLog("--- ")
+    for familyID, assistList in assistMgr.familyAssistDict.items():
+        GameWorld.DebugLog("仙盟协助列表: %s, 条数: %s" % (familyID, len(assistList)))
+        for assist in assistList:
+            GameWorld.DebugLog("    %s" % assist.GUID)
+        
+    GameWorld.DebugLog("--- ")
+    for playerID, assistList in assistMgr.playerNoSaveDBAssistDict.items():
+        GameWorld.DebugLog("玩家协助列表: %s, 条数: %s" % (playerID, len(assistList)))
+        for assist in assistList:
+            GameWorld.DebugLog("    %s" % assist.GUID)
+        
+    GameWorld.DebugLog("--- ")
+    for playerID, assist in assistMgr.playerAssistingDict.items():
+        GameWorld.DebugLog("玩家协助中的: %s, %s" % (playerID, assist.GUID))
+        
+    GameWorld.DebugLog("-----------------------------------------")
+    return
+
diff --git a/ServerPython/CoreServerGroup/GameServer/Script/Player/ChPlayer.py b/ServerPython/CoreServerGroup/GameServer/Script/Player/ChPlayer.py
index 34b1463..94b3cbf 100644
--- a/ServerPython/CoreServerGroup/GameServer/Script/Player/ChPlayer.py
+++ b/ServerPython/CoreServerGroup/GameServer/Script/Player/ChPlayer.py
@@ -62,6 +62,7 @@
 import IPY_PlayerDefine
 import CrossRealmPK
 import AuctionHouse
+import PlayerAssist
 import PlayerFB
 #---------------------------------------------------------------------
 
@@ -189,7 +190,14 @@
         PlayerZhuXianBoss.OnPlayerLogin(curPlayer)
         #骑宠boss状态通知
         PlayerHorsePetBoss.OnLogin(curPlayer)
+        #协助
+        PlayerAssist.OnPlayerLogin(curPlayer, False)
         GMT_CTG.OnPlayerLogin(curPlayer)
+        
+    else:
+        #协助
+        PlayerAssist.OnPlayerLogin(curPlayer, True)
+        
     return
 
 def __UpdOnedayJobPlayerLoginoffTime(curPlayer):
@@ -542,6 +550,8 @@
     SetPlayerOfflineTime(curPlayer)
     #拍卖行
     AuctionHouse.OnPlayerLeaveServer(curPlayer)
+    #协助
+    PlayerAssist.OnLeaveServer(curPlayer)
     #------------镖车逻辑
     #TruckPlayerDisconnectProcess(curPlayer, tick)
     
@@ -645,6 +655,14 @@
         PlayerTeam.SetTeamCheckState(curPlayer, packValue)
         return
     
+    #if packType == IPY_GameServer.CDBPlayerRefresh_ExAttr1:
+    #    PlayerControl.SetAssistTagPlayerID(curPlayer, packValue)
+    #    return
+    
+    if packType == IPY_GameServer.CDBPlayerRefresh_ExAttr3:
+        PlayerControl.SetFBFuncLineID(curPlayer, packValue)
+        return
+    
     #---常规逻辑处理---
         
     elif packType == IPY_GameServer.CDBPlayerRefresh_LV:
diff --git a/ServerPython/CoreServerGroup/GameServer/Script/Player/PlayerAssist.py b/ServerPython/CoreServerGroup/GameServer/Script/Player/PlayerAssist.py
new file mode 100644
index 0000000..91db9e4
--- /dev/null
+++ b/ServerPython/CoreServerGroup/GameServer/Script/Player/PlayerAssist.py
@@ -0,0 +1,668 @@
+#!/usr/bin/python
+# -*- coding: GBK -*-
+#-------------------------------------------------------------------------------
+#
+##@package Player.PlayerAssist
+#
+# @todo:协助系统
+# @author hxp
+# @date 2019-12-06
+# @version 1.0
+#
+# 详细描述: 协助系统
+#
+#-------------------------------------------------------------------------------
+#"""Version = 2019-12-06 21:00"""
+#-------------------------------------------------------------------------------
+
+import PyDataManager
+import NetPackCommon
+import PyGameDataStruct
+import ChPyNetSendPack
+import PlayerControl
+import PlayerFamily
+import ShareDefine
+import PlayerTeam
+import GameWorld
+import ChConfig
+
+import uuid
+
+# 协助类型
+(
+AssistType_Unknown,
+AssistType_Boss, # boss
+AssistType_TeamFB, # 组队副本
+) = range(3)
+
+
+def OnPlayerLogin(curPlayer, isTJ=False):
+    ## 玩家上线
+    # @param isTJ: 是否脱机上线
+    
+    familyID = curPlayer.GetFamilyID()
+    playerID = curPlayer.GetPlayerID()
+    if not familyID:
+        return
+    
+    assistMgr = PyDataManager.GetPlayerAssistPyManager()
+    if playerID in assistMgr.playerNoSaveDBAssistDict:
+        playerNoSaveList = assistMgr.playerNoSaveDBAssistDict[playerID]
+        
+        # 非脱机上线
+        if not isTJ:
+            GameWorld.DebugLog("玩家上线,恢复协助发布到仙盟列表!", playerID)
+            if familyID not in assistMgr.familyAssistDict:
+                assistMgr.familyAssistDict[familyID] = []
+            recoverList = []
+            familyAssistList = assistMgr.familyAssistDict[familyID]
+            for assistObj in playerNoSaveList:
+                familyAssistList.append(assistObj)
+                recoverList.append(assistObj)
+                GameWorld.DebugLog("    恢复: %s" % assistObj.GUID)
+                
+            if recoverList:
+                # 通知本盟其他玩家
+                PlayerFamily.SendFamilyFakePack(familyID, GetAssistInfoListPack(recoverList), [playerID])
+                
+        # 脱机上线
+        else:
+            GameWorld.DebugLog("玩家脱机上线,强制取消发布的非存库协助!", playerID)
+            for assistObj in playerNoSaveList:
+                OnCancelPlayerRequestAssist(assistObj, "TJGLogin", True)
+                
+    if not isTJ:
+        SyncFamilyAssist(curPlayer)
+        
+    # 没有协助中的信息
+    if playerID in assistMgr.playerAssistingDict:
+        assistObj = assistMgr.playerAssistingDict[playerID]
+        if not isTJ:
+            tagPlayerID = assistObj.PlayerID
+            GameWorld.DebugLog("非脱机上线,继续协助!tagPlayerID=%s" % tagPlayerID, playerID)
+            
+            PlayerControl.SetAssistTagPlayerID(curPlayer, tagPlayerID)
+            assistPack = ChPyNetSendPack.tagGCAssistingInfo()
+            assistPack.AssistGUID = assistObj.GUID
+            NetPackCommon.SendFakePack(curPlayer, assistPack)
+            
+        # 脱机上线
+        else:
+            OnCancelPlayerAssist(curPlayer, playerID, assistObj, "TJGLogin", True)
+            
+    return
+
+def OnLeaveServer(curPlayer):
+    ## 玩家离线
+    familyID = curPlayer.GetFamilyID()
+    if not familyID:
+        return
+    
+    if PlayerControl.GetAssistTagPlayerID(curPlayer):
+        PlayerControl.SetAssistTagPlayerID(curPlayer, 0)
+        
+    assistMgr = PyDataManager.GetPlayerAssistPyManager()
+    if familyID not in assistMgr.familyAssistDict:
+        return
+    
+    playerID = curPlayer.GetPlayerID()
+    if playerID not in assistMgr.playerNoSaveDBAssistDict:
+        #GameWorld.DebugLog("玩家没有发布过协助,离线不处理!", playerID)
+        return
+    playerNoSaveList = assistMgr.playerNoSaveDBAssistDict[playerID]    
+    familyAssistList = assistMgr.familyAssistDict[familyID]
+    # 暂时移除离线玩家发布的不存库协助信息,不再让其他盟友继续前往协助,已经在协助的不影响
+    for assistObj in playerNoSaveList:
+        if assistObj not in familyAssistList:
+            continue
+        familyAssistList.remove(assistObj)
+        SyncFamilyClearAssist(familyID, assistObj.GUID)
+        GameWorld.DebugLog("玩家下线,暂时从仙盟协助列表移除玩家发布的协助: %s" % assistObj.GUID)
+        
+    return
+
+def OnPlayerLeaveFamily(familyID, leavePlayerID, leavePlayer):
+    ## 玩家离开仙盟
+    ## @param leavePlayer: 可能不在线为None
+    
+    assistMgr = PyDataManager.GetPlayerAssistPyManager()
+    
+    # 玩家发布的
+    if leavePlayerID in assistMgr.playerNoSaveDBAssistDict:
+        playerAssistList = assistMgr.playerNoSaveDBAssistDict[leavePlayerID]
+        for assistObj in playerAssistList[::-1]:
+            OnCancelPlayerRequestAssist(assistObj, "LeaveFamily", True)
+            
+    # 玩家协助中的
+    if leavePlayerID in assistMgr.playerAssistingDict:
+        assistObj = assistMgr.playerAssistingDict[leavePlayerID]
+        OnCancelPlayerAssist(leavePlayer, leavePlayerID, assistObj, "LeaveFamily", True)
+        
+    return
+
+def OnInitAssistData(dbData, isSaveDB):
+    ## 加载协助数据额外处理
+    setattr(dbData, "IsSaveDB", isSaveDB) # 是否保存数据库,离线可协助的需要存库,如挖矿类
+    setattr(dbData, "ObjID", 0) # NPC实例ID
+    
+    assistType = AssistType_Unknown
+    if dbData.NPCID:
+        assistType = AssistType_Boss
+    else:
+        assMapID = dbData.MapID
+        gameMap = GameWorld.GetMap(assMapID)
+        if gameMap and gameMap.GetMapFBType() == ChConfig.fbtTeam:
+            assistType = AssistType_TeamFB
+    setattr(dbData, "AssistType", assistType) # 协助类型
+    setattr(dbData, "AssistPlayerIDList", []) # 协助中的玩家ID列表
+    return
+
+#// B0 12 开始协助Boss #tagCGStartAssistBoss
+#
+#struct    tagCGStartAssistBoss
+#
+#{
+#    tagHead        Head;
+#    char        AssistGUID[40];    //协助GUID
+#};
+def OnStartAssistBoss(index, clientData, tick):
+    curPlayer = GameWorld.GetPlayerManager().GetPlayerByIndex(index)
+    assistGUID = clientData.AssistGUID
+    
+    assistMgr = PyDataManager.GetPlayerAssistPyManager()
+    if assistGUID not in assistMgr.allAssistDict:
+        GameWorld.DebugLog("不存在该协助!assistGUID=%s" % assistGUID)
+        return
+    assistObj = assistMgr.allAssistDict[assistGUID]
+    if assistObj.AssistType != AssistType_Boss:
+        return
+    if assistObj.PlayerID == curPlayer.GetPlayerID():
+        GameWorld.DebugLog("不能协助自己!")
+        return
+    
+    if assistObj.FamilyID != curPlayer.GetFamilyID():
+        GameWorld.DebugLog("非同盟玩家不能协助!")
+        return
+    
+    mapID = assistObj.MapID
+    lineID = assistObj.LineID
+    gameMap = GameWorld.GetMap(mapID)
+    if not gameMap:
+        return
+    if gameMap.GetMapFBType() != ChConfig.fbtNull:
+        playerMapID = curPlayer.GetMapID()
+        playerLineID = PlayerControl.GetFBFuncLineID(curPlayer)
+        if playerMapID != mapID or playerLineID != lineID:
+            #副本中无法协助
+            PlayerControl.NotifyCode(curPlayer, "AssistFBLimit")
+            return
+        
+    # 设定协助必须离开队伍
+    if gameMap.GetMapFBType() != ChConfig.fbtTeam:
+        curTeam = curPlayer.GetTeam()
+        if curTeam:
+            PlayerTeam.DoPlayerLeaveTeam(curPlayer, curTeam, tick)
+            
+    SetPlayerStartAssistBoss(curPlayer, assistObj)
+    return
+
+#// B0 13 取消协助Boss #tagCGCancelAssistBoss
+#
+#struct    tagCGCancelAssistBoss
+#
+#{
+#    tagHead        Head;
+#    char        AssistGUID[40];    //协助GUID
+#};
+def OnCancelAssistBoss(index, clientData, tick):
+    curPlayer = GameWorld.GetPlayerManager().GetPlayerByIndex(index)
+    assistGUID = clientData.AssistGUID
+    
+    playerID = curPlayer.GetPlayerID()
+    assistMgr = PyDataManager.GetPlayerAssistPyManager()
+    if playerID not in assistMgr.playerAssistingDict:
+        GameWorld.DebugLog("没有在协助中,无需取消!", playerID)
+        return
+    assistObj = assistMgr.playerAssistingDict[playerID]
+    if assistGUID != assistObj.GUID:
+        GameWorld.DebugLog("非正在协助中的GUID,不能取消!assistGUID=%s,assisting=%s" % (assistGUID, assistObj.GUID), playerID)
+        return
+    OnCancelPlayerAssist(curPlayer, curPlayer.GetPlayerID(), assistObj, "ClientCancel", True)
+    return
+
+def SetPlayerStartAssistBoss(curPlayer, assistObj):
+    ## 玩家开始协助 boss
+    
+    assistGUID = assistObj.GUID
+    tagPlayerID = assistObj.PlayerID
+    mapID = assistObj.MapID
+    lineID = assistObj.LineID
+    npcID = assistObj.NPCID
+    objID = assistObj.ObjID
+    
+    tagPlayer = GameWorld.GetPlayerManager().FindPlayerByID(tagPlayerID)
+    if not tagPlayer:
+        GameWorld.DebugLog("玩家已离线,无法协助!tagPlayerID=%s" % (tagPlayerID))
+        return
+    
+    if tagPlayer.GetMapID() != mapID:
+        GameWorld.DebugLog("目标玩家已不在请求协助的地图,无法协助!tagPlayerID=%s" % (tagPlayerID))
+        return
+    
+    playerID = curPlayer.GetPlayerID()
+    # 设置新协助之前需要先取消正在进行中的协助
+    assistMgr = PyDataManager.GetPlayerAssistPyManager()
+    if playerID in assistMgr.playerAssistingDict:
+        assistingObj = assistMgr.playerAssistingDict[playerID]
+        if assistGUID != assistingObj.GUID:
+            OnCancelPlayerAssist(curPlayer, playerID, assistingObj, "StartNewAssistBoss", True)
+    assistMgr.playerAssistingDict[playerID] = assistObj
+    
+    #if playerID in assistObj.AssistPlayerIDList:
+    #    GameWorld.DebugLog("已经在协助中!", playerID)
+    #    return
+    if playerID not in assistObj.AssistPlayerIDList:
+        assistObj.AssistPlayerIDList.append(playerID)
+        
+    GameWorld.DebugLog("开始协助: tagPlayerID=%s,mapID=%s,lineID=%s,npcID=%s,objID=%s" % (tagPlayerID, mapID, lineID, npcID, objID), playerID)
+    
+    # 设置协助
+    PlayerControl.SetAssistTagPlayerID(curPlayer, tagPlayerID)
+    
+    # 通知目标玩家
+    assistPlayerID = curPlayer.GetPlayerID()
+    assistPlayerName = curPlayer.GetName()
+    # xxx开始协助你
+    PlayerControl.NotifyCode(tagPlayer, "AssistStart", [assistPlayerName])
+    
+    tagPlayerName = tagPlayer.GetName()
+    tagTeamID = tagPlayer.GetTeamID()
+    assistData = [mapID, "Start", assistGUID, assistPlayerID, assistPlayerName, tagPlayerID, tagPlayerName, tagTeamID, lineID, objID, npcID]
+    GameWorld.SendMapServerMsgEx(ShareDefine.Def_Notify_WorldKey_AssistBoss, assistData)
+    return
+
+def OnAddAssistBossPlayerOK(queryData):
+    ## 添加协助Boss的玩家成功,通知协助玩家可以前往
+    assistGUID, assistPlayerID = queryData
+    assistPlayer = GameWorld.GetPlayerManager().FindPlayerByID(assistPlayerID)
+    if not assistPlayer:
+        return
+    
+    assistPack = ChPyNetSendPack.tagGCAssistingInfo()
+    assistPack.AssistGUID = assistGUID
+    NetPackCommon.SendFakePack(assistPlayer, assistPack)
+    return
+
+def SetPlayerStartAssistTeamFB(curPlayer, queryData):
+    ## 开始协助组队副本 - 玩家进入副本后才真正进入协助状态
+    
+    #mapID, lineID, tagPlayerID = queryData
+    
+    return
+
+def MapServer_PlayerAssistLogic(curPlayer, msgList, tick):
+    ## 地图同步的协助信息逻辑处理
+    
+    playerID = 0 if not curPlayer else curPlayer.GetPlayerID()
+    familyID = 0 if not curPlayer else curPlayer.GetFamilyID()
+    queryType, queryData = msgList
+    #result = []
+    
+    GameWorld.Log("收到地图协助信息: familyID=%s,queryType=%s,queryData=%s" % (familyID, queryType, queryData), playerID)
+    
+    # 请求协助boss
+    if queryType == "RequestAssistBoss":
+        __DoRequestAssistBoss(curPlayer, queryData)
+        return
+    
+    # 添加协助Boss的玩家成功
+    elif queryType == "AddAssistBossPlayerOK":
+        OnAddAssistBossPlayerOK(queryData)
+        return
+    
+    # 取消boss协助发布
+    elif queryType == "OnCancelBossRequestAssist":
+        mapID, lineID, npcID, objID, reason, cancelPlayerIDList = queryData
+        assistMgr = PyDataManager.GetPlayerAssistPyManager()
+        for cancelPlayerID in cancelPlayerIDList:
+            if cancelPlayerID in assistMgr.playerNoSaveDBAssistDict:
+                playerAssistList = assistMgr.playerNoSaveDBAssistDict[cancelPlayerID]
+                for assistObj in playerAssistList:
+                    if assistObj.MapID == mapID and assistObj.LineID == lineID and assistObj.NPCID == npcID and assistObj.ObjID == objID:
+                        OnCancelPlayerRequestAssist(assistObj, reason, False)
+                        break
+        return
+        
+    # 取消协助Boss
+    elif queryType == "OnCancelBossAssist":
+        mapID, lineID, npcID, objID, reason = queryData
+        assistMgr = PyDataManager.GetPlayerAssistPyManager()
+        if playerID in assistMgr.playerAssistingDict:
+            assistObj = assistMgr.playerAssistingDict[playerID]
+            if assistObj.MapID == mapID and assistObj.LineID == lineID and assistObj.NPCID == npcID and assistObj.ObjID == objID:
+                OnCancelPlayerAssist(curPlayer, playerID, assistObj, reason, False)
+        return
+    
+    ## -------------------------------------------------------------------------------------------
+    
+    # 请求协助组队副本
+    elif queryType == "RequestAssistTeamFB":
+        __DoRequestAssistTeamFB(curPlayer, queryData)
+        return
+    
+    # 开始协助组队副本
+    elif queryType == "OnStartAssistTeamFB":
+        SetPlayerStartAssistTeamFB(curPlayer, queryData)
+        return
+    
+    #QueryPlayerResult_PlayerAssist(curPlayer, queryType, queryData, result)
+    return
+
+#def QueryPlayerResult_PlayerAssist(curPlayer, queryType, queryData, result=[]):
+#    if not curPlayer:
+#        return
+#    resultMsg = str([queryType, queryData, result])
+#    curPlayer.MapServer_QueryPlayerResult(0, 0, "PlayerAssist", resultMsg, len(resultMsg))
+#    GameWorld.DebugLog("协助信息发送 MapServer: playerID=%s,queryType=%s,queryData=%s" % (curPlayer.GetPlayerID(), queryType, queryData))
+#    return
+
+def __DoRequestAssistBoss(curPlayer, queryData):
+    ## 请求协助Boss
+    
+    familyID = curPlayer.GetFamilyID()
+    if not familyID:
+        return
+    mapID, lineID, npcID, objID = queryData
+    
+    playerID = curPlayer.GetPlayerID()
+    
+    assistObj = None
+    addNewAssist = True
+    assistMgr = PyDataManager.GetPlayerAssistPyManager()
+    if playerID in assistMgr.playerNoSaveDBAssistDict:
+        playerAssistList = assistMgr.playerNoSaveDBAssistDict[playerID]
+        for assistObj in playerAssistList:
+            if assistObj.AssistType != AssistType_Boss:
+                continue
+            if npcID != assistObj.NPCID or lineID != assistObj.LineID or objID != assistObj.ObjID:
+                OnCancelPlayerRequestAssist(assistObj, "RequestNewAssistBoss", True)
+            else:
+                addNewAssist = False
+            break
+        
+    if addNewAssist:
+        assistObj = __AddNewAssist(assistMgr, curPlayer, mapID, lineID, npcID, objID)
+        
+    if not assistObj:
+        return
+    
+    # 通知本仙盟玩家
+    PlayerFamily.SendFamilyFakePack(familyID, GetAssistInfoListPack([assistObj]))
+    # 求助信息已发送,请等待盟友支援
+    PlayerControl.NotifyCode(curPlayer, "AssistRequestOK")
+    return
+
+def __DoRequestAssistTeamFB(curPlayer, queryData):
+    ## 请求协助组队副本
+    
+    familyID = curPlayer.GetFamilyID()
+    if not familyID:
+        return
+    mapID, lineID  = queryData
+    
+    playerID = curPlayer.GetPlayerID()
+    
+    assistObj = None
+    addNewAssist = False
+    assistMgr = PyDataManager.GetPlayerAssistPyManager()
+    if playerID in assistMgr.playerNoSaveDBAssistDict:
+        playerAssistList = assistMgr.playerNoSaveDBAssistDict[playerID]
+        for assistObj in playerAssistList:
+            if assistObj.AssistType != AssistType_TeamFB:
+                continue
+            if mapID != assistObj.MapID or lineID != assistObj.LineID:
+                OnCancelPlayerRequestAssist(assistObj, "RequestNewAssistTeamFB", True)
+                addNewAssist = True
+                break
+    else:
+        addNewAssist = True
+        
+    if addNewAssist:
+        assistObj = __AddNewAssist(assistMgr, curPlayer, mapID, lineID)
+        
+    if not assistObj:
+        return
+    
+    # 通知本仙盟玩家
+    PlayerFamily.SendFamilyFakePack(familyID, GetAssistInfoListPack([assistObj]))
+    # 求助信息已发送,请等待盟友支援
+    PlayerControl.NotifyCode(curPlayer, "AssistRequestOK")
+    return
+
+def __AddNewAssist(assistMgr, curPlayer, mapID, lineID, npcID=0, objID=0, exData="", isSaveDB=0):
+    ## 添加新协助请求
+    assistGUID = str(uuid.uuid1())
+    familyID = curPlayer.GetFamilyID()
+    playerID = curPlayer.GetPlayerID()
+    assistObj = PyGameDataStruct.tagDBAssist()
+    assistObj.GUID = assistGUID
+    assistObj.FamilyID = familyID
+    assistObj.PlayerID = playerID
+    assistObj.PlayerName = curPlayer.GetName()
+    assistObj.Job = curPlayer.GetJob()
+    assistObj.LV = curPlayer.GetLV()
+    assistObj.RealmLV = curPlayer.GetOfficialRank()
+    assistObj.MapID = mapID
+    assistObj.LineID = lineID
+    assistObj.NPCID = npcID
+    assistObj.ExData = exData
+    assistObj.ExDataLen = len(exData)
+    
+    # 以下是非DB字段属性
+    OnInitAssistData(assistObj, isSaveDB)
+    assistObj.ObjID = objID
+    
+    #assistMgr = PyDataManager.GetPlayerAssistPyManager()
+    assistMgr.allAssistDict[assistGUID] = assistObj
+    if familyID not in assistMgr.familyAssistDict:
+        assistMgr.familyAssistDict[familyID] = []
+    familyAssistList = assistMgr.familyAssistDict[familyID]
+    familyAssistList.append(assistObj)
+    
+    if not isSaveDB:
+        if playerID not in assistMgr.playerNoSaveDBAssistDict:
+            assistMgr.playerNoSaveDBAssistDict[playerID] = []
+        playerAssistList = assistMgr.playerNoSaveDBAssistDict[playerID]
+        playerAssistList.append(assistObj)
+        
+    GameWorld.DebugLog("    增加新协助请求: familyID=%s,mapID=%s,lineID=%s,npcID=%s,objID=%s,exData=%s,assistGUID=%s" 
+                       % (familyID, mapID, lineID, npcID, objID, exData, assistGUID), playerID)
+    return assistObj
+
+def GetAssistInfoListPack(familyAssistList):
+    ## 协助列表封包数据
+    assistList = []
+    for assistObj in familyAssistList:
+        assistInfo = ChPyNetSendPack.tagGCAssistInfo()
+        assistInfo.AssistGUID = assistObj.GUID
+        assistInfo.PlayerName = assistObj.PlayerName
+        assistInfo.Job = assistObj.Job
+        assistInfo.LV = assistObj.LV
+        assistInfo.RealmLV = assistObj.RealmLV
+        assistInfo.MapID = assistObj.MapID
+        assistInfo.LineID = assistObj.LineID
+        assistInfo.NPCID = assistObj.NPCID
+        assistInfo.ExData = assistObj.ExData
+        assistInfo.ExDataLen = len(assistObj.ExData)
+        assistList.append(assistInfo)
+        
+    infoListPack = ChPyNetSendPack.tagGCAssistInfoList()
+    infoListPack.AssistInfoList = assistList
+    infoListPack.Count = len(assistList)
+    return infoListPack
+
+def OnCancelPlayerRequestAssist(assistObj, reason, isGameServer):
+    '''取消玩家协助 - 发布方
+    MapServer 触发取消
+    1. 攻击了另一只boss
+    2. 重新发布了另一条协助boss/组队副本
+    3. 进入协助状态,即本来在打同一只boss,后面直接改为协助
+    4. 退出了发布协助地图
+    
+    GameServer 触发取消
+    5. 被踢/退出仙盟
+    '''
+    
+    assistGUID = assistObj.GUID
+    playerID = assistObj.PlayerID
+    familyID = assistObj.FamilyID
+    assistType = assistObj.AssistType
+    mapID = assistObj.MapID
+    lineID = assistObj.LineID
+    npcID = assistObj.NPCID
+    objID = assistObj.ObjID
+    
+    GameWorld.DebugLog("取消发布的协助请求: mapID=%s,lineID=%s,npcID=%s,objID=%s,reason=%s,isGameServer=%s, %s" 
+                       % (mapID, lineID, npcID, objID, reason, isGameServer, assistGUID))
+    
+    assistMgr = PyDataManager.GetPlayerAssistPyManager()
+    if assistGUID not in assistMgr.allAssistDict:
+        return
+    assistMgr.allAssistDict.pop(assistGUID)
+    
+    if familyID in assistMgr.familyAssistDict:
+        familyAssistList = assistMgr.familyAssistDict[familyID]
+        if assistObj in familyAssistList:
+            familyAssistList.remove(assistObj)
+            
+    if playerID in assistMgr.playerNoSaveDBAssistDict:
+        playerAssistList = assistMgr.playerNoSaveDBAssistDict[playerID]
+        if assistObj in playerAssistList:
+            playerAssistList.remove(assistObj)
+            
+    SyncFamilyClearAssist(familyID, assistGUID)
+    
+    # 取消boss协助
+    if assistType == AssistType_Boss:
+        
+        # 强制取消正在协助中的玩家
+        playerMgr = GameWorld.GetPlayerManager()
+        for assPlayerID in assistObj.AssistPlayerIDList[::-1]:
+            assistObj.AssistPlayerIDList.remove(assPlayerID)
+            if assPlayerID in assistMgr.playerAssistingDict:
+                assistingObj = assistMgr.playerAssistingDict[assPlayerID]
+                if assistingObj.GUID == assistGUID:
+                    assPlayer = playerMgr.FindPlayerByID(assPlayerID)
+                    OnCancelPlayerAssist(assPlayer, assPlayerID, assistObj, reason, isGameServer, isNotify=False)
+                    
+        # 发布方取消不需要通知地图,对地图发布玩家没有影响,可继续攻击boss
+        
+    # 取消副本协助
+    elif assistType == AssistType_TeamFB:
+        # 暂不需要处理
+        pass
+    
+    return
+
+def OnCancelPlayerAssist(cancelPlayer, cancelPlayerID, assistObj, reason, isGameServer, isNotify=True):
+    '''取消玩家协助 - 协助方
+    MapServer 触发取消
+    1. 攻击了另一只boss
+    2. 退出了当前协助的协助地图
+    
+    GameServer 触发取消
+    3. 重新协助了另一条协助boss/组队副本
+    4. 被踢/退出仙盟
+    5. 主动点取消
+    
+        其他,发布方取消方式
+    6. 发布方取消了发布的协助
+    '''
+    
+    assistGUID = assistObj.GUID
+    tagPlayerID = assistObj.PlayerID
+    mapID = assistObj.MapID
+    lineID = assistObj.LineID
+    npcID = assistObj.NPCID
+    objID = assistObj.ObjID
+    
+    GameWorld.DebugLog("取消协助: tagPlayerID=%s,mapID=%s,lineID=%s,npcID=%s,objID=%s,reason=%s,isGameServer=%s, %s" 
+                       % (tagPlayerID, mapID, lineID, npcID, objID, reason, isGameServer, assistGUID), cancelPlayerID)
+    
+    assistMgr = PyDataManager.GetPlayerAssistPyManager()
+    if cancelPlayerID in assistMgr.playerAssistingDict:
+        assistMgr.playerAssistingDict.pop(cancelPlayerID)
+        
+    if cancelPlayerID in assistObj.AssistPlayerIDList:
+        assistObj.AssistPlayerIDList.remove(cancelPlayerID)
+        
+    if cancelPlayer:
+        PlayerControl.SetAssistTagPlayerID(cancelPlayer, 0)
+        
+    # 取消boss协助
+    if assistObj.AssistType == AssistType_Boss:
+        
+        # GameServer取消的需要通知协助boss地图,删除协助玩家记录
+        if isGameServer:
+            assistData = [mapID, "Cancel", cancelPlayerID, lineID, objID, npcID]
+            GameWorld.SendMapServerMsgEx(ShareDefine.Def_Notify_WorldKey_AssistBoss, assistData)
+            
+        if isNotify and cancelPlayer:
+            tagPlayer = GameWorld.GetPlayerManager().FindPlayerByID(tagPlayerID)
+            if tagPlayer:
+                # xxx取消对你协助
+                PlayerControl.NotifyCode(tagPlayer, "AssistCancel", [cancelPlayer.GetName()])
+                
+    # 取消副本协助
+    elif assistObj.AssistType == AssistType_TeamFB:
+        pass
+    
+    return
+
+#// B0 14 使用协助感谢礼盒 #tagCGUseAssistThanksGift
+#
+#struct    tagCGUseAssistThanksGift
+#
+#{
+#    tagHead        Head;
+#    DWORD        ItemID;
+#    char        GiftGUID[40];    //预览时GUID不发,确认使用时需发送预览返回的GUID
+#};
+def OnUseAssistThanksGift(index, clientData, tick):
+    curPlayer = GameWorld.GetPlayerManager().GetPlayerByIndex(index)
+    itemID = clientData.ItemID
+    giftGUID = clientData.GiftGUID
+    return
+
+#// B0 15 领取协助感谢礼物 #tagCGGetAssistThanksGift
+#
+#struct    tagCGGetAssistThanksGift
+#
+#{
+#    tagHead        Head;
+#    char        GiftGUID[40];    //礼盒GUID
+#};
+def OnGetAssistThanksGift(index, clientData, tick):
+    curPlayer = GameWorld.GetPlayerManager().GetPlayerByIndex(index)
+    giftGUID = clientData.GiftGUID
+    return
+
+def SyncFamilyAssist(curPlayer):
+    ## 同步当前仙盟所有协助请求信息
+    familyID = curPlayer.GetFamilyID()
+    if not familyID:
+        return
+    assistMgr = PyDataManager.GetPlayerAssistPyManager()
+    familyAssistList = assistMgr.familyAssistDict.get(familyID, [])
+    if not familyAssistList:
+        return
+    NetPackCommon.SendFakePack(curPlayer, GetAssistInfoListPack(familyAssistList))
+    return
+
+def SyncFamilyClearAssist(familyID, assistGUID):
+    # 封包通知仙盟清除协助
+    clearPack = ChPyNetSendPack.tagGCClearAssist()
+    clearPack.AssistGUID = assistGUID
+    PlayerFamily.SendFamilyFakePack(familyID, clearPack)
+    return
+
diff --git a/ServerPython/CoreServerGroup/GameServer/Script/Player/PlayerControl.py b/ServerPython/CoreServerGroup/GameServer/Script/Player/PlayerControl.py
index 2176370..60b9741 100644
--- a/ServerPython/CoreServerGroup/GameServer/Script/Player/PlayerControl.py
+++ b/ServerPython/CoreServerGroup/GameServer/Script/Player/PlayerControl.py
@@ -192,6 +192,16 @@
 
 
 #------------------------------------------------------------------------------ 
+## 协助目标玩家ID
+def SetAssistTagPlayerID(curPlayer, value):
+    curPlayer.SetExAttr1(value)
+    SetMapServerPlayerAttrValue(curPlayer, "SetAssistTagPlayerID", value)
+    return
+def GetAssistTagPlayerID(curPlayer): return curPlayer.GetExAttr1()
+
+## 副本功能线路ID
+def SetFBFuncLineID(curPlayer, funcLineID): return curPlayer.SetExAttr3(funcLineID)
+def GetFBFuncLineID(curPlayer): return curPlayer.GetExAttr3()
 
 ##VIP到期时间
 def GetVIPExpireTime(curPlayer): return curPlayer.GetExAttr9()
diff --git a/ServerPython/CoreServerGroup/GameServer/Script/Player/PlayerFamily.py b/ServerPython/CoreServerGroup/GameServer/Script/Player/PlayerFamily.py
index 312fb5d..cb145d1 100644
--- a/ServerPython/CoreServerGroup/GameServer/Script/Player/PlayerFamily.py
+++ b/ServerPython/CoreServerGroup/GameServer/Script/Player/PlayerFamily.py
@@ -41,6 +41,7 @@
 import PlayerViewCache
 import GameWorldBoss
 import AuctionHouse
+import PlayerAssist
 import PlayerTalk
 import PlayerTeam
 
@@ -405,6 +406,8 @@
     PlayerFamilyParty.NotifyFamilyPartyQuestion(jionPlayer)
     #通知守卫人皇信息
     PlayerFamilySWRH.NotifySWRHInfo(jionPlayer, curFamily.GetID())
+    #通知仙盟协助信息
+    PlayerAssist.SyncFamilyAssist(jionPlayer)
     #oss记录加入家族信息
     DataRecordPack.DR_PlayerJoinFamily(jionPlayer, curFamily.GetID(), curFamily.GetName(), curFamily.GetCount())
     return
@@ -608,7 +611,7 @@
     #===============================================================================================
     return
 
-def SendFamilyFakePack(familyID, clientPack):
+def SendFamilyFakePack(familyID, clientPack, excludePlayerIDList=[]):
     ## 广播家族成员PY封包
     family = GameWorld.GetFamilyManager().FindFamily(familyID)
     if not family:
@@ -617,8 +620,11 @@
     for index in xrange(family.GetCount()):
         member = family.GetAt(index)
         memPlayer = member.GetPlayer()
-        if memPlayer:
-            NetPackCommon.SendFakePack(memPlayer, clientPack)
+        if not memPlayer:
+            continue
+        if excludePlayerIDList and memPlayer.GetPlayerID() in excludePlayerIDList:
+            continue
+        NetPackCommon.SendFakePack(memPlayer, clientPack)
     return
 
 def Sync_PyAllFamilyInfo(curPlayer, allPageCnt, viewPage, startIndex, endIndex):
@@ -1465,10 +1471,10 @@
     PlayerFamilyAction.AddFamilyActionNote(tagPlayerName, curFamily.GetID(), ShareDefine.Def_ActionType_FamilyEvent,
                                            [ShareDefine.Def_FamilyActionEvent_MemberChange, ShareDefine.Def_FamilyMemberChange_KickOut], tick)
     #删除玩家
-    curFamily.DeleteMember(tagPlayerID)
-    __DoPlayerLeaveFamilyByID(curFamily, tagPlayerID)
-    
+    curFamily.DeleteMember(tagPlayerID)    
     tagPlayer = playerManager.FindPlayerByID(tagMemberID)
+    
+    __DoPlayerLeaveFamilyByID(curFamily, tagPlayerID, tagPlayer)
     #玩家在线, 设置这个玩家的属性
     PlayerForceLeaveFamily(tagPlayer, tick)
     
@@ -1565,7 +1571,7 @@
     curFamily.DeleteMember(curMember.GetPlayerID())
     #玩家在线, 设置这个玩家的属性
     PlayerForceLeaveFamily(curPlayer, tick)  
-    __DoPlayerLeaveFamilyByID(curFamily, curPlayerID)
+    __DoPlayerLeaveFamilyByID(curFamily, curPlayerID, curPlayer)
 
     DataRecordPack.DR_PlayerLeaveFamily(curPlayer, curFamily.GetID(), curFamily.GetName(), curFamily.GetCount(),
                                         familyLV, curPlayer.GetPlayerID(), curPlayer.GetName(), familyLV, updTime)
@@ -1583,12 +1589,13 @@
 #  @param curFamily 离开的家族
 #  @param leavePlayerID 离开的玩家ID
 #  @return None
-def __DoPlayerLeaveFamilyByID(curFamily, leavePlayerID):
+def __DoPlayerLeaveFamilyByID(curFamily, leavePlayerID, tagPlayer=None):
     PlayerFamilyAction.DelFamilyOfficerModelEquip(curFamily.GetID(), leavePlayerID)
     # 玩家战盟名变更处理
     __OnFamilyNameChange(leavePlayerID, '')
     AddFamilyIDToFightPowerChangeList(curFamily.GetID())
     PlayerViewCache.OnPlayerFamilyChange(leavePlayerID, 0, "")
+    PlayerAssist.OnPlayerLeaveFamily(curFamily.GetID(), leavePlayerID, tagPlayer)
     if leavePlayerID in PyGameData.g_autoViceleaderDict.get(curFamily.GetID(),[]):
         PyGameData.g_autoViceleaderDict[curFamily.GetID()].remove(leavePlayerID)
     return
diff --git a/ServerPython/CoreServerGroup/GameServer/Script/Player/PlayerQuery.py b/ServerPython/CoreServerGroup/GameServer/Script/Player/PlayerQuery.py
index b934983..450a64d 100644
--- a/ServerPython/CoreServerGroup/GameServer/Script/Player/PlayerQuery.py
+++ b/ServerPython/CoreServerGroup/GameServer/Script/Player/PlayerQuery.py
@@ -74,6 +74,7 @@
 import AuctionHouse
 import PlayerFairyDomain
 import GameWorldItem
+import PlayerAssist
 
 import time
 import datetime
@@ -779,6 +780,16 @@
         AuctionHouse.MapServer_AuctionHouseLogic(curPlayer, eval(resultName), tick)
         return
     
+    # 协助
+    if callName == "PlayerAssist":
+        curPlayer = None
+        if srcPlayerID:
+            curPlayer = GameWorld.GetPlayerManager().FindPlayerByID(srcPlayerID)
+            if not curPlayer:
+                return
+        PlayerAssist.MapServer_PlayerAssistLogic(curPlayer, eval(resultName), tick)
+        return
+    
     if callName == "TeamMemFuncData":
         PlayerTeam.MapServer_TeamMemFuncData(srcPlayerID, eval(resultName))
         return
diff --git a/ServerPython/CoreServerGroup/GameServer/Script/Player/PlayerTeam.py b/ServerPython/CoreServerGroup/GameServer/Script/Player/PlayerTeam.py
index 53ff4f7..a511d9f 100644
--- a/ServerPython/CoreServerGroup/GameServer/Script/Player/PlayerTeam.py
+++ b/ServerPython/CoreServerGroup/GameServer/Script/Player/PlayerTeam.py
@@ -590,6 +590,11 @@
         PlayerControl.NotifyCode(curPlayer, "TeamEnterForbid")
         return False
     
+    if PlayerControl.GetAssistTagPlayerID(curPlayer):
+        #协助中无法执行此操作
+        PlayerControl.NotifyCode(curPlayer, "InAssistForbid")
+        return False
+    
     if tagPlayer:
         tagPlayerID = tagPlayer.GetPlayerID()
         #检查ID
@@ -614,6 +619,11 @@
             PlayerControl.NotifyCode(curPlayer, "TeamEnterForbid")
             return False
         
+        if PlayerControl.GetAssistTagPlayerID(tagPlayer):
+            #对方协助中,无法执行此操作
+            PlayerControl.NotifyCode(curPlayer, "TagInAssistForbid")
+            return False
+        
     return True
 
 #===============================================================================
diff --git a/ServerPython/CoreServerGroup/GameServer/Script/PyDataManager.py b/ServerPython/CoreServerGroup/GameServer/Script/PyDataManager.py
index 9f0c1c5..15f575f 100644
--- a/ServerPython/CoreServerGroup/GameServer/Script/PyDataManager.py
+++ b/ServerPython/CoreServerGroup/GameServer/Script/PyDataManager.py
@@ -23,6 +23,7 @@
 import PyGameData
 import CrossRealmPK
 import AuctionHouse
+import PlayerAssist
 import PyGameDataStruct
 import CommFunc
 
@@ -86,8 +87,11 @@
 class PlayerAssistPyManager(object):
     
     def __init__(self):
-        self.playerAssistDict = {} # 所有协助 {GUID:tagDBAssist, ...}
+        self.allAssistDict = {} # 所有协助 {GUID:tagDBAssist, ...}
         self.familyAssistDict = {} # 仙盟协助缓存 {familyID:[tagDBAssist, ...], ...}
+        
+        self.playerNoSaveDBAssistDict = {} # 玩家发布的不存库协助 {playerID:[tagDBAssist, ...], ...}
+        self.playerAssistingDict = {} # 玩家正在协助中的协助,只能存在一条 {playerID:tagDBAssist, ...}
         return
     
     # 保存数据 存数据库和realtimebackup
@@ -96,7 +100,9 @@
         cntData = ""
         cnt = 0
         
-        for dbData in self.playerAssistDict.values():
+        for dbData in self.allAssistDict.values():
+            if not dbData.IsSaveDB:
+                continue
             cnt += 1
             savaData += dbData.getBuffer()
             
@@ -113,7 +119,9 @@
             dbData.clear()
             pos += dbData.readData(datas, pos, dataslen)
             
-            self.playerAssistDict[dbData.GUID] = dbData
+            PlayerAssist.OnInitAssistData(dbData, 1)
+            
+            self.allAssistDict[dbData.GUID] = dbData
             familyID = dbData.FamilyID
             if familyID not in self.familyAssistDict:
                 self.familyAssistDict[familyID] = []
diff --git a/ServerPython/CoreServerGroup/GameServer/Script/ShareDefine.py b/ServerPython/CoreServerGroup/GameServer/Script/ShareDefine.py
index 1c964a5..a520f4d 100644
--- a/ServerPython/CoreServerGroup/GameServer/Script/ShareDefine.py
+++ b/ServerPython/CoreServerGroup/GameServer/Script/ShareDefine.py
@@ -195,6 +195,9 @@
 Def_Notify_WorldKey_RedPacketOutput = 'RedPacketOutput'  # 红包产出信息
 Def_Notify_WorldKey_HurtLog = 'HurtLog'  # 战斗伤害日志
 Def_Notify_WorldKey_FairyDomainLimit = "FairyDomainLimit"  # 缥缈仙域限制事件
+
+Def_Notify_WorldKey_AssistBoss = "AssistBoss"  # 协助boss
+
 #运营活动表名定义
 OperationActionName_ExpRate = "ActExpRate" # 多倍经验活动
 OperationActionName_CostRebate = "ActCostRebate" # 消费返利活动
diff --git a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/PyNetPack.ini b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/PyNetPack.ini
index 0725a6e..e58a32c 100644
--- a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/PyNetPack.ini
+++ b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/PyNetPack.ini
@@ -1422,6 +1422,22 @@
 PacketSubCMD_1=0x25
 PacketCallFunc_1=OnDailyActionBuyCnt
 
+;协助
+[PlayerAssist]
+ScriptName = Player\PlayerAssist.py
+Writer = hxp
+Releaser = hxp
+RegType = 0
+RegisterPackCount = 2
+
+PacketCMD_1=0xB0
+PacketSubCMD_1=0x10
+PacketCallFunc_1=OnRequestAssistBoss
+
+PacketCMD_2=0xB0
+PacketSubCMD_2=0x11
+PacketCallFunc_2=OnRequestAssistTeamFB
+
 ;缥缈仙域
 [PlayerFairyDomain]
 ScriptName = Player\PlayerFairyDomain.py
diff --git a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Attack/AttackLogic/AttackCommon.py b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Attack/AttackLogic/AttackCommon.py
index b164b78..81f8c02 100644
--- a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Attack/AttackLogic/AttackCommon.py
+++ b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Attack/AttackLogic/AttackCommon.py
@@ -55,6 +55,7 @@
 import BuffSkill
 import PlayerState
 import ChPyNetSendPack
+import NPCHurtManager
 import NetPackCommon
 import FamilyRobBoss
 import FBCommon
@@ -699,15 +700,14 @@
 #  @param hurtHP 攻击伤血值
 #  @return None or True
 #  @remarks 函数详细说明.
-def NPCAddObjInHurtList(curObj, curTagObj, curTaglNPCHPBefore, hurtHP) :
+def NPCAddObjInHurtList(curObj, curTagObj, curTaglNPCHPBefore, hurtHP, isBounce=False):
     if curObj == None:
         GameWorld.ErrLog("NPCAddObjInHurtList NoFindObj")
         return
 
     if curTaglNPCHPBefore < hurtHP:
         hurtHP = curTaglNPCHPBefore
-
-    defNPCHurtList = curTagObj.GetPlayerHurtList()
+        
     curObjType = curObj.GetGameObjType()
     if curObjType == IPY_GameWorld.gotPlayer:
         #BossHurtMng.BossAddPlayerInHurtList(curObj, curTagObj, hurtHP)
@@ -716,6 +716,9 @@
             FBLogic.DoFB_Player_HurtNPC(curObj, curTagObj, hurtHP)
         if GameObj.GetHP(curTagObj) == 0:
             curTagObj.SetDict(ChConfig.Def_PlayerKey_LastHurt, curObj.GetPlayerID())
+            
+        if NPCHurtManager.AddHurtValue(curObj, curTagObj, hurtHP, isBounce):
+            return
         
         curTeam = curObj.GetTeam()
         
@@ -780,7 +783,7 @@
         #    return False
         
         #击杀次数判断
-        if not CheckKillNPCByCnt(attacker, defender):
+        if not NPCHurtManager.IsAssistPlayer(attacker.GetPlayerID(), defender) and not CheckKillNPCByCnt(attacker, defender):
             return False
         
         #仙盟归属NPC判断
@@ -794,7 +797,7 @@
         #    return False
         
         #击杀次数判断
-        if not CheckKillNPCByCnt(defender, attacker, False):
+        if not CheckKillNPCByCnt(defender, attacker, False) and not NPCHurtManager.IsAssistPlayer(defender.GetPlayerID(), attacker):
             return False
         
         #仙盟归属NPC判断
@@ -806,8 +809,10 @@
         if PetControl.IsPet(attacker) or attacker.GetGameNPCObjType()== IPY_GameWorld.gnotSummon:
             #击杀次数判断
             if not CheckKillNPCByCnt(attacker, defender, False):
-                return False
-            
+                ownerPlayer = GetAttackPlayer(attacker)[0]
+                if ownerPlayer and not NPCHurtManager.IsAssistPlayer(ownerPlayer.GetPlayerID(), defender):
+                    return False
+                
             #仙盟归属NPC判断
             if not CheckCanAttackFamilyOwnerNPC(attacker, defender, False):
                 return False
@@ -2464,7 +2469,7 @@
     #    GameWorld.DebugLog("不能攻击,不反弹")
     #    return
     #杀怪次数判断
-    if not CheckKillNPCByCnt(defObj, atkObj, False):
+    if not CheckKillNPCByCnt(defObj, atkObj, False) and not NPCHurtManager.IsAssistPlayer(defObj.GetPlayerID(), atkObj):
         #GameWorld.DebugLog("不能攻击,不反弹")
         return
     
diff --git a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Attack/AttackLogic/NormalNPC_Attack_Player.py b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Attack/AttackLogic/NormalNPC_Attack_Player.py
index d3dee81..ebb1910 100644
--- a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Attack/AttackLogic/NormalNPC_Attack_Player.py
+++ b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Attack/AttackLogic/NormalNPC_Attack_Player.py
@@ -87,7 +87,7 @@
     lastHP = curNPCBeHP - GameObj.GetHP(curNormalNPC)
     if lastHP > 0 :
         #添加伤血列表
-        AttackCommon.NPCAddObjInHurtList(curTagPlayer, curNormalNPC, curNPCBeHP, lastHP)
+        AttackCommon.NPCAddObjInHurtList(curTagPlayer, curNormalNPC, curNPCBeHP, lastHP, True)
     #给这个玩家的召唤兽增加仇恨
     AttackCommon.SummonAddAngryByOwner(curNormalNPC, curTagPlayer, hurtHP)
     #添加技能伤害通知列表,(用于攻击结束,统一通知客户端)
diff --git a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/ChConfig.py b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/ChConfig.py
index 04f263d..90856b2 100644
--- a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/ChConfig.py
+++ b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/ChConfig.py
@@ -2220,6 +2220,7 @@
                          1000 * 15,                        # 渡劫鼓舞间隔
                          1000 * 1,                        # vip体验时效
                          1000 * 1,                        # 限时抢购
+                         1000 * 5,                        # 请求协助间隔
                          ]
 TYPE_Player_Tick_Count = len(TYPE_Player_Tick_Time) 
 
@@ -2293,6 +2294,7 @@
 TYPE_Player_Tick_DuJieInspire,    # 渡劫鼓舞间隔
 TYPE_Player_Tick_VIPExperience,        #vip体验时效
 TYPE_Player_Tick_FlashSale,        #限时抢购
+TYPE_Player_Tick_RequestAssist,        #请求协助间隔
 ) = range(0, TYPE_Player_Tick_Count)
 
 #---------------------------------------------------------------------
diff --git a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/GM/Commands/PrintNPCHurt.py b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/GM/Commands/PrintNPCHurt.py
index fb307e5..78be798 100644
--- a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/GM/Commands/PrintNPCHurt.py
+++ b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/GM/Commands/PrintNPCHurt.py
@@ -19,6 +19,8 @@
 import FamilyRobBoss
 import NPCCommon
 import ChConfig
+import NPCHurtManager
+import PlayerTeam
 
 ##查看点选的NPC仇恨列表
 # @param curPlayer 玩家实例
@@ -38,19 +40,29 @@
         GameWorld.DebugAnswer(curPlayer, "objID(%s) 错误 找不到对应NPC" % objID)
         return
     
-    GameWorld.DebugAnswer(curPlayer, "---%s,ID=%s,team=%s,family=%s" 
-                          % (GameWorld.GetGameWorld().GetTick()%1000, curPlayer.GetPlayerID(), curPlayer.GetTeamID(), curPlayer.GetFamilyID()))
+    GameWorld.DebugAnswer(curPlayer, "---------------- %s" % (GameWorld.GetGameWorld().GetTick() % 1000))
+    GameWorld.DebugAnswer(curPlayer, "ID=%s,team=%s,family=%s" % (curPlayer.GetPlayerID(), curPlayer.GetTeamID(), curPlayer.GetFamilyID()))
     
     # 归属仙盟的,取仙盟伤血统计
     if NPCCommon.GetDropOwnerType(curNPC) == ChConfig.DropOwnerType_Family:
         FamilyRobBoss.OnGMPrintFamilyOwnerBossHurt(curPlayer, curNPC)
         return
     
-    npcHurtList = curNPC.GetPlayerHurtList()
+    isPyHurtList = True
+    npcHurtList = NPCHurtManager.GetPlayerHurtList(curNPC)
+    if not npcHurtList:
+        npcHurtList = curNPC.GetPlayerHurtList()
+        isPyHurtList = False
     if isSort:
         npcHurtList.Sort()  #sort以后伤血列表从大到小排序
         
     GameWorld.DebugAnswer(curPlayer, "ID=%s, NPCID=%s, 伤血数=%s, isSort=%s" % (curNPC.GetID(), curNPC.GetNPCID(), npcHurtList.GetHurtCount(), isSort))
+    if isPyHurtList:
+        for playerID, assistPlayerIDList in npcHurtList.GetNoAssitPlayerIDDict().items():
+            GameWorld.DebugAnswer(curPlayer, "玩家:%s, 协助玩家:%s" % (playerID, assistPlayerIDList))
+        for assistPlayerID, tagPlayerID in npcHurtList.GetAssistPlayerIDDict().items():
+            GameWorld.DebugAnswer(curPlayer, "协助玩家:%s, 目标:%s" % (assistPlayerID, tagPlayerID))
+            
     for index in xrange(npcHurtList.GetHurtCount()):
         #获得伤血对象
         hurtObj = npcHurtList.GetHurtAt(index)
@@ -71,6 +83,12 @@
                 d = GameWorld.GetDist(curNPC.GetPosX(), curNPC.GetPosY(), hurtPlayer.GetPosX(), hurtPlayer.GetPosY())
                 GameWorld.DebugAnswer(curPlayer, "%s 玩家ID=%s,距离=%s, V=%s" % (index, hurtID, d, hurtValue))
                 
+            if isPyHurtList:
+                playerHurtValue, assistPlayerHurtDict = npcHurtList.GetPlayerHurtDetail(hurtID)
+                GameWorld.DebugAnswer(curPlayer, "    非协id=%s,v=%s" % (hurtID, playerHurtValue))
+                for assistPlayerID, assistHurtValue in assistPlayerHurtDict.items():
+                    GameWorld.DebugAnswer(curPlayer, "        协助id=%s,v=%s" % (assistPlayerID, assistHurtValue))
+                    
         elif hurtType == ChConfig.Def_NPCHurtTypeTeam:
             #获得当前队伍
             curTeam = GameWorld.GetTeamManager().FindTeam(hurtID)
@@ -79,6 +97,16 @@
             else:
                 GameWorld.DebugAnswer(curPlayer, "%s 队伍ID=%s,人数=%s, V=%s" % (index, hurtID, curTeam.GetMemberCount(), hurtValue))
                 
+            if isPyHurtList:
+                mapTeamPlayerIDList = PlayerTeam.GetMapTeamPlayerIDList(hurtID)
+                for teamPlayerID in mapTeamPlayerIDList:
+                    if not npcHurtList.IsNoAssistPlayer(teamPlayerID):
+                        continue
+                    playerHurtValue, assistPlayerHurtDict = npcHurtList.GetPlayerHurtDetail(teamPlayerID)
+                    GameWorld.DebugAnswer(curPlayer, "    非协id=%s,v=%s" % (teamPlayerID, playerHurtValue))
+                    for assistPlayerID, assistHurtValue in assistPlayerHurtDict.items():
+                        GameWorld.DebugAnswer(curPlayer, "        协助id=%s,v=%s" % (assistPlayerID, assistHurtValue))
+                        
         elif hurtType == ChConfig.Def_NPCHurtTypeNPC:
             GameWorld.DebugAnswer(curPlayer, "%s NPC=%s, V=%s" % (index, hurtID, hurtValue))
             
diff --git a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/GM/Commands/SetWorldPos.py b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/GM/Commands/SetWorldPos.py
index 86d3fc1..453fcbe 100644
--- a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/GM/Commands/SetWorldPos.py
+++ b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/GM/Commands/SetWorldPos.py
@@ -22,6 +22,9 @@
 #  @remarks 函数详细说明.
 def OnExec(curPlayer, paramList):
     #输入命令格式错误
+    if not paramList:
+        GameWorld.DebugAnswer(curPlayer, "SetWorldPos mapID posX posY")
+        return
     mapID, posX, posY = paramList[:3]
     lineID = paramList[3] if len(paramList) > 3 else - 1
     if not mapID:
diff --git a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/NPC/NPCCommon.py b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/NPC/NPCCommon.py
index 39c9ca2..3c88207 100644
--- a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/NPC/NPCCommon.py
+++ b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/NPC/NPCCommon.py
@@ -53,6 +53,7 @@
 import PlayerNewFairyCeremony
 import GameLogic_CrossGrassland
 import PlayerWeekParty
+import NPCHurtManager
 import PlayerActLogin
 import FamilyRobBoss
 import IpyGameDataPY
@@ -2271,6 +2272,7 @@
     AttackCommon.ClearTeamPlayerHurtValue(curNPC)
     # 清除自定义伤血列表
     #BossHurtMng.ClearHurtValueList(curNPC)
+    NPCHurtManager.DeletePlayerHurtList(curNPC)
     if curNPC.GetType() == ChConfig.ntRobot:
         lineID = GameWorld.GetGameWorld().GetLineID()
         lineRobotJobDict = PyGameData.g_fbRobotJobDict.get(lineID, {})
@@ -2393,7 +2395,6 @@
     #  @remarks 类初始化
     def __init__(self, iNPC):
         self.__Instance = iNPC
-        self.__MaxHurtPlayer = None  # 最大伤血者,队伍取队长
         self.__LastHurtPlayer = None    # 最后一击的玩家
         self.__Killer = None # 击杀者, 由各种规则得出, 一般也是物品归属的代表, 用于广播、记录等确保与归属一致
         self.__AllKillerDict = {} # 所有击杀的玩家ID对应字典, 非队伍, 一般也是归属的拥有者
@@ -3198,231 +3199,6 @@
         npcHurtList.Clear()
         return True
     
-    def __IsBossHurtOfflineProtect(self, refreshPoint, playerID, tick):
-        ## boss伤血是否掉线保护中
-        # 是否离线超过3分钟,下线坐标是否不在boss区域等
-        leaveTick = PlayerControl.GetPlayerLeaveServerTick(playerID)
-        leavePos = PlayerControl.GetPlayerLeaveServerPos(playerID)
-        if not leaveTick or not leavePos:
-            GameWorld.DebugLog("玩家不在本地图或已长久离线!清除该伤血!playerID=%s" % playerID)
-            return False
-        
-        if tick - leaveTick > ChConfig.Def_PlayerOfflineProtectTime:
-            GameWorld.DebugLog("本地图离线玩家超过保护时长!清除该伤血!playerID=%s,tick=%s,leaveTick=%s" % (playerID, tick, leaveTick))
-            return False
-        
-        if not self.GetIsInRefreshPoint(leavePos[0], leavePos[1], refreshPoint):
-            GameWorld.DebugLog("本地图离线玩家不在保护区域内!playerID=%s,leavePos=%s" % (playerID, leavePos))
-            return False
-        
-        return True
-    
-    def __IsClearPlayerHurt(self, hurtPlayer, refreshPoint, hurtID, tick):
-        if hurtPlayer == None:
-            if not self.__IsBossHurtOfflineProtect(refreshPoint, hurtID, tick):
-                return True
-            
-            #GameWorld.DebugLog("本地图离线玩家伤血值保护中!playerID=%s" % (hurtID))
-            return False
-                                        
-        #if hurtPlayer.GetHP() <= 0:
-        #    GameWorld.DebugLog("伤血玩家血量为0,清除该伤血!playerID=%s" % hurtID)
-        #    return True
-        
-        curNPC = self.__Instance
-        if hurtPlayer.GetInitOK() and (not hurtPlayer.GetVisible() or hurtPlayer.GetSightLevel() != curNPC.GetSightLevel()):
-            GameWorld.DebugLog("伤血玩家不可见,清除该伤血!playerID=%s" % hurtID)
-            return True
-        
-        if not self.GetIsInRefreshPoint(hurtPlayer.GetPosX(), hurtPlayer.GetPosY(), refreshPoint):
-            GameWorld.DebugLog("伤血玩家不在boss范围里,清除该伤血!playerID=%s" % hurtID)
-            return True
-        
-        if hurtPlayer.GetTeamID():
-            # 这种情况一般是玩家未加入队伍前对该NPC有伤血,加入到某个队伍后,将该伤害转移到队伍中
-            return True
-        
-        if hurtPlayer.GetHP() <= 0 or hurtPlayer.GetPlayerAction() == IPY_GameWorld.paDie:
-            deadTime = hurtPlayer.NomalDictGetProperty(ChConfig.Def_Player_Dict_DeadTime)
-            if time.time() - deadTime >= IpyGameDataPY.GetFuncCfg("BossHurtValue", 1):
-                #GameWorld.DebugLog("伤血玩家死亡超过伤血保护时长,清除该伤血!playerID=%s" % hurtID)
-                return True
-            
-        #GameWorld.DebugLog("正常玩家伤血保护中!playerID=%s" % hurtID)
-        return False
-    
-    def __GetTeamHurtNPCPlayerIDList(self, refreshPoint, teamID, tick):
-        
-        curNPC = self.__Instance
-        teamHurtPlayerIDList = []
-        # 如果没有在线队员在有效范围内,则进一步判断离线队员是否有伤血保护中的
-        playerMgr = GameWorld.GetPlayerManager()
-        copyMapID = GameWorld.GetGameWorld().GetCopyMapID()
-        mapTeamPlayerIDList = PlayerTeam.GetMapTeamPlayerIDList(teamID) # 因为需要判断离线队员,所以只能用含离线的队伍缓存
-        for playerID in mapTeamPlayerIDList:
-            
-            curTeamPlayer = playerMgr.FindPlayerByID(playerID)
-            if curTeamPlayer:
-                if curTeamPlayer.GetCopyMapID() != copyMapID:
-                    #GameWorld.DebugLog("队员不在本线路,不计!playerID=%s" % playerID)
-                    continue
-                
-                if curTeamPlayer.GetInitOK() and (not curTeamPlayer.GetVisible() or curTeamPlayer.GetSightLevel() != curNPC.GetSightLevel()):
-                    #GameWorld.DebugLog("队员不可见,不计!playerID=%s" % playerID)
-                    continue
-                
-                if curTeamPlayer.GetHP() <= 0 or curTeamPlayer.GetPlayerAction() == IPY_GameWorld.paDie:
-                    deadTime = curTeamPlayer.NomalDictGetProperty(ChConfig.Def_Player_Dict_DeadTime)
-                    if time.time() - deadTime >= IpyGameDataPY.GetFuncCfg("BossHurtValue", 1):
-                        #GameWorld.DebugLog("伤血队员死亡超过伤血保护时长,不计!playerID=%s" % playerID)
-                        continue
-                    
-                #if curTeamPlayer.GetHP() > 0 and self.GetIsInRefreshPoint(curTeamPlayer.GetPosX(), curTeamPlayer.GetPosY(), refreshPoint):
-                if self.GetIsInRefreshPoint(curTeamPlayer.GetPosX(), curTeamPlayer.GetPosY(), refreshPoint) \
-                    and AttackCommon.CheckKillNPCByCnt(curTeamPlayer, curNPC, False):
-                    #GameWorld.DebugLog("有队员在boss范围内,保留队伍伤血!teamID=%s,playerID=%s" % (teamID, curTeamPlayer.GetPlayerID()))
-                    teamHurtPlayerIDList.append(playerID)
-            else:
-                if self.__IsBossHurtOfflineProtect(refreshPoint, playerID, tick):
-                    #GameWorld.DebugLog("有队员对boss离线伤血保护中!teamID=%s,playerID=%s" % (teamID, playerID))
-                    teamHurtPlayerIDList.append(playerID)
-                    
-        if not teamHurtPlayerIDList:
-            GameWorld.DebugLog("伤血队伍没有符合条件的队员在boss区域内,清除该伤血!teamID=%s,mapTeamPlayerIDList=%s" % (teamID, mapTeamPlayerIDList))
-        return teamHurtPlayerIDList
-    
-    def RefreshHurtList(self, tick, refreshInterval=3000):
-        ## 刷新伤血列表
-        # @return: 可攻击的最大伤血数值对象 IPY_PlayerHurtValue
-        
-        curNPC = self.__Instance
-        if not curNPC.GetIsBoss() or GetDropOwnerType(curNPC) != ChConfig.DropOwnerType_MaxHurt:
-            return
-        
-        npcHurtList = curNPC.GetPlayerHurtList()
-        if tick - curNPC.GetDictByKey(ChConfig.Def_NPC_Dict_LastRefreshHurtTick) < (refreshInterval):
-            if npcHurtList.GetHurtCount():
-                return self.__GetAtkObjByHurtList(npcHurtList)
-            return
-        curNPC.SetDict(ChConfig.Def_NPC_Dict_LastRefreshHurtTick, tick)
-        
-        hurtPlayerDict = {} # {playerID:teamID, ...}
-        refreshPoint = curNPC.GetRefreshPosAt(curNPC.GetCurRefreshPointIndex())
-        hurtCount = npcHurtList.GetHurtCount()
-        isInHurt = 0
-        for index in xrange(hurtCount):
-            #获得伤血对象
-            hurtObj = npcHurtList.GetHurtAt(index)
-            hurtType = hurtObj.GetValueType()
-            hurtID = hurtObj.GetValueID()
-            hurtValue = hurtObj.GetHurtValue()
-            if not hurtID:
-                continue
-            
-            # Clear()只会把伤血类型及数值重置为0,不会清除该伤血数值对象
-            
-            if hurtType == ChConfig.Def_NPCHurtTypePlayer:
-                hurtPlayer = GameWorld.GetObj(hurtID, IPY_GameWorld.gotPlayer)
-                if self.__IsClearPlayerHurt(hurtPlayer, refreshPoint, hurtID, tick):
-                    hurtObj.Clear()
-                    teamID = 0 if not hurtPlayer else hurtPlayer.GetTeamID()
-                    if teamID:
-                        GameWorld.DebugLog("加入队伍,之气的伤血转移到队伍中!playerID=%s,teamID=%s" % (hurtID, teamID))
-                        AttackCommon.AddHurtValue(curNPC, teamID, ChConfig.Def_NPCHurtTypeTeam, hurtValue)
-                else:
-                    isInHurt = 1
-                    hurtPlayerDict[hurtID] = 0
-                    
-            elif hurtType == ChConfig.Def_NPCHurtTypeTeam:
-                teamHurtPlayerIDList = self.__GetTeamHurtNPCPlayerIDList(refreshPoint, hurtID, tick)
-                if not teamHurtPlayerIDList:
-                    hurtObj.Clear()
-                else:
-                    isInHurt = 1
-                    for tPlayerID in teamHurtPlayerIDList:
-                        hurtPlayerDict[tPlayerID] = hurtID
-                        
-        mapID = GameWorld.GetMap().GetMapID()
-        if IsMapNeedBossShunt(mapID):
-            self.__UpdBossShuntInfo(mapID, hurtPlayerDict, tick)
-        
-        #GameWorld.DebugLog("RefreshHurtList, hurtCount=%s,isInHurt=%s" % (hurtCount, isInHurt))
-        npcHurtList.Sort()
-        curNPC.SetDict(ChConfig.Def_NPC_Dict_InHurtProtect, isInHurt)
-        
-        if hurtCount:
-            # 排序后的,第一个可攻击的最大伤血对象
-            return self.__GetAtkObjByHurtList(npcHurtList)
-        return
-    
-    def __UpdBossShuntInfo(self, mapID, hurtPlayerDict, tick):
-        ## 更新本地图线路boss分流信息
-        curNPC = self.__Instance
-        npcID = curNPC.GetNPCID()
-        lineID = GameWorld.GetGameWorld().GetLineID()
-        key = (mapID, lineID)
-        shuntPlayerDict = PyGameData.g_bossShuntPlayerInfo.get(key, {})
-        
-        shuntChange = False
-        for playerID, shuntInfo in shuntPlayerDict.items():
-            bossID, teamID, relatedTick = shuntInfo
-            if bossID != npcID:
-                # 不是该boss的伤害不处理
-                continue
-            
-            # 还在伤血中
-            if playerID in hurtPlayerDict:
-                newTeamID = hurtPlayerDict[playerID]
-                if newTeamID != teamID:
-                    shuntPlayerDict[playerID] = [npcID, newTeamID, 0]
-                    shuntChange = True
-                    GameWorld.DebugLog("boss分流 -> 玩家对该boss的伤害变更队伍!playerID=%s,npcID=%s,teamID=%s,newTeamID=%s" 
-                                       % (playerID, npcID, teamID, newTeamID), lineID)
-                elif relatedTick:
-                    shuntPlayerDict[playerID] = [npcID, newTeamID, 0]
-                    shuntChange = True
-                    GameWorld.DebugLog("boss分流 -> 玩家对该boss的关联状态转为伤害状态!playerID=%s,npcID=%s,teamID=%s,newTeamID=%s" 
-                                       % (playerID, npcID, teamID, newTeamID), lineID)
-                    
-            # 不在伤血中,更新关联tick
-            elif not relatedTick:
-                shuntPlayerDict[playerID] = [npcID, teamID, tick]
-                shuntChange = True
-                GameWorld.DebugLog("boss分流 -> 玩家不在该boss伤血中,设置为关联状态!playerID=%s,npcID=%s,teamID=%s,tick=%s" 
-                                   % (playerID, npcID, teamID, tick), lineID)
-                
-        # 伤先优先级最高,可直接覆盖更新
-        for playerID, teamID in hurtPlayerDict.items():
-            if playerID not in shuntPlayerDict:
-                shuntPlayerDict[playerID] = [npcID, teamID, 0]
-                shuntChange = True
-                GameWorld.DebugLog("boss分流 -> 新增玩家对boss伤害!playerID=%s,npcID=%s,teamID=%s" % (playerID, npcID, teamID), lineID)
-                
-            elif shuntPlayerDict[playerID][0] != npcID:
-                shuntPlayerDict[playerID] = [npcID, teamID, 0]
-                shuntChange = True
-                GameWorld.DebugLog("boss分流 -> 伤害转移到本boss上!playerID=%s,npcID=%s,teamID=%s" % (playerID, npcID, teamID), lineID)
-                
-        if shuntChange:
-            PyGameData.g_bossShuntPlayerInfo[key] = shuntPlayerDict
-            GameServer_WorldBossShuntInfo(mapID, lineID)
-        return
-    
-    def __GetAtkObjByHurtList(self, npcHurtList):
-        '''第一个可攻击的最大伤血对象,也是实际的归属者或队伍
-        因为玩家伤血掉线、死亡有一定时间的保留机制,故最大伤血不一定是可攻击目标(归属者)
-        注意: 该规则必须与最终算归属的规则一致,不然可能导致归属错乱
-        '''
-        for index in xrange(npcHurtList.GetHurtCount()):
-            #获得伤血对象
-            hurtObj = npcHurtList.GetHurtAt(index)
-            
-            curPlayer, curTeam = self.__GetTagByHurtObj(hurtObj, True)
-            
-            if curPlayer or curTeam:
-                return hurtObj
-        return
-    
     def IsInHurtProtect(self):
         '''NPC是否伤血保护中
         因为手游比较会出现网络切换的情况,此时会可能会引起掉线重连
@@ -3457,6 +3233,8 @@
         self.ClearNPCHurtList()
         #清除所有身上buff
         self.ClearAllBuff(isClearAuraBuff)
+        curNPC = self.__Instance
+        NPCHurtManager.ClearPlayerHurtList(curNPC)
         return True
     
     #---------------------------------------------------------------------
@@ -4234,7 +4012,9 @@
         curNPC = self.__Instance
         self.__FeelPlayerList = []
         
-        npcHurtList = curNPC.GetPlayerHurtList()
+        npcHurtList = NPCHurtManager.GetPlayerHurtList(curNPC)
+        if not npcHurtList:
+            npcHurtList = curNPC.GetPlayerHurtList()
         #npcHurtList.Sort()  #这里不排序,只要有伤害就算
         
         eventPlayerList = []
@@ -4672,7 +4452,6 @@
         #objID = curNPC.GetID()
         npcID = curNPC.GetNPCID()
         self.__LastHurtPlayer = self.__FindLastTimeHurtObjEx()
-        self.__MaxHurtPlayer = self.__FindBossMaxHurtObj() # py自定义伤血所得到的Boss最大伤血玩家
         
         isGameBoss = ChConfig.IsGameBoss(curNPC)
         self.__AllKillerDict, curTeam, hurtType, hurtID = self.__FindNPCKillerInfo(isGameBoss)
@@ -4691,8 +4470,6 @@
             for curPlayer in self.__AllKillerDict.values():
                 if not self.__LastHurtPlayer:
                     self.__LastHurtPlayer = curPlayer
-                if not self.__MaxHurtPlayer:
-                    self.__MaxHurtPlayer = curPlayer
                 if not self.__Killer:
                     self.__Killer = curPlayer
                     
@@ -4736,12 +4513,7 @@
                 
         if OnNPCDie:
             OnNPCDie(curNPC, hurtType, hurtID)
-        
-        #执行玩家连轧逻辑
-        lastTimeHurtObj = self.__FindLastTimeHurtObj()
-        if lastTimeHurtObj[0] == None and lastTimeHurtObj[1] == None:
-            return
-         
+            
         return
     
     ## 最后一击处理
@@ -4757,35 +4529,6 @@
             return
         
         curNPC = self.__Instance
-        #npcID = curNPC.GetNPCID()
-        #mapID = GameWorld.GetMap().GetMapID()
-        #playerName = lastHurtPlayer.GetPlayerName()
-                
-        #===========================================================================================
-        # # 最后一击奖励
-        # lastTimeHurtBossAwardDict = ReadChConfig.GetEvalChConfig("LastTimeHurtBossAward")
-        # if npcID in lastTimeHurtBossAwardDict:
-        #    giveItemList = lastTimeHurtBossAwardDict[npcID]
-        #    playerID = lastHurtPlayer.GetPlayerID()
-        #    bossAwardMailInfo = ReadChConfig.GetEvalChConfig("LastTimeHurtBossAwardMail")
-        #    title, content, getDays = bossAwardMailInfo
-        #    mailContent = content % (npcID)
-        #    PlayerControl.SendMail(title, mailContent, getDays, [playerID], giveItemList)
-        #    
-        #    # 广播
-        #    if len(giveItemList) > 0:
-        #        itemID = giveItemList[0][0] # 默认取第一个奖励物品广播
-        #        PlayerControl.WorldNotify(0, "GeRen_liubo_698214", [playerName, mapID, npcID, itemID, itemID])
-        #===========================================================================================
-            
-        #===========================================================================================
-        # # 最后一击广播
-        # LastHurtNotifyDict = ReadChConfig.GetEvalChConfig("LastHurtNotify")
-        # for npcIDTuple, notifyMark in LastHurtNotifyDict.items():
-        #    if npcID in npcIDTuple:
-        #        PlayerControl.WorldNotify(0, notifyMark, [playerName, mapID, npcID])
-        #        break
-        #===========================================================================================
         
         # VIP杀怪加攻
         PlayerVip.DoAddVIPKillLVExp(lastHurtPlayer, curNPC)
@@ -4851,14 +4594,6 @@
                     if isGameBoss:
                         GameWorld.Log("    归属最大伤血队伍: npcID=%s,dropOwnerType=%s,teamID=%s" % (npcID, dropOwnerType, curTeam.GetTeamID()))
                     return killerDict, curTeam, ChConfig.Def_NPCHurtTypeTeam, curTeam.GetTeamID()
-        # 最大伤血玩家 - 伤血不会被重置
-        elif dropOwnerType == ChConfig.DropOwnerType_MaxHurtPlayer:
-            if self.__MaxHurtPlayer:
-                maxHurtPlayerID = self.__MaxHurtPlayer.GetPlayerID()
-                if maxHurtPlayerID not in killerDict:
-                    killerDict[maxHurtPlayerID] = self.__MaxHurtPlayer
-                    #GameWorld.DebugLog("    特殊归属最大伤害玩家: playerID=%s" % maxHurtPlayerID)
-                    return killerDict, None, ChConfig.Def_NPCHurtTypePlayer, maxHurtPlayerID
                 
         # 最大仇恨
         elif dropOwnerType == ChConfig.DropOwnerType_MaxAngry:
@@ -4946,20 +4681,7 @@
         return curTag, GameWorld.GetTeamManager().FindTeam(teamID)
     
     #---------------------------------------------------------------------
-    ## 获取补刀者,包含队伍
-    #  @param self 类实例
-    #  @return 返回值 玩家或者None
-    #  @remarks 
-    def __FindLastTimeHurtObj(self):
-        curNPC = self.__Instance
-        npcHurtList = curNPC.GetPlayerHurtList()
-        if npcHurtList.GetHurtCount() <= 0:
-            return (None, None)
-        
-        maxHurtObj = npcHurtList.GetLastTimeHurtValue()
-        return self.__GetTagByHurtObj(maxHurtObj)
-
-
+    
     ## 获取补刀者(这个绝对是玩家)
     #  @param self 类实例
     #  @return 返回值 玩家或者None
@@ -4968,17 +4690,6 @@
         playerID = curNPC.GetDictByKey(ChConfig.Def_PlayerKey_LastHurt)
         
         curPlayer = GameWorld.GetPlayerManager().FindPlayerByID(playerID)
-        if not curPlayer:
-            return None
-        
-        return curPlayer
-    
-    def __FindBossMaxHurtObj(self):
-        # py自定义伤血所得到的Boss最大伤血玩家
-        curNPC = self.__Instance
-        maxHurtID = curNPC.GetDictByKey(ChConfig.Def_NPC_Dict_BossMaxHurtID % (curNPC.GetID(), curNPC.GetNPCID()))
-        
-        curPlayer = GameWorld.GetPlayerManager().FindPlayerByID(maxHurtID)
         if not curPlayer:
             return None
         
@@ -5137,8 +4848,6 @@
         
         if not self.__LastHurtPlayer:
             self.__LastHurtPlayer = playerlist[0]
-        if not self.__MaxHurtPlayer:
-            self.__MaxHurtPlayer = playerlist[0]
         if not self.__Killer:
             self.__Killer = playerlist[0]
         maxHurtID = playerlist[0].GetPlayerID()
@@ -5217,8 +4926,6 @@
         # 赶时间,先简单处理直接取最大等级的,之后可按实际情况来
         if not self.__LastHurtPlayer:
             self.__LastHurtPlayer = dropPlayer
-        if not self.__MaxHurtPlayer:
-            self.__MaxHurtPlayer = dropPlayer
         if not self.__Killer:
             self.__Killer = dropPlayer
         maxHurtID = dropPlayer.GetPlayerID()
@@ -5441,14 +5148,10 @@
             GameWorld.Log("Boss死亡: lineID=%s,objID=%s,npcID=%s,dropOwnerType=%s" 
                           % (GameWorld.GetGameWorld().GetLineID(), curNPC.GetID(), curNPC.GetNPCID(), dropOwnerType))
         if dropOwnerType == ChConfig.DropOwnerType_MaxHurt:
-            maxHurtObj = self.RefreshHurtList(tick, refreshInterval)
-            if maxHurtObj:
-                ownerType, ownerID = maxHurtObj.GetValueType(), maxHurtObj.GetValueID()
-                if ownerType == ChConfig.Def_NPCHurtTypeTeam:
-                    tagObj = self.__GetMaxHurtTeamPlayer(ownerID, isDead)
-                elif ownerType == ChConfig.Def_NPCHurtTypePlayer:
-                    tagObj = GameWorld.GetObj(ownerID, IPY_GameWorld.gotPlayer)
-                    
+            maxHurtInfo = NPCHurtManager.RefreshHurtList(curNPC, tick, refreshInterval, isDead)
+            if maxHurtInfo:
+                tagObj, ownerType, ownerID = maxHurtInfo
+                
         elif dropOwnerType == ChConfig.DropOwnerType_Family:
             ownerInfo = FamilyRobBoss.RefreshFamilyOwnerNPCHurt(self, curNPC, tick, refreshInterval)
             if ownerInfo:
@@ -5576,16 +5279,20 @@
         if isDead:
             GameWorld.Log("Boss归属: key=%s,ownerType=%s,ownerID=%s" % (key, ownerType, ownerID))
             
+        hurtList = NPCHurtManager.GetPlayerHurtList(curNPC)
         # 刷新归属
         if ownerType == ChConfig.Def_NPCHurtTypePlayer:
             curPlayer = GameWorld.GetObj(ownerID, IPY_GameWorld.gotPlayer)
             if curPlayer:
                 playerID = curPlayer.GetPlayerID()
-                hurtType, hurtID = ChConfig.Def_NPCHurtTypePlayer, playerID
-                killerDict[playerID] = curPlayer
-                self.__AddDropOwnerPlayerBuff(curPlayer, tick)
-                if dropOwnerType == ChConfig.DropOwnerType_Contend:
-                    curPlayer.SetDict(ChConfig.Def_PlayerKey_ContendNPCObjID, curNPC.GetID())
+                if not hurtList or hurtList.HaveHurtValue(playerID):
+                    hurtType, hurtID = ChConfig.Def_NPCHurtTypePlayer, playerID
+                    killerDict[playerID] = curPlayer
+                    self.__AddDropOwnerPlayerBuff(curPlayer, tick)
+                    if dropOwnerType == ChConfig.DropOwnerType_Contend:
+                        curPlayer.SetDict(ChConfig.Def_PlayerKey_ContendNPCObjID, curNPC.GetID())
+                else:
+                    BuffSkill.DelBuffBySkillID(curPlayer, ChConfig.Def_SkillID_DropOwnerBuff, tick, buffOwner=curNPC)
                     
         elif ownerType == ChConfig.Def_NPCHurtTypeTeam:
             curTeam = GameWorld.GetTeamManager().FindTeam(ownerID)
@@ -5605,6 +5312,7 @@
                     continue
                 
                 if curTeamPlayer.GetCopyMapID() == GameWorld.GetGameWorld().GetCopyMapID() \
+                    and (not hurtList or hurtList.HaveHurtValue(curTeamPlayer.GetPlayerID()))\
                     and self.GetIsInRefreshPoint(curTeamPlayer.GetPosX(), curTeamPlayer.GetPosY(), refreshPoint) \
                     and AttackCommon.CheckKillNPCByCnt(curTeamPlayer, curNPC, False) and curTeamPlayer.GetVisible():
                     self.__AddDropOwnerPlayerBuff(curTeamPlayer, tick)
diff --git a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/NPC/NPCHurtManager.py b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/NPC/NPCHurtManager.py
new file mode 100644
index 0000000..7e39069
--- /dev/null
+++ b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/NPC/NPCHurtManager.py
@@ -0,0 +1,953 @@
+#!/usr/bin/python
+# -*- coding: GBK -*-
+#-------------------------------------------------------------------------------
+#
+##@package NPCHurtManager
+#
+# @todo:NPC伤血管理
+# @author hxp
+# @date 2019-12-06
+# @version 1.0
+#
+# 详细描述: NPC伤血管理
+#          因为加入了协助系统,协助玩家暂无上限限制,所以总伤害会超过20亿
+#          原伤血列表不支持协助、不支持总伤害超过20亿,故重写一套py版伤血管理,函数命名与c++接口提供的函数名一致
+#
+#-------------------------------------------------------------------------------
+#"""Version = 2019-12-06 21:00"""
+#-------------------------------------------------------------------------------
+
+import GameWorld
+import PyGameData
+import PlayerAssist
+import AttackCommon
+import IPY_GameWorld
+import PlayerControl
+import ChPyNetSendPack
+import ItemControler
+import NetPackCommon
+import IpyGameDataPY
+import ShareDefine
+import PlayerTeam
+import NPCCommon
+import ChConfig
+
+import time
+
+
+class HurtValueObj():
+    ''' 伤血对象,可能是玩家、队伍等,支持协助信息等
+    '''
+    
+    def __init__(self):
+        self.__hurtID = 0
+        self.__hurtType = ChConfig.Def_NPCHurtTypePlayer # 均默认是玩家
+        self.__hurtName = ""
+        self.__hurtValue = 0
+        return
+    
+    def GetValueID(self): return self.__hurtID
+    def SetValueID(self, hurtID): self.__hurtID = hurtID
+    def GetValueType(self): return self.__hurtType
+    def SetValueType(self, hurtType): self.__hurtType = hurtType
+    def GetHurtName(self): return self.__hurtName
+    def SetHurtName(self, hurtName): self.__hurtName = hurtName
+    def GetHurtValue(self): return self.__hurtValue
+    def SetHurtValue(self, hurtValue): self.__hurtValue = hurtValue
+    
+class PlayerHurtList():
+    ''' 伤血列表, 类似 IPY_GameObj.IPY_PlayerHurtList
+    '''
+    
+    def __init__(self, lineID, objID, npcID):
+        GameWorld.DebugLog("初始化NPC伤血列表实例!", npcID, lineID)
+        self.lineID = lineID
+        self.objID = objID
+        self.npcID = npcID
+        self.curNPC = GameWorld.FindNPCByID(objID)
+        self.__noAssitPlayerIDDict = {} # 非协助玩家ID字典 {playerID:[assistPlayerID, ...], ...}
+        self.__assistPlayerIDDict = {} # 协助玩家对应目标ID字典 {assistPlayerID:tagPlayerID, ...}
+        self.__hurtPlayerDict = {} # 所有伤血玩家个体实例字典,含协助玩家 {playerID:HurtValueObj, ...}
+        
+        self.__hurtSortList = []
+        self.__hurtDict = {} # 伤血列表实例字典,实际的NPC伤血列表实例,可能不是玩家{(hurtID, hurtType):HurtValueObj, ...}
+        return
+    
+    def Clear(self):
+        # 在NPC触发重置伤血时重置,不重置协助关系及伤血玩家实例,只重置伤血值
+        
+        GameWorld.DebugLog("Clear重置NPC伤血!", self.npcID, self.lineID)
+        self.__hurtSortList = []
+        self.__hurtDict = {}
+        # 重置所有玩家伤血值
+        for hurtObj in self.__hurtPlayerDict.values():
+            hurtObj.SetHurtValue(0)
+        return
+    
+    def OnDelete(self):
+        # 删除伤血列表,NPC死亡调用
+        
+        cancelPlayerIDList = self.__noAssitPlayerIDDict.keys()
+        if cancelPlayerIDList:
+            mapID = GameWorld.GetMap().GetMapID()
+            queryData = [mapID, self.lineID, self.npcID, self.objID, "OnBossDead", cancelPlayerIDList]
+            PlayerAssist.QueryGameServer_PlayerAssist(0, "OnCancelBossRequestAssist", queryData)
+            
+        return
+    
+    def __GetHurtTypeObj(self, hurtID, hurtType, hurtName=""):
+        ## 获取伤血列表伤血类型实例
+        hurtObj = None
+        hurtKey = (hurtID, hurtType)
+        if hurtKey not in self.__hurtDict:
+            GameWorld.DebugLog("添加伤血列表对象: hurtID=%s,hurtType=%s" % (hurtID, hurtType), self.npcID, self.lineID)
+            hurtObj = HurtValueObj()
+            hurtObj.SetValueID(hurtID)
+            hurtObj.SetValueType(hurtType)
+            hurtObj.SetHurtName(hurtName)
+            self.__hurtDict[hurtKey] = hurtObj
+        hurtObj = self.__hurtDict[hurtKey]
+        return hurtObj
+    
+    def __GetHurtPlayer(self, playerID, playerName=""):
+        ## 获取伤血玩家实例, 每个玩家独立, 且伤血独立统计
+        hurtPlayer = None
+        if playerID not in self.__hurtPlayerDict:
+            GameWorld.DebugLog("添加伤血玩家: playerID=%s,playerName=%s" % (playerID, playerName), self.npcID, self.lineID)
+            hurtPlayer = HurtValueObj()
+            hurtPlayer.SetValueID(playerID)
+            #hurtPlayer.SetHurtName(playerName)
+            self.__hurtPlayerDict[playerID] = hurtPlayer
+        hurtPlayer = self.__hurtPlayerDict[playerID]
+        # 因为伤血玩家可能在某些情况下创建了没有名字的实例,所以这里在有传入名字时进行强制更新
+        if playerName:
+            hurtPlayer.SetHurtName(playerName)
+        return hurtPlayer
+    
+    def AddAssistPlayer(self, assistPlayerID, assistPlayerName, tagPlayerID, tagPlayerName, tagTeamID):
+        ## 添加助战玩家
+        # @param assistPlayerID: 协助玩家ID
+        # @param tagPlayerID: 目标玩家ID,即发布协助的玩家ID
+                
+        if assistPlayerID == tagPlayerID:
+            # 不能协助自己
+            return
+        
+        if tagPlayerID not in self.__noAssitPlayerIDDict or tagPlayerID not in self.__hurtPlayerDict:
+            GameWorld.ErrLog("新增协助玩家异常,不存在该常规伤血玩家,无法协助他!assistPlayerID=%s,tagPlayerID=%s" 
+                             % (assistPlayerID, tagPlayerID), self.npcID, self.lineID)
+            return
+        
+        GameWorld.DebugLog("新增协助玩家: assistPlayerID=%s,tagPlayerID=%s,tagTeamID=%s" 
+                           % (assistPlayerID, tagPlayerID, tagTeamID), self.npcID, self.lineID)
+        
+        if assistPlayerID in self.__noAssitPlayerIDDict:
+            GameWorld.DebugLog("原来为常规玩家,需要先删除!", self.npcID, self.lineID)
+            self.DelHurtPlayer(assistPlayerID, "BecomeAssistPlayer")
+            
+        assistPlayerIDList = self.__noAssitPlayerIDDict[tagPlayerID]
+        if assistPlayerID not in assistPlayerIDList:
+            assistPlayerIDList.append(assistPlayerID)
+        self.__assistPlayerIDDict[assistPlayerID] = tagPlayerID
+        
+        GameWorld.DebugLog("    self.__noAssitPlayerIDDict=%s" % (self.__noAssitPlayerIDDict), self.npcID, self.lineID)
+        GameWorld.DebugLog("    self.__assistPlayerIDDict=%s" % (self.__assistPlayerIDDict), self.npcID, self.lineID)
+        return
+    
+    def DelHurtPlayer(self, playerID, reason, isMapServerDel=True):
+        ## 删除伤血玩家, 可能由地图、GameServer触发删除
+        
+        if playerID not in self.__hurtPlayerDict:
+            return
+        
+        GameWorld.DebugLog("删除伤血玩家: playerID=%s,reason=%s,isMapServerDel=%s" % (playerID, reason, isMapServerDel), self.npcID, self.lineID)
+            
+        #非协助玩家
+        if playerID in self.__noAssitPlayerIDDict:
+            # 先清玩家伤血
+            self.__ClearPlayerHurt(playerID)
+            
+            assistPlayerIDList = self.__noAssitPlayerIDDict.pop(playerID, [])
+            GameWorld.DebugLog("    是常规玩家,同时删除协助玩家: assistPlayerIDList=%s" % assistPlayerIDList, self.npcID, self.lineID)
+            
+            # 强制删除所有协助该玩家,列表倒序删
+            for assistPlayerID in assistPlayerIDList[::-1]:
+                self.DelHurtPlayer(assistPlayerID, "RequestPlayerCancel_%s" % reason, isMapServerDel)
+                
+            # 地图删除的同步GameServer
+            if isMapServerDel:
+                mapID = GameWorld.GetMap().GetMapID()
+                cancelPlayerIDList = [playerID]
+                queryData = [mapID, self.lineID, self.npcID, self.objID, reason, cancelPlayerIDList]
+                PlayerAssist.QueryGameServer_PlayerAssist(0, "OnCancelBossRequestAssist", queryData)
+                
+        # 协助玩家
+        elif playerID in self.__assistPlayerIDDict:
+            # 先清玩家伤血
+            self.__ClearPlayerHurt(playerID)
+            
+            tagPlayerID = self.__assistPlayerIDDict.pop(playerID, 0)
+            
+            # 移除协助对应关系
+            if tagPlayerID in self.__noAssitPlayerIDDict:
+                assistPlayerIDList = self.__noAssitPlayerIDDict[tagPlayerID]
+                if playerID in assistPlayerIDList:
+                    assistPlayerIDList.remove(playerID)
+                    GameWorld.DebugLog("    是协助玩家,删除协助关系: tagPlayerID=%s,assistPlayerIDList=%s" 
+                                       % (tagPlayerID, self.__noAssitPlayerIDDict[tagPlayerID]), self.npcID, self.lineID)
+            
+            # 地图删除的同步GameServer
+            if isMapServerDel:
+                mapID = GameWorld.GetMap().GetMapID()
+                queryData = [mapID, self.lineID, self.npcID, self.objID, reason]
+                PlayerAssist.QueryGameServer_PlayerAssist(playerID, "OnCancelBossAssist", queryData)
+                
+        self.__hurtPlayerDict.pop(playerID) # 放在最后pop伤血实例
+        
+        if not self.__hurtPlayerDict:
+            GameWorld.DebugLog("没有伤血玩家了!", self.npcID, self.lineID)
+            self.Clear()
+            
+        return
+    
+    def HaveHurtValue(self, playerID):
+        ## 是否对该boss有伤害贡献
+        
+        if playerID not in self.__hurtPlayerDict:
+            return False
+        
+        hurtPlayer = self.__GetHurtPlayer(playerID)
+        if hurtPlayer.GetHurtValue():
+            return True
+        
+        if playerID in self.__noAssitPlayerIDDict:
+            assistPlayerIDList = self.__noAssitPlayerIDDict[playerID]
+            for assistPlayerID in assistPlayerIDList:
+                assHurtPlayer = self.__GetHurtPlayer(assistPlayerID)
+                if assHurtPlayer.GetHurtValue():
+                    return True
+                
+        return False
+    
+    def GetPlayerHurtDetail(self, playerID):
+        ## 玩家伤血输出明细 - 暂时GM命令输出数据用
+        # @return: hurtValue, {assistPlayerID:hurtValue, ...}
+        
+        if playerID in self.__noAssitPlayerIDDict:
+            hurtPlayer = self.__GetHurtPlayer(playerID)
+            assistPlayerHurtDict = {}
+            assistPlayerIDList = self.__noAssitPlayerIDDict[playerID]
+            for assistPlayerID in assistPlayerIDList:
+                assHurtPlayer = self.__GetHurtPlayer(assistPlayerID)
+                assistPlayerHurtDict[assistPlayerID] = assHurtPlayer.GetHurtValue()
+            return hurtPlayer.GetHurtValue(), assistPlayerHurtDict
+        
+        if playerID in self.__assistPlayerIDDict:
+            assHurtPlayer = self.__GetHurtPlayer(playerID)
+            return assHurtPlayer.GetHurtValue(), {}
+        
+        return 0, {}
+    
+    def GetNoAssitPlayerIDDict(self): return self.__noAssitPlayerIDDict
+    def GetAssistPlayerIDDict(self): return self.__assistPlayerIDDict
+    
+    def IsNoAssistPlayer(self, playerID):
+        ## 是否非助战伤血玩家
+        return  playerID in self.__noAssitPlayerIDDict
+    
+    def IsAssistPlayer(self, playerID):
+        ## 是否助战伤血玩家
+        return playerID in self.__assistPlayerIDDict
+    
+    def OnHurtPlayerEnterTeam(self, playerID, playerName, befTeamID, newTeam, tick):
+        ''' 伤血玩家加入队伍
+                                个人伤害并入队伍伤害,个人所有协助玩家伤害重新统计,表现在协助伤害排行榜,协助玩家之后的伤害计入队伍伤害
+        '''
+        if playerID not in self.__noAssitPlayerIDDict:
+            # 只处理非协助玩家
+            return
+        
+        newTeamID = newTeam.GetTeamID()
+        
+        if befTeamID:
+            # 因为必须是先退队再进队的,所以进入队伍时不存在有之前队伍ID的情况
+            GameWorld.ErrLog("玩家进入时已有队伍!playerID=%s,befTeamID=%s,newTeamID=%s" % (playerID, befTeamID, newTeamID), self.npcID, self.lineID)
+            return
+        
+        # 更新玩家个人伤血类型、ID
+        hurtPlayer = self.__GetHurtPlayer(playerID, playerName)
+        hurtPlayer.SetValueType(ChConfig.Def_NPCHurtTypeTeam)
+        hurtPlayer.SetValueID(newTeamID)
+        
+        # 删除个人伤血列表实例
+        playerTotalHurt = 0
+        playerHurtKey = (playerID , ChConfig.Def_NPCHurtTypePlayer)
+        if playerHurtKey in self.__hurtDict:
+            playerHurtObj = self.__hurtDict.pop(playerHurtKey)
+            playerTotalHurt = playerHurtObj.GetHurtValue()
+            
+        GameWorld.DebugLog("常规伤血玩家加入队伍: playerID=%s,playerTotalHurt=%s,newTeamID=%s" 
+                           % (playerID, playerTotalHurt, newTeamID), self.npcID, self.lineID)
+        
+        # 伤血并入队伍伤血
+        if playerTotalHurt:
+            teamHurtObj = self.__GetHurtTypeObj(newTeamID , ChConfig.Def_NPCHurtTypeTeam, playerName)
+            teamHurtValue = teamHurtObj.GetHurtValue()
+            updTeamHurtValue = teamHurtValue + playerTotalHurt
+            teamHurtObj.SetHurtValue(updTeamHurtValue)
+            GameWorld.DebugLog("    个人总伤血并入新队伍伤血: teamHurtValue=%s,playerTotalHurt=%s,updTeamHurtValue=%s" 
+                               % (teamHurtValue, playerTotalHurt, updTeamHurtValue), self.npcID, self.lineID)
+            self.__UpdHurtTeamName(newTeamID)
+              
+        self.Sort()
+        return
+    
+    def OnHurtPlayerLeaveTeam(self, playerID, leaveTeamID, tick):
+        ''' 伤血玩家退出队伍
+                                原队伍伤害不变,个人及所有协助玩家伤害重新计算,协助玩家之后的伤害计入个人伤害;
+                              离线被踢离队的情况协助玩家可继续协助输出并获得协助奖励,伤害计入个人,但是离线玩家无法获得归属
+        '''
+        if playerID not in self.__noAssitPlayerIDDict:
+            # 只处理非协助玩家
+            return
+        
+        # 玩家个人伤血类型、ID
+        hurtPlayer = self.__GetHurtPlayer(playerID)
+        hurtPlayer.SetValueType(ChConfig.Def_NPCHurtTypePlayer)
+        hurtPlayer.SetValueID(playerID)
+        
+        # 统计个人及协助总伤害
+        playerTotalHurt = hurtPlayer.GetHurtValue()
+        assistPlayerIDList = self.__noAssitPlayerIDDict.get(playerID, [])
+        for assistPlayerID in assistPlayerIDList:
+            assistHurtPlayer = self.__GetHurtPlayer(assistPlayerID)
+            playerTotalHurt += assistHurtPlayer.GetHurtValue()
+        
+        GameWorld.DebugLog("常规伤血玩家离开队伍: playerID=%s,playerTotalHurt=%s,leaveTeamID=%s" 
+                           % (playerID, playerTotalHurt, leaveTeamID), self.npcID, self.lineID)
+        
+        if not playerTotalHurt:
+            return
+        
+        # 原队伍扣除对应伤害
+        teamHurtKey = (leaveTeamID , ChConfig.Def_NPCHurtTypeTeam)
+        if teamHurtKey in self.__hurtDict:
+            teamHurtObj = self.__hurtDict[teamHurtKey]
+            teamHurtValue = teamHurtObj.GetHurtValue()
+            updTeamHurtValue = max(0, teamHurtValue - playerTotalHurt)
+            teamHurtObj.SetHurtValue(updTeamHurtValue)
+            GameWorld.DebugLog("    原队伍伤害扣除: teamHurtValue=%s,playerTotalHurt=%s,updTeamHurtValue=%s" 
+                               % (teamHurtValue, playerTotalHurt, updTeamHurtValue), self.npcID, self.lineID)
+            self.__UpdHurtTeamName(leaveTeamID)
+            
+        # 创建新个人伤害列表实例
+        newHurtObj = self.__GetHurtTypeObj(playerID, ChConfig.Def_NPCHurtTypePlayer, hurtPlayer.GetHurtName())
+        newHurtObj.SetHurtValue(playerTotalHurt)
+        
+        self.Sort()
+        return
+    
+    def __UpdHurtTeamName(self, teamID):
+        ## 更新队伍伤血名称,队长在优先使用队长名字,否则使用任意一个在线攻击此boss的非协助队员名
+        
+        hurtTeamObj = self.__GetHurtTypeObj(teamID , ChConfig.Def_NPCHurtTypeTeam)
+        
+        updName = ""
+        copyPlayerManager = GameWorld.GetMapCopyPlayerManager()
+        mapTeamPlayerIDList = PlayerTeam.GetMapTeamPlayerIDList(teamID)
+        for memPlayerID in mapTeamPlayerIDList:
+            if memPlayerID not in self.__noAssitPlayerIDDict:
+                continue
+            player = copyPlayerManager.FindPlayerByID(memPlayerID)
+            if not player:
+                continue
+            playerName = player.GetPlayerName()
+            if player.GetTeamLV() == IPY_GameWorld.tmlLeader:
+                hurtTeamObj.SetHurtName(playerName)
+                GameWorld.DebugLog("    更新队伍名称,使用队长名称!teamID=%s" % teamID, self.npcID, self.lineID)
+                return
+            if not updName:
+                updName = playerName
+                
+        if updName:
+            hurtTeamObj.SetHurtName(updName)
+            GameWorld.DebugLog("    更新队伍名称,使用队员名称!teamID=%s" % teamID, self.npcID, self.lineID)
+        return
+    
+    def AddPlayerHurtValue(self, atkPlayerID, atkName, value, atkTeamID=0, isSort=False):
+        ## 添加玩家伤血
+        
+        tagPlayerID = 0 # 协助目标玩家ID
+        atkHurtPlayer = self.__GetHurtPlayer(atkPlayerID, atkName)
+        if atkPlayerID in self.__assistPlayerIDDict:
+            tagPlayerID = self.__assistPlayerIDDict[atkPlayerID]
+            tagHurtPlayer = self.__GetHurtPlayer(tagPlayerID)
+            tagPlayerName = tagHurtPlayer.GetHurtName()
+            hurtID, hurtType = tagHurtPlayer.GetValueID(), tagHurtPlayer.GetValueType()
+            #GameWorld.DebugLog("协助玩家伤血: atkPlayerID=%s,value=%s,tagPlayerID=%s" % (atkPlayerID, value, tagPlayerID), self.npcID, self.lineID)
+            
+        else:
+            if atkTeamID:
+                hurtID, hurtType = atkTeamID, ChConfig.Def_NPCHurtTypeTeam
+            else:
+                hurtID, hurtType = atkPlayerID, ChConfig.Def_NPCHurtTypePlayer
+            atkHurtPlayer.SetValueType(hurtType)
+            atkHurtPlayer.SetValueID(hurtID)
+            if atkPlayerID not in self.__noAssitPlayerIDDict:
+                self.__noAssitPlayerIDDict[atkPlayerID] = []
+                GameWorld.DebugLog("新增常规玩家: atkPlayerID=%s" % (atkPlayerID), self.npcID, self.lineID)
+            #GameWorld.DebugLog("常规玩家伤血: atkPlayerID=%s,value=%s" % (atkPlayerID, value), self.npcID, self.lineID)
+            
+        hurtObj = self.__GetHurtTypeObj(hurtID, hurtType, tagPlayerName if tagPlayerID else atkName)
+        
+        # 伤血列表伤血累加
+        befValue = hurtObj.GetHurtValue()
+        updValue = befValue + value
+        hurtObj.SetHurtValue(updValue)
+        isNewHurt = befValue == 0 and updValue > 0
+        
+        # 伤血玩家伤血累加,这个仅伤血玩家自己的个人输出,非伤血列表中的汇总输出
+        updAtkHurtValue = atkHurtPlayer.GetHurtValue() + value
+        atkHurtPlayer.SetHurtValue(updAtkHurtValue)
+        
+        #GameWorld.DebugLog("    hurtID=%s,hurtType=%s,hurtValue=%s (%s + %s) updAtkHurtValue=%s" 
+        #                   % (hurtID, hurtType, updValue, befValue, value, updAtkHurtValue), self.npcID, self.lineID)
+        
+        if isNewHurt or isSort:
+            self.Sort()
+                
+        return
+    
+    def RefreshHurtList(self, tick, refreshInterval=3000, isDead=False):
+        ## 刷新伤血列表
+        # @return: atkPlayer, hurtID, hurtType
+        
+        curNPC = self.curNPC
+        if not self.__hurtDict:
+            return
+        
+        if not isDead:
+            if refreshInterval and tick - curNPC.GetDictByKey(ChConfig.Def_NPC_Dict_LastRefreshHurtTick) < refreshInterval:
+                return self.__GetAtkObjByHurtList()
+            
+        curNPC.SetDict(ChConfig.Def_NPC_Dict_LastRefreshHurtTick, tick)
+        
+        hurtPlayerDict = {} # {playerID:teamID, ...}
+        refreshPoint = curNPC.GetRefreshPosAt(curNPC.GetCurRefreshPointIndex())
+        for hurtKey in self.__hurtDict.keys():
+            #获得伤血对象
+            hurtID, hurtType = hurtKey
+            if not hurtID:
+                continue
+            
+            if hurtType == ChConfig.Def_NPCHurtTypePlayer:
+                teamID = 0
+                playerID = hurtID
+                if self.__UnAssistPlayerHurtValidLogic(playerID, refreshPoint, tick):
+                    hurtPlayerDict[playerID] = teamID
+                    
+            elif hurtType == ChConfig.Def_NPCHurtTypeTeam:
+                teamID = hurtID
+                mapTeamPlayerIDList = PlayerTeam.GetMapTeamPlayerIDList(teamID)
+                for teamPlayerID in mapTeamPlayerIDList:
+                    if self.__UnAssistPlayerHurtValidLogic(teamPlayerID, refreshPoint, tick):
+                        hurtPlayerDict[teamPlayerID] = teamID
+                        
+        mapID = GameWorld.GetMap().GetMapID()
+        if NPCCommon.IsMapNeedBossShunt(mapID):
+            self.__UpdBossShuntInfo(mapID, hurtPlayerDict, tick)
+            
+        self.Sort()
+        
+        if isDead:
+            self.__DoGiveAssistAward()
+            
+        isInHurt = self.__hurtSortList != []
+        curNPC.SetDict(ChConfig.Def_NPC_Dict_InHurtProtect, isInHurt)
+        return self.__GetAtkObjByHurtList()
+    
+    def __UnAssistPlayerHurtValidLogic(self, playerID, refreshPoint, tick):
+        ## 非协助玩家伤血有效性检查逻辑
+        
+        valid = False
+        checkPlayerIDList = [playerID] + self.__noAssitPlayerIDDict.get(playerID, []) # 检查所有有关系的玩家ID
+        for checkPlayerID in checkPlayerIDList:
+            if self.__IsPlayerHurtValid(checkPlayerID, refreshPoint, tick):
+                valid = True
+            else:
+                self.__ClearPlayerHurt(checkPlayerID)
+                
+        return valid
+    
+    def __IsPlayerHurtValid(self, playerID, refreshPoint, tick):
+        ## 玩家伤血是否还有效
+        
+        if playerID not in self.__hurtPlayerDict:
+            return False
+        
+        hurtPlayer = self.__GetHurtPlayer(playerID)
+        hurtValue = hurtPlayer.GetHurtValue()
+        if not hurtValue:
+            return False
+        
+        player = GameWorld.GetMapCopyPlayerManager().FindPlayerByID(playerID)
+        if player:
+            if playerID in self.__noAssitPlayerIDDict:
+                if not AttackCommon.CheckKillNPCByCnt(player, self.curNPC, False):
+                    GameWorld.DebugLog("非协助伤血玩家没有攻击boss次数,不计!playerID=%s" % playerID, self.npcID, self.lineID)
+                    return False
+                
+            if player.GetInitOK() and (not player.GetVisible() or player.GetSightLevel() != self.curNPC.GetSightLevel()):
+                GameWorld.DebugLog("伤血玩家不可见,不计!playerID=%s" % playerID, self.npcID, self.lineID)
+                return False
+            
+            if player.GetHP() <= 0 or player.GetPlayerAction() == IPY_GameWorld.paDie:
+                deadTime = player.NomalDictGetProperty(ChConfig.Def_Player_Dict_DeadTime)
+                if time.time() - deadTime >= IpyGameDataPY.GetFuncCfg("BossHurtValue", 1):
+                    GameWorld.DebugLog("伤血玩家死亡超过伤血保护时长,不计!playerID=%s" % playerID, self.npcID, self.lineID)
+                    return False
+                
+            if not self.__GetIsInRefreshPoint(player.GetPosX(), player.GetPosY(), refreshPoint):
+                GameWorld.DebugLog("伤血玩家不在boss范围内,不计!playerID=%s" % playerID, self.npcID, self.lineID)
+                return False
+            
+            return True
+        
+        else:
+            
+            # 是否离线超过3分钟,下线坐标是否不在boss区域等
+            leaveTick = PlayerControl.GetPlayerLeaveServerTick(playerID)
+            leavePos = PlayerControl.GetPlayerLeaveServerPos(playerID)
+            if not leaveTick or not leavePos:
+                GameWorld.DebugLog("伤血玩家不在本地图或已长久离线,不计!playerID=%s" % playerID, self.npcID, self.lineID)
+                return False
+            
+            if tick - leaveTick > ChConfig.Def_PlayerOfflineProtectTime:
+                GameWorld.DebugLog("伤血离线玩家超过保护时长,不计!playerID=%s,tick=%s,leaveTick=%s" % (playerID, tick, leaveTick), self.npcID, self.lineID)
+                return False
+            
+            if not self.__GetIsInRefreshPoint(leavePos[0], leavePos[1], refreshPoint):
+                GameWorld.DebugLog("伤血离线玩家不在保护区域内,不计!playerID=%s,leavePos=%s" % (playerID, leavePos), self.npcID, self.lineID)
+                return False
+            
+            return True
+        
+        return False
+    
+    def __ClearPlayerHurt(self, playerID):
+        ## 清除玩家伤血,不删实例
+        
+        if playerID not in self.__hurtPlayerDict:
+            return
+        
+        hurtPlayer = self.__GetHurtPlayer(playerID)
+        hurtValue = hurtPlayer.GetHurtValue()
+        if not hurtValue:
+            return
+        hurtPlayer.SetHurtValue(0)
+        
+        # 协助玩家
+        if playerID in self.__assistPlayerIDDict:
+            tagPlayerID = self.__assistPlayerIDDict[playerID]
+            tagHurtPlayer = self.__GetHurtPlayer(tagPlayerID)
+            hurtID = tagHurtPlayer.GetValueID()
+            hurtType = tagHurtPlayer.GetValueType()
+        else:
+            hurtID = hurtPlayer.GetValueID()
+            hurtType = hurtPlayer.GetValueType()
+        
+        GameWorld.DebugLog("    清除玩家伤血: playerID=%s,hurtValue=%s,hurtID=%s,hurtType=%s" 
+                           % (playerID, hurtValue, hurtID, hurtType), self.npcID, self.lineID)
+        
+        hurtKey = (hurtID, hurtType)
+        if hurtKey not in self.__hurtDict:
+            return
+        
+        # 扣除伤血列表实例伤血
+        hurtObj = self.__GetHurtTypeObj(hurtID, hurtType)
+        befValue = hurtObj.GetHurtValue()
+        updValue = max(0, befValue - hurtValue)
+        hurtObj.SetHurtValue(updValue)
+        GameWorld.DebugLog("    扣除伤血更新: hurtID=%s,hurtType=%s,befValue=%s,updValue=%s" 
+                           % (hurtID, hurtType, befValue, updValue), self.npcID, self.lineID)
+        
+        if not updValue:
+            self.__hurtDict.pop(hurtKey)
+        else:
+            # 如果是非协助玩家队伍队员伤血被清,则更新伤血队伍名称
+            if playerID in self.__noAssitPlayerIDDict and hurtType == ChConfig.Def_NPCHurtTypeTeam:
+                self.__UpdHurtTeamName(hurtID)
+                
+        return
+    
+    def __GetAtkObjByHurtList(self):
+        '''第一个可攻击的最大伤血对象,也是实际的归属者或队伍
+        因为玩家伤血掉线、死亡有一定时间的保留机制,故最大伤血不一定是可攻击目标(归属者)
+        注意: 该规则必须与最终算归属的规则一致,不然可能导致归属错乱
+        @return: atkPlayer, hurtType, hurtID
+        '''
+        
+        atkPlayer, atkHurtType, atkHurtID = None, 0, 0
+        curNPC = self.curNPC
+        refreshPoint = curNPC.GetRefreshPosAt(curNPC.GetCurRefreshPointIndex())
+        for hurtObj in self.__hurtSortList:
+            
+            hurtID = hurtObj.GetValueID()
+            hurtType = hurtObj.GetValueType()
+            
+            playerIDList = []
+            if hurtType == ChConfig.Def_NPCHurtTypePlayer:
+                assistPlayerIDList = self.__noAssitPlayerIDDict.get(hurtID, [])
+                playerIDList = [hurtID] + assistPlayerIDList
+                
+            elif hurtType == ChConfig.Def_NPCHurtTypeTeam:
+                teamID = hurtID
+                mapTeamPlayerIDList = PlayerTeam.GetMapTeamPlayerIDList(teamID)
+                for teamPlayerID in mapTeamPlayerIDList:
+                    if teamPlayerID not in self.__noAssitPlayerIDDict:
+                        continue
+                    playerIDList.append(teamPlayerID)
+                    assistPlayerIDList = self.__noAssitPlayerIDDict.get(teamPlayerID, [])
+                    playerIDList.extend(assistPlayerIDList)
+                    
+            else:
+                continue
+            
+            maxHurtValue = 0
+            for playerID in playerIDList:
+                
+                player = GameWorld.GetObj(playerID, IPY_GameWorld.gotPlayer)
+                if player == None:
+                    continue
+                
+                if player.GetHP() <= 0 or player.GetPlayerAction() == IPY_GameWorld.paDie:
+                    continue
+                
+                if not player.GetVisible() or player.GetSightLevel() != curNPC.GetSightLevel():
+                    continue
+                
+                if not self.__GetIsInRefreshPoint(player.GetPosX(), player.GetPosY(), refreshPoint):
+                    continue
+                
+                if playerID not in self.__hurtPlayerDict:
+                    continue
+                hurtPlayer = self.__hurtPlayerDict[playerID]
+                hurtValue = hurtPlayer.GetHurtValue()                
+                if hurtValue > maxHurtValue:
+                    maxHurtValue = hurtValue
+                    atkPlayer, atkHurtType, atkHurtID = player, hurtType, hurtID
+                    
+            if maxHurtValue:
+                return atkPlayer, atkHurtType, atkHurtID
+            
+        return atkPlayer, atkHurtType, atkHurtID
+    
+    def __GetIsInRefreshPoint(self, curPosX, curPosY, refreshPoint):
+        if not refreshPoint:
+            return False
+        
+        if (curPosX >= refreshPoint.GetPosX() - refreshPoint.GetMoveDist() and
+                curPosX <= refreshPoint.GetPosX() + refreshPoint.GetMoveDist() and
+                curPosY >= refreshPoint.GetPosY() - refreshPoint.GetMoveDist() and
+                curPosY <= refreshPoint.GetPosY() + refreshPoint.GetMoveDist()):
+            return True
+        
+        return False
+    
+    def __UpdBossShuntInfo(self, mapID, hurtPlayerDict, tick):
+        ## 更新本地图线路boss分流信息
+        npcID = self.npcID
+        lineID = self.lineID
+        key = (mapID, lineID)
+        shuntPlayerDict = PyGameData.g_bossShuntPlayerInfo.get(key, {})
+        
+        shuntChange = False
+        for playerID, shuntInfo in shuntPlayerDict.items():
+            bossID, teamID, relatedTick = shuntInfo
+            if bossID != npcID:
+                # 不是该boss的伤害不处理
+                continue
+            
+            # 还在伤血中
+            if playerID in hurtPlayerDict:
+                newTeamID = hurtPlayerDict[playerID]
+                if newTeamID != teamID:
+                    shuntPlayerDict[playerID] = [npcID, newTeamID, 0]
+                    shuntChange = True
+                    GameWorld.DebugLog("boss分流 -> 玩家对该boss的伤害变更队伍!playerID=%s,npcID=%s,teamID=%s,newTeamID=%s" 
+                                       % (playerID, npcID, teamID, newTeamID), lineID)
+                elif relatedTick:
+                    shuntPlayerDict[playerID] = [npcID, newTeamID, 0]
+                    shuntChange = True
+                    GameWorld.DebugLog("boss分流 -> 玩家对该boss的关联状态转为伤害状态!playerID=%s,npcID=%s,teamID=%s,newTeamID=%s" 
+                                       % (playerID, npcID, teamID, newTeamID), lineID)
+                    
+            # 不在伤血中,更新关联tick
+            elif not relatedTick:
+                shuntPlayerDict[playerID] = [npcID, teamID, tick]
+                shuntChange = True
+                GameWorld.DebugLog("boss分流 -> 玩家不在该boss伤血中,设置为关联状态!playerID=%s,npcID=%s,teamID=%s,tick=%s" 
+                                   % (playerID, npcID, teamID, tick), lineID)
+                
+        # 伤先优先级最高,可直接覆盖更新
+        for playerID, teamID in hurtPlayerDict.items():
+            if playerID not in shuntPlayerDict:
+                shuntPlayerDict[playerID] = [npcID, teamID, 0]
+                shuntChange = True
+                GameWorld.DebugLog("boss分流 -> 新增玩家对boss伤害!playerID=%s,npcID=%s,teamID=%s" % (playerID, npcID, teamID), lineID)
+                
+            elif shuntPlayerDict[playerID][0] != npcID:
+                shuntPlayerDict[playerID] = [npcID, teamID, 0]
+                shuntChange = True
+                GameWorld.DebugLog("boss分流 -> 伤害转移到本boss上!playerID=%s,npcID=%s,teamID=%s" % (playerID, npcID, teamID), lineID)
+                
+        if shuntChange:
+            PyGameData.g_bossShuntPlayerInfo[key] = shuntPlayerDict
+            NPCCommon.GameServer_WorldBossShuntInfo(mapID, lineID)
+        return
+    
+    def __DoGiveAssistAward(self):
+        ''' 执行协助奖励逻辑
+        '''
+        
+        liheItemID = 2244 # 感谢礼盒物品ID,暂山寨,感谢系统再修改
+        
+        GameWorld.DebugLog("执行协助奖励逻辑", self.npcID, self.lineID)
+        copyPlayerManager = GameWorld.GetMapCopyPlayerManager()
+        for playerID, assistPlayerIDList in self.__noAssitPlayerIDDict.items():
+            if not assistPlayerIDList:
+                GameWorld.DebugLog("发布方没有发布协助,不给奖励: playerID=%s" % playerID, self.npcID, self.lineID)
+                continue
+            
+            player = copyPlayerManager.FindPlayerByID(playerID)
+            if player:
+                GameWorld.DebugLog("发布方给感谢礼盒奖励: playerID=%s" % playerID, self.npcID, self.lineID)
+                ItemControler.GivePlayerItemOrMail(player, [[liheItemID, 1, 0]])
+            else:
+                GameWorld.DebugLog("发布方离线或不在本地图,不给感谢礼盒奖励: playerID=%s" % playerID, self.npcID, self.lineID)
+                
+            for assistPlayerID in assistPlayerIDList:
+                assistHurtPlayer = self.__GetHurtPlayer(assistPlayerID)
+                if not assistHurtPlayer.GetHurtValue():
+                    GameWorld.DebugLog("协助方没有输出,不给奖励: assistPlayerID=%s" % assistPlayerID, self.npcID, self.lineID)
+                    continue
+                assPlayer = copyPlayerManager.FindPlayerByID(assistPlayerID)
+                if not assPlayer:
+                    GameWorld.DebugLog("协助方离线或不在本地图,不给活跃令奖励: assistPlayerID=%s" % assistPlayerID, self.npcID, self.lineID)
+                    continue
+                GameWorld.DebugLog("协助方给活跃令奖励: assistPlayerID=%s" % assistPlayerID, self.npcID, self.lineID)
+                PlayerControl.GiveMoney(assPlayer, ShareDefine.TYPE_Price_FamilyActivity, 35)
+                
+        return
+    
+    def __CmpHurtValue(self, hurtObjA, hurtObjB):
+        ## 伤害排序比较函数
+        if hurtObjA.GetHurtValue() > hurtObjB.GetHurtValue():
+            return 1
+        if hurtObjA.GetHurtValue() == hurtObjB.GetHurtValue():
+            return 0
+        return -1
+    
+    def Sort(self, isSync=True):
+        ## 伤血排序
+        self.__hurtSortList = sorted(self.__hurtDict.values(), cmp=self.__CmpHurtValue, reverse=True)
+        
+        if not isSync:
+            return
+        
+        syncPlayerIDList = self.__noAssitPlayerIDDict.keys() + self.__assistPlayerIDDict.keys()
+        if not syncPlayerIDList:
+            return
+        
+        # 暂定排序后默认同步伤血列表给所有相关玩家,伤血为0的不同步前端,仅用于后端逻辑用
+        hurtValueList = []
+        for hurtObj in self.__hurtSortList:
+            hurtValue = hurtObj.GetHurtValue()
+            if not hurtValue:
+                continue
+            hurtValueObj = ChPyNetSendPack.tagMCBossHurtValue()
+            hurtValueObj.HurtID = hurtObj.GetValueID()
+            hurtValueObj.HurtType = hurtObj.GetValueType()
+            hurtValueObj.HurtName = hurtObj.GetHurtName()
+            hurtValueObj.HurtValue = hurtValue % ShareDefine.Def_PerPointValue
+            hurtValueObj.HurtValueEx = hurtValue / ShareDefine.Def_PerPointValue
+            hurtValueList.append(hurtValueObj)
+        
+        bossHurtInfoPack = ChPyNetSendPack.tagMCBossHurtValueRankInfo()
+        bossHurtInfoPack.ObjID = self.objID
+        bossHurtInfoPack.HurtValueList = hurtValueList
+        bossHurtInfoPack.HurtCount = len(hurtValueList)
+        
+        assistHurtValueListDict = {}
+        copyPlayerManager = GameWorld.GetMapCopyPlayerManager()
+        for playerID in syncPlayerIDList:
+            player = copyPlayerManager.FindPlayerByID(playerID)
+            if not player:
+                continue
+            
+            if playerID in self.__noAssitPlayerIDDict:
+                assTagPlayerID = playerID
+            elif playerID in self.__assistPlayerIDDict:
+                assTagPlayerID = self.__assistPlayerIDDict[playerID]
+            else:
+                continue
+            
+            if assTagPlayerID not in assistHurtValueListDict:    
+                assistPlayerIDList = self.__noAssitPlayerIDDict[assTagPlayerID]
+                assistHurtValueList = []
+                for assistPlayerID in assistPlayerIDList:
+                    assHurtPlayer = self.__GetHurtPlayer(assistPlayerID)
+                    assHurtValue = assHurtPlayer.GetHurtValue()
+                    if not assHurtValue:
+                        continue
+                    assHurtValueObj = ChPyNetSendPack.tagMCBossHurtValueAssist()
+                    assHurtValueObj.PlayerID = assHurtPlayer.GetValueID()
+                    assHurtValueObj.PlayerName = assHurtPlayer.GetHurtName()
+                    assHurtValueObj.HurtValue = assHurtValue % ShareDefine.Def_PerPointValue
+                    assHurtValueObj.HurtValueEx = assHurtValue / ShareDefine.Def_PerPointValue
+                    assistHurtValueList.append(assHurtValueObj)
+                assistHurtValueListDict[assTagPlayerID] = assistHurtValueList
+                
+            assistHurtValueList = assistHurtValueListDict[assTagPlayerID]
+            bossHurtInfoPack.AssistHurtValueList = assistHurtValueList
+            bossHurtInfoPack.AssistHurtCount = len(assistHurtValueList)
+            NetPackCommon.SendFakePack(player, bossHurtInfoPack)
+            
+        return
+    
+    def GetHurtCount(self): return len(self.__hurtSortList)
+    def GetHurtAt(self, index): return self.__hurtSortList[index]
+    
+    def GetMaxHurtValue(self): return None if not self.__hurtSortList else self.__hurtSortList[0]
+    def GetLastTimeHurtValue(self):
+        return
+
+def OnPlayerLeaveMap(curPlayer):
+    ## 玩家离开地图处理
+    
+    playerID = curPlayer.GetPlayerID()
+    for hurtList in PyGameData.g_npcHurtDict.values():
+        if hurtList.IsNoAssistPlayer(playerID) or hurtList.IsAssistPlayer(playerID):
+            GameWorld.DebugLog("玩家离开地图, 删除boss伤血玩家!npcID=%s" % (hurtList.npcID), playerID)
+            hurtList.DelHurtPlayer(playerID, "LeaveMap")
+            break
+        
+    return
+
+def OnSetAssistTagPlayerID(curPlayer, value):
+    '''玩家更新了新的协助对象玩家ID
+        需要 清除本地图中玩家以非协助身份正在攻击的boss
+        
+        以协助身份攻击的通过GameServer进行清除,因为玩家可能不和协助目标同一个地图
+        比如先点了协助A玩家,还没过去的时候,又点了协助B玩家,所以需要通过GameServer清除协助目标的相关数据
+    '''
+        
+    if not value:
+        # 只处理有协助目标的情况
+        return
+    
+    playerID = curPlayer.GetPlayerID()
+    for hurtList in PyGameData.g_npcHurtDict.values():
+        if hurtList.IsNoAssistPlayer(playerID):
+            GameWorld.DebugLog("玩家开始协助其他人, 删除该boss伤血!npcID=%s" % (hurtList.npcID), playerID)
+            hurtList.DelHurtPlayer(playerID, "StartAssistBoss")
+            break
+        
+    return
+
+def ClearPlayerHurtList(curNPC):
+    ## 清空伤血列表
+    defendHurtList = GetPlayerHurtList(curNPC)
+    if not defendHurtList:
+        return
+    defendHurtList.Clear()
+    return
+
+def DeletePlayerHurtList(curNPC):
+    ## 删除伤血列表
+    lineID = GameWorld.GetGameWorld().GetLineID()
+    objID = curNPC.GetID()
+    npcID = curNPC.GetNPCID()
+    key = (lineID, objID, npcID)
+    if key in PyGameData.g_npcHurtDict:
+        hurtList =PyGameData.g_npcHurtDict.pop(key)
+        hurtList.OnDelete()
+        
+    return
+
+def GetPlayerHurtList(curNPC):
+    ## 获取伤血列表,可能为None
+    lineID = GameWorld.GetGameWorld().GetLineID()
+    objID = curNPC.GetID()
+    npcID = curNPC.GetNPCID()
+    return GetPlayerHurtListEx(lineID, objID, npcID)
+def GetPlayerHurtListEx(lineID, objID, npcID):
+    ## 获取伤血列表,可能为None
+    key = (lineID, objID, npcID)
+    defendHurtList = None
+    if key not in PyGameData.g_npcHurtDict:
+        ## 只统计最大伤血归属的boss
+        npcData = GameWorld.GetGameData().FindNPCDataByID(npcID)
+        if not npcData:
+            return defendHurtList
+        if not npcData.GetIsBoss() or NPCCommon.GetDropOwnerType(npcData) != ChConfig.DropOwnerType_MaxHurt:
+            return defendHurtList
+        defendHurtList = PlayerHurtList(lineID, objID, npcID)
+        PyGameData.g_npcHurtDict[key] = defendHurtList
+    defendHurtList = PyGameData.g_npcHurtDict[key]
+    return defendHurtList
+
+def OnNPCHurtPlayerEnterTeam(playerID, playerName, befTeamID, newTeam, tick):
+    ## 伤血玩家加入队伍
+    for hurtList in PyGameData.g_npcHurtDict.values():
+        hurtList.OnHurtPlayerEnterTeam(playerID, playerName, befTeamID, newTeam, tick)
+    return
+
+def OnNPCHurtPlayerLeaveTeam(playerID, leaveTeamID, tick):
+    ## 伤血玩家离开队伍
+    for hurtList in PyGameData.g_npcHurtDict.values():
+        hurtList.OnHurtPlayerLeaveTeam(playerID, leaveTeamID, tick)
+    return
+
+def AddHurtValue(atkPlayer, defNPC, value, isBounce):
+    '''添加伤血
+    @param isBounce: 是否反弹伤害,反弹伤害不计入非主动攻击的玩家伤血,因为规定玩家攻击另一个boss则要清除同地图上一个boss的该玩家伤害
+                                                        防止死亡回复活点跑图中被主动型boss攻击计入伤血导致清除同地图上一个主动攻击的boss伤血
+    '''
+    
+    defendHurtList = GetPlayerHurtList(defNPC)
+    if not defendHurtList:
+        return
+    atkPlayerID = atkPlayer.GetPlayerID()
+    
+    if isBounce and not defendHurtList.HaveHurtValue(atkPlayerID):
+        GameWorld.DebugLog("还没有伤害输出时反弹伤害不计入!", atkPlayerID)
+        return
+    
+    objID = defNPC.GetID()
+    npcID = defNPC.GetNPCID()
+    # 删除其他boss伤血、协助
+    for hurtList in PyGameData.g_npcHurtDict.values():
+        if hurtList.npcID == npcID and hurtList.objID == objID:
+            continue
+        if not hurtList.IsNoAssistPlayer(atkPlayerID) and not hurtList.IsAssistPlayer(atkPlayerID):
+            continue
+        GameWorld.Log("玩家主动攻击了其他boss,取消原boss伤血协助等数据! befNPCID=%s,newNPCID=%s" % (hurtList.npcID, npcID))
+        hurtList.DelHurtPlayer(atkPlayerID, "AttackNewBoss")
+        
+    defendHurtList.AddPlayerHurtValue(atkPlayerID, atkPlayer.GetPlayerName(), value, atkPlayer.GetTeamID())
+    return True
+
+def RefreshHurtList(curNPC, tick, refreshInterval=3000, isDead=False):
+    defendHurtList = GetPlayerHurtList(curNPC)
+    if not defendHurtList:
+        return
+    return defendHurtList.RefreshHurtList(tick, refreshInterval, isDead)
+
+def IsAssistPlayer(playerID, defNPC):
+    ## 是否协助中的玩家
+    defendHurtList = GetPlayerHurtList(defNPC)
+    if not defendHurtList:
+        return False
+    return defendHurtList.IsAssistPlayer(playerID)
+
diff --git a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/PlayerAssist.py b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/PlayerAssist.py
new file mode 100644
index 0000000..3f0e579
--- /dev/null
+++ b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/PlayerAssist.py
@@ -0,0 +1,151 @@
+#!/usr/bin/python
+# -*- coding: GBK -*-
+#-------------------------------------------------------------------------------
+#
+##@package Player.PlayerAssist
+#
+# @todo:协助系统
+# @author hxp
+# @date 2019-12-06
+# @version 1.0
+#
+# 详细描述: 协助系统
+#
+#-------------------------------------------------------------------------------
+#"""Version = 2019-12-06 21:00"""
+#-------------------------------------------------------------------------------
+
+import GameWorld
+import IpyGameDataPY
+import PlayerControl
+import NPCHurtManager
+import IPY_GameWorld
+
+import ChConfig
+
+#// B0 10 请求协助Boss #tagCMRequestAssistBoss
+#
+#struct    tagCMRequestAssistBoss
+#
+#{
+#    tagHead        Head;
+#    DWORD        ObjID;
+#    DWORD        NPCID;
+#};
+def OnRequestAssistBoss(index, clientData, tick):
+    curPlayer = GameWorld.GetPlayerManager().GetPlayerByIndex(index)
+    playerID = curPlayer.GetPlayerID()
+    objID = clientData.ObjID
+    npcID = clientData.NPCID
+    
+    if not curPlayer.GetFamilyID():
+        GameWorld.DebugLog("没有仙盟不能请求协助!", playerID)
+        return
+    
+    curNPC = GameWorld.FindNPCByNPCID(npcID)
+    if not curNPC or curNPC.GetID() != objID:
+        GameWorld.DebugLog("协助NPC不存在无法协助!", playerID)
+        return
+    
+    ipyData = IpyGameDataPY.GetIpyGameDataNotLog('BOSSInfo', npcID)
+    if not ipyData or not ipyData.GetCanAssist():
+        GameWorld.DebugLog("该NPC不能协助!npcID=%s" % npcID, playerID)
+        return
+    
+    hurtList = NPCHurtManager.GetPlayerHurtList(curNPC)
+    if not hurtList.IsNoAssistPlayer(playerID):
+        GameWorld.DebugLog("不是该boss的非助战伤血玩家,无法发起协助!npcID=%s" % npcID, playerID)
+        return
+    
+    if not GameWorld.SetPlayerTickTime(curPlayer, ChConfig.TYPE_Player_Tick_RequestAssist, tick):
+        GameWorld.DebugLog("请求协助CD中!npcID=%s" % npcID, playerID)
+        return
+    
+    mapID = curPlayer.GetMapID()
+    lineID = 0
+    if GameWorld.GetMap().GetMapFBType() != IPY_GameWorld.fbtNull: # 副本型boss,如封魔坛
+        lineID = PlayerControl.GetFBFuncLineID(curPlayer)
+    queryData = [mapID, lineID, npcID, objID]
+    QueryGameServer_PlayerAssist(playerID, "RequestAssistBoss", queryData)
+    return
+
+
+#// B0 11 请求协助组队副本 #tagCMRequestAssistTeamFB
+#
+#struct    tagCMRequestAssistTeamFB
+#
+#{
+#    tagHead        Head;
+#    WORD        MapID;
+#    WORD        LineID;
+#};
+def OnRequestAssistTeamFB(index, clientData, tick):
+    curPlayer = GameWorld.GetPlayerManager().GetPlayerByIndex(index)
+    playerID = curPlayer.GetPlayerID()
+    mapID = clientData.MapID
+    lineID = clientData.LineID
+    
+    if not curPlayer.GetFamilyID():
+        GameWorld.DebugLog("没有仙盟不能请求协助!", playerID)
+        return
+    
+    if GameWorld.GetMap().GetMapFBTypeByMapID(mapID) != IPY_GameWorld.fbtTeam:
+        GameWorld.DebugLog("非组队副本不能请求协助!mapID=%s" % mapID, playerID)
+        return
+    
+    if not GameWorld.SetPlayerTickTime(curPlayer, ChConfig.TYPE_Player_Tick_RequestAssist, tick):
+        GameWorld.DebugLog("请求协助CD中!mapID=%s,lineID=%s" % (mapID, lineID), playerID)
+        return
+    
+    queryData = [mapID, lineID]
+    QueryGameServer_PlayerAssist(playerID, "RequestAssistTeamFB", queryData)
+    return
+
+def OnStartAssistTeamFB(playerID, mapID, lineID, tagPlayerID):
+    ## 开始协助组队副本,协助玩家进入副本调用
+    queryData = [mapID, lineID, tagPlayerID]
+    QueryGameServer_PlayerAssist(playerID, "OnStartAssistTeamFB", queryData)
+    return
+
+def QueryGameServer_PlayerAssist(playerID, queryType, queryData):
+    msgInfo = str([queryType, queryData])
+    GameWorld.GetPlayerManager().GameServer_QueryPlayerResult(playerID, 0, 0, "PlayerAssist", msgInfo, len(msgInfo))
+    GameWorld.DebugLog("协助信息发送 GameServer: playerID=%s,queryType=%s,queryData=%s" % (playerID, queryType, queryData))
+    return
+
+def QueryResult_PlayerAssist(curPlayer, resultList):
+    ## 协助信息GameServer返回处理
+    if len(resultList) != 3:
+        return
+    #queryType, queryData, result = resultList
+    
+    return
+
+def GameServer_AssistBossMsg(assistData):
+    ## GameServer推送到指定boss地图的信息
+    
+    GameWorld.DebugLog("GameServer同步Boss协助信息: %s" % assistData)
+    
+    msgType = assistData[0]
+    
+    # 开始协助
+    if msgType == "Start":
+        assistGUID, assistPlayerID, assistPlayerName, tagPlayerID, tagPlayerName, tagTeamID, lineID, objID, npcID = assistData[1:]
+        npchurtList = NPCHurtManager.GetPlayerHurtListEx(lineID, objID, npcID)
+        if not npchurtList:
+            return
+        
+        npchurtList.AddAssistPlayer(assistPlayerID, assistPlayerName, tagPlayerID, tagPlayerName, tagTeamID)
+        QueryGameServer_PlayerAssist(0, "AddAssistBossPlayerOK", [assistGUID, assistPlayerID])
+        
+    # 取消协助
+    elif msgType == "Cancel":
+        assistPlayerID, lineID, objID, npcID = assistData[1:]
+        npchurtList = NPCHurtManager.GetPlayerHurtListEx(lineID, objID, npcID)
+        if not npchurtList:
+            return
+        npchurtList.DelHurtPlayer(assistPlayerID, "Cancel", isMapServerDel=False)
+        
+    return
+
+
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 4a78d00..1b8199b 100644
--- a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/PlayerControl.py
+++ b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/PlayerControl.py
@@ -87,6 +87,7 @@
 import FunctionNPCCommon
 import CrossRealmPlayer
 import CrossPlayerData
+import NPCHurtManager
 import ChNetSendPack
 import PlayerCoat
 import PlayerState
@@ -1522,6 +1523,10 @@
         NomalDictSetProperty(curPlayer, ChConfig.Def_Player_Dict_FromFBLineID, lineID)
         GameWorld.DebugLog("进入副本时,最后一次离开的可返回的副本ID更新!mapID=%s,lineID=%s,Pos(%s,%s)" % (mapID, lineID, posX, posY))
         
+    # 离开地图,暂时只处理离开中立、组队副本
+    if curPlayer.GetMapID() in IpyGameDataPY.GetFuncEvalCfg("MapLine", 4) or GameWorld.GetMap().GetMapFBType() == IPY_GameWorld.fbtTeam:
+        NPCHurtManager.OnPlayerLeaveMap(curPlayer)
+        
     # 从副本中切图
     if GameWorld.GetMap().GetMapFBType() != IPY_GameWorld.fbtNull:
         #默认回满血
@@ -2347,8 +2352,9 @@
 def __GetBossShuntLineID(curPlayer, curMapID, curLineID, tagMapID, npcID, lineIDList):
     '''获取目标地图boss分流线路
                 根据人数分流玩家,优先分配到活着的线路
-                队伍无视任何规则,默认分配到队伍队员多的那条线
-                
+                队伍默认分配到队伍队员多的那条线
+                协助默认分配到目标玩家线路,优先级最高
+                      
                 前端:
         1.在中立地图的时候,显示当前线路BOSS的状态
         2.在常规地图的时候,显示玩家击杀BOSS的CD时间
@@ -2384,10 +2390,14 @@
             lineIDList.remove(activityLineID)
             GameWorld.DebugLog("    非1线的活动线路不参与分流: activityLineID=%s,lineIDList=%s" % (activityLineID, lineIDList), playerID)
             
+    assistTagPlayerID = GetAssistTagPlayerID(curPlayer)
     for lineID in lineIDList:
         key = (tagMapID, lineID)
         # boss分流玩家信息{(mapID, lineID):{playerID:[bossID, teamID, relatedTick], ...}, ...}
         shuntPlayerDict = PyGameData.g_bossShuntPlayerInfo.get(key, {})
+        if assistTagPlayerID and assistTagPlayerID in shuntPlayerDict:
+            GameWorld.DebugLog("    分流到协助目标玩家线路 assistTagPlayerID=%s,lineID=%s" % (assistTagPlayerID, lineID), playerID)
+            return lineID
         playerCount = 0
         teamPlayerCount = 0
         for shuntPlayerID, shuntInfo in shuntPlayerDict.items():
@@ -5893,13 +5903,20 @@
     curPlayer.SendPropertyRefresh(ShareDefine.CDBPlayerRefresh_ForbidenTalk, value, False)
     return
 
+## 协助目标玩家ID
+def SetAssistTagPlayerID(curPlayer, value):
+    curPlayer.SetExAttr1(value, True, False) # 不通知GameServer
+    NPCHurtManager.OnSetAssistTagPlayerID(curPlayer, value)
+    return
+def GetAssistTagPlayerID(curPlayer): return curPlayer.GetExAttr1()
+
 ## 队伍相关审核开关状态, joinReqCheck-入队申请是否需要审核; inviteCheck-组队邀请是否需要审核;
 def SetTeamCheckStateEx(curPlayer, joinReqCheck, inviteCheck): return SetTeamCheckState(curPlayer, joinReqCheck * 10 + inviteCheck)
 def SetTeamCheckState(curPlayer, checkState): return curPlayer.SetExAttr2(checkState, False, True)
 def GetTeamCheckState(curPlayer): return curPlayer.GetExAttr2()
 
 ## 副本功能线路ID, 这里做db存储,防止在合并地图副本中掉线重上时前端无法加载正确的场景资源,登录加载场景时机为0102包
-def SetFBFuncLineID(curPlayer, funcLineID): return curPlayer.SetExAttr3(funcLineID, False, False)
+def SetFBFuncLineID(curPlayer, funcLineID): return curPlayer.SetExAttr3(funcLineID, False, True)
 def GetFBFuncLineID(curPlayer): return curPlayer.GetExAttr3()
 
 ## 跨服状态所在地图ID: 0-非跨服状态,非0-跨服状态对应的地图ID
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 370b4f4..a68cb25 100644
--- a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/PlayerEventCounter.py
+++ b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/PlayerEventCounter.py
@@ -97,6 +97,7 @@
 import FamilyRobBoss
 import FBHelpBattle
 import QuestManager
+import PlayerAssist
 import PyGameData
 import PlayerTJG
 
@@ -1291,6 +1292,12 @@
                 GameLogic_FamilyParty.DoAddFamilyMemberFamilyActivity(familyID, addFamilyActivity)
             return
         
+        if key == ShareDefine.Def_Notify_WorldKey_AssistBoss:
+            assistData = eval(msgValue)
+            if GameWorld.GetMap().GetMapID() == assistData[0]:
+                PlayerAssist.GameServer_AssistBossMsg(assistData[1:])
+            return
+        
         if key == ShareDefine.Def_Notify_WorldKey_AddFamilyAuctionItem:
             mapID, familyAuctionItemDict = eval(msgValue)
             if GameWorld.GetMap().GetMapID() == mapID:
diff --git a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/PlayerTeam.py b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/PlayerTeam.py
index ccd03a4..0c59fe1 100644
--- a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/PlayerTeam.py
+++ b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/PlayerTeam.py
@@ -26,6 +26,7 @@
 import SkillCommon
 import IPY_GameWorld
 import DataRecordPack
+import NPCHurtManager
 #import PlayerTruck
 import ShareDefine
 import SkillShell
@@ -249,10 +250,10 @@
         #curMapTeam.SetTeamType(recvPack.GetTeamType())
         DR_Team("RefreshPlayerTeamID_Create", teamID, dataDict)
             
-    if playerTeamID != teamID or not curTeam:
-        isNewTeam = playerTeamID != teamID # 切地图/上线时teamID可能不变,但是team为None
+    if (playerTeamID != teamID or not curTeam) and curMapTeam:
+        #isNewTeam = playerTeamID != teamID # 切地图/上线时teamID可能不变,但是team为None
         #GameWorld.DebugLog("    玩家当前无队伍,处理玩家进队!isNewTeam=%s" % isNewTeam, playerID)
-        __OnEnterTeam(curPlayer, curMapTeam, isNewTeam, tick, dataDict)
+        __OnEnterTeam(curPlayer, curMapTeam, playerTeamID, tick, dataDict)
     else:
         dataDict["MemberCount"] = 0 if not curMapTeam else curMapTeam.GetMemberCount()
         DR_Team("RefreshPlayerTeamID_Update", teamID, dataDict)
@@ -264,7 +265,7 @@
 #  @param tick 当前时间
 #  @return None
 #  @remarks 函数详细说明.
-def __OnEnterTeam(curPlayer, curMapTeam, isNewTeam, tick, dataDict):
+def __OnEnterTeam(curPlayer, curMapTeam, playerTeamID, tick, dataDict):
     teamID = curMapTeam.GetTeamID()
     memCount = curMapTeam.GetMemberCount()
     if memCount >= ShareDefine.Def_Team_MaxPlayerCount:
@@ -292,8 +293,8 @@
     #PlayerTruck.ChangeTruckNoteInfo(curPlayer)
     
     #以下为进入一个新的队伍额外处理
-    if not isNewTeam:
-        return
+    if playerTeamID != teamID:
+        NPCHurtManager.OnNPCHurtPlayerEnterTeam(playerID, curPlayer.GetPlayerName(), playerTeamID, curMapTeam, tick)
     return
  
 def __OnPlayerLeaveTeam(copyMapID, playerID, leaveTeamID, tick):
@@ -311,6 +312,8 @@
             
     __DelPlayerIDFromTeamPlayer(playerID, False)
     
+    NPCHurtManager.OnNPCHurtPlayerLeaveTeam(playerID, leaveTeamID, tick)
+    
     dataDict = {"playerID":playerID, "copyMapID":copyMapID}
     ### =========================== 以下逻辑是玩家存在时才需要处理的 =================================
     curPlayer = GameWorld.GetPlayerManager().FindPlayerByID(playerID)
diff --git a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/RemoteQuery/GY_Query_PlayerAssist.py b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/RemoteQuery/GY_Query_PlayerAssist.py
new file mode 100644
index 0000000..689e48f
--- /dev/null
+++ b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/RemoteQuery/GY_Query_PlayerAssist.py
@@ -0,0 +1,51 @@
+#!/usr/bin/python
+# -*- coding: GBK -*-
+#-------------------------------------------------------------------------------
+#
+##@package Player.RemoteQuery.GY_Query_PlayerAssist
+#
+# @todo:协助系统
+# @author hxp
+# @date 2019-12-06
+# @version 1.0
+#
+# 详细描述: 协助系统
+#
+#-------------------------------------------------------------------------------
+#"""Version = 2019-12-06 21:00"""
+#-------------------------------------------------------------------------------
+
+import PlayerAssist
+import GameWorld
+
+
+## 请求逻辑
+#  @param query_Type 请求类型
+#  @param query_ID 请求的玩家ID
+#  @param packCMDList 发包命令 [ ]
+#  @param tick 当前时间
+#  @return resultDisc
+def DoLogic(query_Type, query_ID, packCMDList, tick):
+    return
+    
+#---------------------------------------------------------------------
+## 执行结果
+#  @param curPlayer 发出请求的玩家
+#  @param callFunName 功能名称
+#  @param funResult 查询的结果
+#  @param tick 当前时间
+#  @return None
+#  @remarks 函数详细说明.
+def DoResult(curPlayer, callFunName, funResult, tick):
+    try:
+        funResultList = eval(funResult)
+    except:
+        GameWorld.ErrLog("GY_Query_PlayerAssist %s eval Error" % funResult, curPlayer.GetPlayerID())
+        return
+    
+    GameWorld.DebugLog("GY_Query_PlayerAssist ResultList=%s" % str(funResultList), curPlayer.GetPlayerID())
+    PlayerAssist.QueryResult_PlayerAssist(curPlayer, funResultList)
+    return
+
+
+
diff --git a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/PyGameData.py b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/PyGameData.py
index 5e5ad3d..3edcfd4 100644
--- a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/PyGameData.py
+++ b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/PyGameData.py
@@ -28,6 +28,8 @@
 
 g_filterEquipDict = {} # 按装备条件过滤的装备ID,不分职业 {"classLV_color_star":{(itemJob,itemPlace):itemID, ...}, ...}
 
+g_npcHurtDict = {} # npc伤血列表信息字典 {(lineID,objID,npcID):PlayerHurtList, ...}
+
 g_teamPlayerHurtValue = {} # 队伍玩家对NPC伤害输出量 {(lineID, objID, npcID):{(teamID, playerID):hurtValue, ...}, }
 g_teamPlayerDict = {} # 地图队伍对应玩家ID列表,含离线玩家 {teamID:[playerID, ...], ...}
 
diff --git a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/ShareDefine.py b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/ShareDefine.py
index 1c964a5..a520f4d 100644
--- a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/ShareDefine.py
+++ b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/ShareDefine.py
@@ -195,6 +195,9 @@
 Def_Notify_WorldKey_RedPacketOutput = 'RedPacketOutput'  # 红包产出信息
 Def_Notify_WorldKey_HurtLog = 'HurtLog'  # 战斗伤害日志
 Def_Notify_WorldKey_FairyDomainLimit = "FairyDomainLimit"  # 缥缈仙域限制事件
+
+Def_Notify_WorldKey_AssistBoss = "AssistBoss"  # 协助boss
+
 #运营活动表名定义
 OperationActionName_ExpRate = "ActExpRate" # 多倍经验活动
 OperationActionName_CostRebate = "ActCostRebate" # 消费返利活动

--
Gitblit v1.8.0