8346 【恺英】【后端】协助系统(初版,可完成协助完整流程,增加新NPC伤血管理,支持协助、支持超过20亿伤害)
20个文件已修改
5个文件已添加
2428 ■■■■ 已修改文件
ServerPython/CoreServerGroup/GameServer/PyNetPack.ini 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ServerPython/CoreServerGroup/GameServer/Script/GM/Commands/Assist.py 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ServerPython/CoreServerGroup/GameServer/Script/Player/ChPlayer.py 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ServerPython/CoreServerGroup/GameServer/Script/Player/PlayerAssist.py 668 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ServerPython/CoreServerGroup/GameServer/Script/Player/PlayerControl.py 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ServerPython/CoreServerGroup/GameServer/Script/Player/PlayerFamily.py 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ServerPython/CoreServerGroup/GameServer/Script/Player/PlayerQuery.py 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ServerPython/CoreServerGroup/GameServer/Script/Player/PlayerTeam.py 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ServerPython/CoreServerGroup/GameServer/Script/PyDataManager.py 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ServerPython/CoreServerGroup/GameServer/Script/ShareDefine.py 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/PyNetPack.ini 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Attack/AttackLogic/AttackCommon.py 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Attack/AttackLogic/NormalNPC_Attack_Player.py 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/ChConfig.py 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/GM/Commands/PrintNPCHurt.py 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/GM/Commands/SetWorldPos.py 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/NPC/NPCCommon.py 320 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/NPC/NPCHurtManager.py 953 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/PlayerAssist.py 151 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/PlayerControl.py 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/PlayerEventCounter.py 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/PlayerTeam.py 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/RemoteQuery/GY_Query_PlayerAssist.py 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/PyGameData.py 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/ShareDefine.py 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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
ServerPython/CoreServerGroup/GameServer/Script/GM/Commands/Assist.py
New file
@@ -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
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:
ServerPython/CoreServerGroup/GameServer/Script/Player/PlayerAssist.py
New file
@@ -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
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()
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,7 +620,10 @@
    for index in xrange(family.GetCount()):
        member = family.GetAt(index)
        memPlayer = member.GetPlayer()
        if memPlayer:
        if not memPlayer:
            continue
        if excludePlayerIDList and memPlayer.GetPlayerID() in excludePlayerIDList:
            continue
            NetPackCommon.SendFakePack(memPlayer, clientPack)
    return
@@ -1466,9 +1472,9 @@
                                           [ShareDefine.Def_FamilyActionEvent_MemberChange, ShareDefine.Def_FamilyMemberChange_KickOut], tick)
    #删除玩家
    curFamily.DeleteMember(tagPlayerID)
    __DoPlayerLeaveFamilyByID(curFamily, 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
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
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
#===============================================================================
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] = []
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" # 消费返利活动
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
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,7 +700,7 @@
#  @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
@@ -707,7 +708,6 @@
    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,6 +809,8 @@
        if PetControl.IsPet(attacker) or attacker.GetGameNPCObjType()== IPY_GameWorld.gnotSummon:
            #击杀次数判断
            if not CheckKillNPCByCnt(attacker, defender, False):
                ownerPlayer = GetAttackPlayer(attacker)[0]
                if ownerPlayer and not NPCHurtManager.IsAssistPlayer(ownerPlayer.GetPlayerID(), defender):
                return False
            
            #仙盟归属NPC判断
@@ -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
    
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)
    #添加技能伤害通知列表,(用于攻击结束,统一通知客户端)
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)
#---------------------------------------------------------------------
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
    
    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))
            
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:
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,6 +4012,8 @@
        curNPC = self.__Instance
        self.__FeelPlayerList = []
        
        npcHurtList = NPCHurtManager.GetPlayerHurtList(curNPC)
        if not npcHurtList:
        npcHurtList = curNPC.GetPlayerHurtList()
        #npcHurtList.Sort()  #这里不排序,只要有伤害就算
        
@@ -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
                    
@@ -4737,11 +4514,6 @@
        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,19 +4681,6 @@
        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 类实例
@@ -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,13 +5148,9 @@
            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)
@@ -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()
                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)
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/NPC/NPCHurtManager.py
New file
@@ -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)
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/PlayerAssist.py
New file
@@ -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
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,7 +2352,8 @@
def __GetBossShuntLineID(curPlayer, curMapID, curLineID, tagMapID, npcID, lineIDList):
    '''获取目标地图boss分流线路
                根据人数分流玩家,优先分配到活着的线路
                队伍无视任何规则,默认分配到队伍队员多的那条线
                队伍默认分配到队伍队员多的那条线
                协助默认分配到目标玩家线路,优先级最高
                
                前端:
        1.在中立地图的时候,显示当前线路BOSS的状态
@@ -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
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:
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)
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/RemoteQuery/GY_Query_PlayerAssist.py
New file
@@ -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
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, ...], ...}
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" # 消费返利活动