hxp
2025-08-25 912176de9ed5b45e5fe0edbb15b8796f54c56ba2
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/NPC/NPCCommon.py
@@ -42,18 +42,12 @@
import FBCommon
import PlayerActivity
import PlayerSuccess
import BossHurtMng
import PlayerPrestigeSys
import GY_Query_BossFirstKill
import GameLogic_FamilyInvade
import GameLogic_GatherSoul
import FormulaControl
import PlayerBossReborn
import PlayerFairyCeremony
import PlayerCrossYaomoBoss
import PlayerActCollectWords
import PlayerNewFairyCeremony
import GameLogic_CrossGrassland
import PlayerActGarbageSorting
import PlayerActBossTrial
import PlayerTongTianLing
@@ -63,18 +57,14 @@
import PlayerGoldInvest
import PlayerWeekParty
import NPCRealmRefresh
import NPCHurtManager
import PlayerActLogin
import PlayerActTask
import PlayerZhanling
import FamilyRobBoss
import IpyGameDataPY
import PlayerGubao
import PlayerState
import TurnAttack
import PyGameData
import PlayerTeam
import NPCHurtMgr
import PlayerVip
import GameObj
import ChNPC
@@ -128,12 +118,12 @@
            return realmNPCIpyData.GetLV()
    return curNPC.GetLV()
def GetNPCDataEx(npcID):
    ## 获取NPC扩展数据表,可热更
    npcDataEx = IpyGameDataPY.GetIpyGameDataNotLog("NPCEx", npcID)
def GetNPCDataPy(npcID):
    ## 获取NPC数据表,py自定义的表,可热更
    npcDataEx = IpyGameDataPY.GetIpyGameDataNotLog("NPC", npcID)
    if not npcDataEx:
        if False: # 不可能成立的条件,只为了 . 出代码提示
            npcDataEx = IpyGameDataPY.IPY_NPCEx()
            npcDataEx = IpyGameDataPY.IPY_NPC()
        return npcDataEx
    return npcDataEx
@@ -141,15 +131,13 @@
def SetRealmLV(curNPC, realmLV): return curNPC.SetMAtkMin(realmLV)      # NPC表中此字段含义改成境界等级
def GetIsLVSuppress(curNPC): return curNPC.GetWindDef() # 风防代表是否等级压制
def GetFightPowerLackAtkLimit(curNPC): # 战力不足限制攻击,默认不限制
    npcDataEx = GetNPCDataEx(curNPC.GetNPCID())
    return npcDataEx.GetFightPowerLackAtkLimit() if npcDataEx else 0
def GetSuppressFightPower(curNPC):
    npcDataEx = GetNPCDataEx(curNPC.GetNPCID())
    return npcDataEx.GetSuppressFightPower() if npcDataEx else curNPC.GetThunderDef() # 雷防代表压制战力
    return 0
def GetSuppressFightPower(curNPC): # 压制战力
    return 0
def SetSuppressFightPower(curNPC, value): return curNPC.SetThunderDef(min(value, ShareDefine.Def_UpperLimit_DWord))
def GetCommendFightPower(curNPC): return curNPC.GetFireDef() # 火防代表推荐战力
def GetDropOwnerType(curNPC): return curNPC.GetThunderAtk() # 雷攻代表掉落归属类型
def GetFaction(curNPC): return curNPC.GetCountry()
def GetFaction(curNPC): return GameObj.GetFaction(curNPC)
def GetSkillAtkRate(curNPC): return curNPC.GetPoisionAtk() # 毒攻代表NPC技能伤害加成万分率
def GetFinalHurt(curNPC): return curNPC.GetFireAtk() # 火攻代表NPC最终固定伤害加成, 普攻也有效果
def SetFinalHurt(curNPC, hurt): return curNPC.SetFireAtk(hurt) # 火攻代表NPC最终固定伤害加成, 普攻也有效果
@@ -178,13 +166,10 @@
    gameFB = GameWorld.GetGameFB()
    
    if strengthenIpyData.GetIsStrengthenByPlayerCount():
        if FamilyRobBoss.IsHorsePetRobBoss(npcID):
            strengthenPlayerCnt = GameWorld.GetGameWorld().GetGameWorldDictByKey(ShareDefine.Def_Notify_WorldKey_HorsePetRobBossPlayerCount)
        else:
            strengthenPlayerCnt = gameFB.GetGameFBDictByKey(ChConfig.Def_FB_NPCStrengthenPlayerCnt)
            if not strengthenPlayerCnt:
                GameWorld.ErrLog("NPC配置了按玩家人数成长类型,但是无法获取到对应的玩家人数!npcID=%s" % (npcID))
                return
        strengthenPlayerCnt = gameFB.GetGameFBDictByKey(ChConfig.Def_FB_NPCStrengthenPlayerCnt)
        if not strengthenPlayerCnt:
            GameWorld.ErrLog("NPC配置了按玩家人数成长类型,但是无法获取到对应的玩家人数!npcID=%s" % (npcID))
            return
            
    lvStrengthenType = strengthenIpyData.GetLVStrengthenType()
    # 根据世界等级
@@ -231,7 +216,7 @@
    if "LV" in npcFBAttrDict:
        strengthenLV = npcFBAttrDict["LV"]
        
    attrDict = GetNPCStrengthenAttrDict(npcID, strengthenLV, strengthenPlayerCnt, strengthenIpyData)
    attrDict = {} #GetNPCStrengthenAttrDict(npcID, strengthenLV, strengthenPlayerCnt, strengthenIpyData)
    attrDict.update(npcFBAttrDict) # 如果副本有指定属性,则以副本为主
    if not attrDict:
        return
@@ -299,151 +284,6 @@
    FBLogic.OnRandomRobotJob(curNPC, lineRobotJobDict)
    return
def __DoGiveVSPlayerNPCSkill(curNPC, job, npcLV):
    skillManager = curNPC.GetSkillManager()
    jobSkillDict = IpyGameDataPY.GetFuncEvalCfg("XMZZRobotSkill", 1)
    if job not in jobSkillDict:
        return
    skillInfoDict = jobSkillDict[job]
    #{1:{(100, 101, 102, 103):1, 50000:100, 50100:200, 50400:300}, 2:{(200, 201, 202, 203):1, 55000:100, 55100:200, 55200:300}}
    skillIDList = []
    for skillInfo, needLV in skillInfoDict.items():
        if npcLV < needLV:
            continue
        if isinstance(skillInfo, int):
            skillIDList.append(skillInfo)
        else:
            skillIDList += list(skillInfo)
    GameWorld.DebugLog("给NPC技能: job=%s,npcLV=%s, %s" % (job, npcLV, skillIDList))
    for skillID in skillIDList:
        skillManager.LearnSkillByID(skillID)
    return
def GetNPCStrengthenAttrDict(npcID, strengthenLV=0, strengthenPlayerCnt=0, strengthenIpyData=None):
    if not strengthenLV and not strengthenPlayerCnt:
        return {}
    npcData = GameWorld.GetGameData().FindNPCDataByID(npcID)
    if not npcData:
        return {}
    attrStrengthenInfo = ReadChConfig.GetEvalChConfig("NPCAttrStrengthen")
    if not attrStrengthenInfo:
        return {}
    attrDict = {}
    paramDict = attrStrengthenInfo[NPCAttr_ParamDict] # 过程参数公式字典
    attrStrengthenDict = attrStrengthenInfo[NPCAttr_AttrStrengthenList] # 属性成长公式字典
    playerCntCoefficient = attrStrengthenInfo[NPCAttr_PlayerCntCoefficient] # 人数系数
    npcIDPlayerCntCoefficient = attrStrengthenInfo[NPCAttr_NPCPlayerCntCoefficient] # 特殊NPC人数系数
    baseMaxHP = GameObj.GetHP(npcData) # NPCData 没有提供Max接口,对应使用GetHP
    if strengthenLV:
        if not strengthenIpyData:
            strengthenIpyData = IpyGameDataPY.GetIpyGameDataNotLog("NPCStrengthen", npcID)
        if not strengthenIpyData:
            return {}
        playerCurLVIpyData = PlayerControl.GetPlayerLVIpyData(strengthenLV) # 取增强怪物等级对应此等级的玩家参考属性值
        if not playerCurLVIpyData:
            return {}
        # NPC表可用参数
        SkillAtkRate = GetSkillAtkRate(npcData) # 技能伤害
        FinalHurt = GetFinalHurt(npcData)
        # 参考玩家属性参数
        ReMaxHP = playerCurLVIpyData.GetReMaxHP() # 最大生命值
        ReAtk = playerCurLVIpyData.GetReAtk() # 攻击(最小、最大攻击)
        ReDef = playerCurLVIpyData.GetReDef() # 防御
        ReHit = playerCurLVIpyData.GetReHit() # 命中
        ReMiss = playerCurLVIpyData.GetReMiss() # 闪避
        ReAtkSpeed = playerCurLVIpyData.GetReAtkSpeed() # 攻击速度
        ReSkillAtkRate = playerCurLVIpyData.GetReSkillAtkRate() # 技能伤害比例
        ReDamagePer = playerCurLVIpyData.GetReDamagePer() # 增加伤害
        ReDamReduce = playerCurLVIpyData.GetReDamReduce() # 减少伤害
        ReIgnoreDefRate = playerCurLVIpyData.GetReIgnoreDefRate() # 无视防御比例
        ReLuckyHitRate = playerCurLVIpyData.GetReLuckyHitRate() # 会心一击率
        ReLuckyHit = playerCurLVIpyData.GetReLuckyHit() # 会心一击伤害
        ReBleedDamage = playerCurLVIpyData.GetReBleedDamage() # 流血伤害增加
        ReIceAtk = playerCurLVIpyData.GetReIceAtk() # 真实伤害
        ReIceDef = playerCurLVIpyData.GetReIceDef() # 真实抵御
        RePetAtk = playerCurLVIpyData.GetRePetAtk() # 灵宠攻击
        RePetSkillAtkRate = playerCurLVIpyData.GetRePetSkillAtkRate() # 灵宠技能
        RePetDamPer = playerCurLVIpyData.GetRePetDamPer() # 灵宠伤害增加
        ReFinalHurt = playerCurLVIpyData.GetReFinalHurt() # 固定伤害增加
        ReFinalHurtReduce = playerCurLVIpyData.GetReFinalHurtReduce() # 固定伤害减少
        RePotionReply = playerCurLVIpyData.GetRePotionReply() # 血瓶恢复量
        RePotionCD = playerCurLVIpyData.GetRePotionCD() # ѪƿCD
        ReFightPower = playerCurLVIpyData.GetReFightPower() # 战斗力
        # 增加NPC属性参数
        HitTime = strengthenIpyData.GetHitTime() # 怪物受击次数
        DefCoefficient = strengthenIpyData.GetDefCoefficient() # 人物防御系数
        AtkCoefficient = strengthenIpyData.GetAtkCoefficient() # 人物攻击系数
        AdjustCoefficient = strengthenIpyData.GetAdjustCoefficient() # 调整系数比例
        AtkInterval = strengthenIpyData.GetAtkInterval() # 怪物攻击间隔
        HitRate = strengthenIpyData.GetHitRate() # 对人物的命中率
        MissRate = strengthenIpyData.GetMissRate() # 对人物的闪避率
        MonterNum = strengthenIpyData.GetMonterNum() # 怪物数
        IceAtkCoefficient = strengthenIpyData.GetIceAtkCoefficient() # 元素攻击比例
        IceDefCoefficient = strengthenIpyData.GetIceDefCoefficient() # 元素抗性比例
        MaxEnduranceTime = strengthenIpyData.GetMaxEnduranceTime() # 玩家最大承受伤害时间
        FightPowerCoefficient = strengthenIpyData.GetFightPowerCoefficient() # 压制战斗力系数
        # 过程参数
        AtkReplyCoefficient = eval(FormulaControl.GetCompileFormula("NPCParam_AtkReplyCoefficient",
                                                                    paramDict["AtkReplyCoefficient"])) # 怪物攻击回复调整值
        MonterHurt = eval(FormulaControl.GetCompileFormula("NPCParam_MonterHurt", paramDict["MonterHurt"])) # 怪物固定伤害
        LostHPPerSecond = eval(FormulaControl.GetCompileFormula("NPCParam_LostHPPerSecond", paramDict["LostHPPerSecond"])) # 玩家每秒掉血量
        LVStrengthenMark = strengthenIpyData.GetLVStrengthenMark()
        attrStrengthenList = attrStrengthenDict.get(LVStrengthenMark, [])
        for attrKey, strengthenFormat in attrStrengthenList:
            strengthenValue = int(eval(FormulaControl.GetCompileFormula("NPCStrengthen_%s_%s" % (attrKey,LVStrengthenMark), strengthenFormat)))
            #GameWorld.DebugLog("    %s=%s" % (attrKey, strengthenValue))
            locals()[attrKey] = strengthenValue # 创建该属性局部变量作为参数提供给后面属性计算时用
            attrDict[attrKey] = strengthenValue
        # 当战力系数为0时,NPC战力默认为NPC表压制战力
        if FightPowerCoefficient:
            attrDict["FightPower"] = int(ReFightPower * FightPowerCoefficient / 10000.0)
    if strengthenPlayerCnt:
        mapID = GameWorld.GetMap().GetMapID()
        dataMapID = FBCommon.GetRecordMapID(mapID)
        formulaKey = "MapCoefficient_%s" % mapID
        playerCntAttrCoefficient = playerCntCoefficient.get(mapID, {})
        if not playerCntAttrCoefficient and dataMapID in playerCntCoefficient:
            playerCntAttrCoefficient = playerCntCoefficient[dataMapID]
            formulaKey = "MapCoefficient_%s" % dataMapID
        if npcID in npcIDPlayerCntCoefficient:
            playerCntAttrCoefficient = npcIDPlayerCntCoefficient[npcID]
            formulaKey = "NPCCoefficient_%s" % npcID
        for attrKey, coefficientDict in playerCntAttrCoefficient.items():
            if attrKey in attrDict:
                attrValue = attrDict[attrKey]
            elif attrKey == "MaxHP":
                attrValue = baseMaxHP
            else:
                attrFuncName = "Get%s" % attrKey
                if not hasattr(npcData, attrFuncName):
                    continue
                attrValue = getattr(npcData, attrFuncName)()
            # 按字典配置
            if isinstance(coefficientDict, dict):
                coefficient = GameWorld.GetDictValueByRangeKey(coefficientDict, strengthenPlayerCnt, 1)
            # 按公式配置
            elif isinstance(coefficientDict, str):
                formulaKey = "%s_%s" % (formulaKey, attrKey)
                coefficient = eval(FormulaControl.GetCompileFormula(formulaKey, coefficientDict))
            else:
                coefficient = 1
            attrDict[attrKey] = int(attrValue * coefficient)
    #GameWorld.DebugLog("计算NPC属性成长: npcID=%s,strengthenLV=%s,strengthenPlayerCnt=%s,baseMaxHP=%s,attrDict=%s"
    #                   % (npcID, strengthenLV, strengthenPlayerCnt, baseMaxHP, attrDict))
    return attrDict
def GiveKillNPCDropPrize(curPlayer, mapID, npcCountDict, exp_rate=None, mailTypeKey=None, isMail=False, 
                         extraItemList=[], prizeMultiple=1, dropItemMapInfo=[], curGrade=0, isVirtualDrop=False):
    '''给玩家击杀NPC掉落奖励
@@ -458,975 +298,19 @@
    @param curGrade: 评级
    @param isVirtualDrop: 是否给物品虚拟掉落表现
    '''
    if not exp_rate:
        exp_rate = PlayerControl.GetPlayerExpRate(curPlayer)
    npcID = 0
    totalExp = 0
    totalMoney = 0
    itemCountDict = {}
    auctionItemIDList = []
    if prizeMultiple > 1:
        hadDropItemKeyList, hadDropItemPlaceList = [], [] # 已经掉落过的物品集合key列表, 已经掉落过的装备部位列表
        mPrizeItemIDList = IpyGameDataPY.GetFuncEvalCfg("SealDemonDoubleDrop", 1) # 允许多倍奖励的物品ID列表
        mPrizePlaceList = IpyGameDataPY.GetFuncEvalCfg("SealDemonDoubleDrop", 2) # 允许多倍奖励的装备部位
        mPrizeItemKeyList = IpyGameDataPY.GetFuncEvalCfg("SealDemonDoubleDrop", 3) # 允许多倍奖励的物品ID集合
        itemKeyDict = IpyGameDataPY.GetFuncEvalCfg("JobItemDropSets") # {物品ID集合key:[职业顺序物品ID列表], ...}
        itemIDKeyDict = {}
        for itemKey, itemIDList in itemKeyDict.items():
            for itemID in itemIDList:
                itemIDKeyDict[itemID] = itemKey
    for npcID, count in npcCountDict.items():
        baseExp = GetNPCExp(curPlayer, npcID)
        addExp = int(baseExp * exp_rate / float(ChConfig.Def_MaxRateValue)) # 基础加成
        totalCount = count * prizeMultiple
        totalExp += (addExp * totalCount)
        # 掉落有概率因素,需多次执行
        for dCount in xrange(1, totalCount + 1):
            isKillCountDrop = dCount == 1 # 同一只NPC一次处理中多次击杀的情况,只算一次附加装备处理
            dropInfo = GetNPCDropInfo(curPlayer, mapID, npcID, isKillCountDrop=isKillCountDrop, curGrade=curGrade)
            if not dropInfo:
                continue
            dropIDList, auctionIDList, dropMoneyCnt, moneyValue = dropInfo
            totalMoney += (dropMoneyCnt * moneyValue)
            for itemID in dropIDList:
                if prizeMultiple > 1:
                    itemData = GameWorld.GetGameData().GetItemByTypeID(itemID)
                    if not itemData:
                        continue
                    itemPlace = itemData.GetEquipPlace()
                    itemKey = itemIDKeyDict.get(itemID)
                    # 超过指定最大只数时,代表多倍奖励,多倍奖励的物品只允许给指定规则内的物品,只对已经掉过的物品有限制
                    if dCount > count:
                        if ItemCommon.GetIsEquip(itemData):
                            if itemPlace in hadDropItemPlaceList and itemPlace not in mPrizePlaceList:
                                GameWorld.DebugLog("    多倍奖励不能给的物品部位: itemID=%s,itemPlace=%s" % (itemID, itemPlace))
                                continue
                        elif itemKey != None:
                            if itemKey in hadDropItemKeyList and itemKey not in mPrizeItemKeyList:
                                GameWorld.DebugLog("    多倍奖励不能给的物品ID集合: itemID=%s,itemKey=%s" % (itemID, itemKey))
                                continue
                        else:
                            if itemID in itemCountDict and itemID not in mPrizeItemIDList:
                                GameWorld.DebugLog("    多倍奖励不能给的物品ID: itemID=%s" % (itemID))
                                continue
                    if itemPlace and itemPlace not in hadDropItemPlaceList:
                        hadDropItemPlaceList.append(itemPlace)
                    if itemKey != None and itemKey not in hadDropItemKeyList:
                        hadDropItemKeyList.append(itemKey)
                itemCountDict[itemID] = itemCountDict.get(itemID, 0) + 1
                if itemID in auctionIDList and itemID not in auctionItemIDList:
                    auctionItemIDList.append(itemID)
    # 固定附加物品
    for itemID, itemCount, isAuctionItem in extraItemList:
        itemCountDict[itemID] = itemCountDict.get(itemID, 0) + itemCount * prizeMultiple
        if isAuctionItem and itemID not in auctionItemIDList:
            auctionItemIDList.append(itemID)
    needSpace = 0
    prizeItemList = []
    jsonItemList = []
    for itemID, itemCount in itemCountDict.items():
        itemData = GameWorld.GetGameData().GetItemByTypeID(itemID)
        if not itemData:
            continue
        isAuctionItem = itemID in auctionItemIDList
        if ItemCommon.GetIsEquip(itemData):
            for _ in xrange(itemCount):
                curItem = ItemControler.GetOutPutItemObj(itemID, 1, isAuctionItem, curPlayer=curPlayer)
                if curItem:
                    needSpace += 1
                    prizeItemList.append(curItem)
                    jsonItemList.append(ItemCommon.GetJsonItem(curItem))
        else:
            needSpace += ItemControler.GetItemNeedPackCount(IPY_GameWorld.rptItem, itemData, itemCount, isAuctionItem)
            prizeItemList.append([itemID, itemCount, isAuctionItem])
            jsonItemList.append(ItemCommon.GetJsonItem([itemID, itemCount, isAuctionItem]))
        #成就
        if not dropItemMapInfo:
            PlayerSuccess.DoAddSuccessProgress(curPlayer, ShareDefine.SuccType_PickUpItem, itemCount, [itemID])
    ## 直接掉地板上
    if dropItemMapInfo:
        dropPosX, dropPosY = dropItemMapInfo[:2]
        isOnlySelfSee = dropItemMapInfo[2] if len(dropItemMapInfo) > 2 else False # 是否仅自己可见
        isDropDisperse = dropItemMapInfo[3] if len(dropItemMapInfo) > 3 else False # 堆叠的物品是否散开掉落
        ## 虚拟掉落表现
        if isVirtualDrop:
            DoGiveItemByVirtualDrop(curPlayer, prizeItemList, npcID, dropPosX, dropPosY, isDropDisperse, mailTypeKey)
        else:
            DoMapDropPrizeItem(curPlayer, prizeItemList, npcID, dropPosX, dropPosY, isDropDisperse, isOnlySelfSee)
    ## 发邮件 或 背包空间不足
    elif isMail or needSpace > ItemCommon.GetItemPackSpace(curPlayer, IPY_GameWorld.rptItem, needSpace):
        mailItemList = []
        for prizeItem in prizeItemList:
            if isinstance(prizeItem, list):
                mailItemList.append(prizeItem)
            else:
                mailItemList.append(ItemCommon.GetMailItemDict(prizeItem))
                prizeItem.Clear() # 发邮件已经创建的物品实例要清空, 不然会导致内存泄露
        #if totalExp:
        #    expItemID = 0
        #    mailItemList.append([expItemID, totalExp, 1])
        PlayerControl.SendMailByKey(mailTypeKey, [curPlayer.GetPlayerID()], mailItemList, silver=totalMoney)
    ## 直接放入背包
    else:
        event = [ChConfig.ItemGive_NPCDrop, False, {"NPCID":npcID}]
        for prizeItem in prizeItemList:
            if isinstance(prizeItem, list):
                itemID, itemCount, isAuctionItem = prizeItem
                ItemControler.GivePlayerItem(curPlayer, itemID, itemCount, isAuctionItem, [IPY_GameWorld.rptItem],
                                             event=event)
            else:
                ItemControler.DoLogic_PutItemInPack(curPlayer, prizeItem, event=event)
        if totalExp:
            PlayerControl.PlayerControl(curPlayer).AddExp(totalExp)
        if totalMoney:
            PlayerControl.GiveMoney(curPlayer, IPY_GameWorld.TYPE_Price_Silver_Money, totalMoney)
    #GameWorld.DebugLog("给玩家击杀NPC掉落奖励:  mapID=%s,npcCountDict=%s,exp_rate=%s,mailTypeKey=%s,isMail=%s,extraItemList=%s"
    #                   % (mapID, npcCountDict, exp_rate, mailTypeKey, isMail, extraItemList))
    #GameWorld.DebugLog("    totalExp=%s,totalMoney=%s,needSpace=%s,jsonItemList=%s" % (totalExp, totalMoney, needSpace, jsonItemList))
    return jsonItemList, totalExp, totalMoney
def DoMapDropPrizeItem(curPlayer, prizeItemList, npcID, dropPosX, dropPosY, isDropDisperse=True, isOnlySelfSee=True):
    ## 奖励物品真实掉落地图,先拆开分散再掉落
    if isDropDisperse:
        dropItemList = []
        for itemInfo in prizeItemList:
            if isinstance(itemInfo, list):
                itemID, itemCount, isAuctionItem = itemInfo
                for _ in xrange(itemCount):
                    dropItemList.append([itemID, 1, isAuctionItem])
            else:
                dropItemList.append(itemInfo)
    else:
        dropItemList = prizeItemList
    index = 0
    playerID = curPlayer.GetPlayerID()
    gameMap = GameWorld.GetMap()
    sightLevel = PlayerControl.GetMapRealmDifficulty(curPlayer)
    for posX, posY in ChConfig.Def_DropItemAreaMatrix:
        resultX = dropPosX + posX
        resultY = dropPosY + posY
        if not gameMap.CanMove(resultX, resultY):
            #玩家不可移动这个点
            continue
        if index > len(dropItemList) - 1:
            break
        curItem = dropItemList[index]
        index += 1
        if isinstance(curItem, list):
            itemID, itemCount, isAuctionItem = curItem
            curItem = ItemControler.GetOutPutItemObj(itemID, itemCount, isAuctionItem, curPlayer=curPlayer)
        if not curItem:
            continue
        ChItem.AddMapDropItem(resultX, resultY, curItem, ownerInfo=[ChConfig.Def_NPCHurtTypePlayer, playerID],
                              dropNPCID=npcID, isOnlySelfSee=isOnlySelfSee, sightLevel=sightLevel)
    return
def DoGiveItemByVirtualDrop(curPlayer, giveItemList, npcID, dropPosX=0, dropPosY=0, isDropDisperse=True, mailTypeKey="ItemNoPickUp", extraVirtualItemList=[]):
    ## 给物品并且做假掉落表现,直接先堆叠给物品,再拆开做虚假掉落表现
    if not giveItemList:
        return
    mapID = PlayerControl.GetCustomMapID(curPlayer)
    lineID = PlayerControl.GetCustomLineID(curPlayer)
    if not mapID:
        mapID = GameWorld.GetGameWorld().GetMapID()
    playerID = curPlayer.GetPlayerID()
    giveItemObjList = []
    virtualItemDropList = []
    itemControl = ItemControler.PlayerItemControler(curPlayer)
    for itemInfo in giveItemList:
        if isinstance(itemInfo, list) or isinstance(itemInfo, tuple):
            itemID, itemCount, isAuctionItem = itemInfo
            curItem = ItemControler.GetOutPutItemObj(itemID, itemCount, isAuctionItem, curPlayer=curPlayer)
            if not curItem:
                continue
        elif hasattr(itemInfo, "GetItemTypeID"):
            curItem = itemInfo
            itemID = curItem.GetItemTypeID()
            itemCount = curItem.GetCount()
            isAuctionItem = ItemControler.GetIsAuctionItem(curItem)
        else:
            continue
        dropItemDataStr = ChItem.GetMapDropItemDataStr(curItem)
        giveItemObjList.append(curItem)
        # 散开掉落
        if isDropDisperse:
            for _ in xrange(itemCount):
                virtualItemDropList.append([itemID, dropItemDataStr])
        else:
            virtualItemDropList.append([itemID, dropItemDataStr])
    # 先通知掉落,再给物品,因为前端表现弹框需要这个顺序需求
    if extraVirtualItemList: #只显示假掉落
        for itemInfo in extraVirtualItemList:
            itemID, itemCount, isAuctionItem = itemInfo
            curItem = ItemControler.GetOutPutItemObj(itemID, itemCount, isAuctionItem, curPlayer=curPlayer)
            if not curItem:
                continue
            dropItemDataStr = ChItem.GetMapDropItemDataStr(curItem)
            # 散开掉落
            if isDropDisperse:
                for _ in xrange(itemCount):
                    virtualItemDropList.append([itemID, dropItemDataStr])
            else:
                virtualItemDropList.append([itemID, dropItemDataStr])
            curItem.Clear()
    gameMap = GameWorld.GetMap()
    index = 0
    for posX, posY in ChConfig.Def_DropItemAreaMatrix:
        if dropPosX or dropPosY:
            resultX = dropPosX + posX
            resultY = dropPosY + posY
            if not gameMap.CanMove(resultX, resultY):
                #玩家不可移动这个点
                continue
        else:
            resultX, resultY = 0, 0
        if index > len(virtualItemDropList) - 1:
            break
        itemID, dropItemDataStr = virtualItemDropList[index]
        index += 1
        SendVirtualItemDrop(curPlayer, itemID, resultX, resultY, dropItemDataStr)
    # 再给物品
    mailItemList = []
    for itemObj in giveItemObjList:
        itemID = itemObj.GetItemTypeID()
        mailItem = ItemCommon.GetMailItemDict(itemObj)
        equipInfo = [itemObj.GetEquipPlace(), ItemCommon.GetItemClassLV(itemObj), itemObj.GetItemColor(),
                     itemObj.GetSuiteID(), itemObj.GetUserData()]
        packIndex = ChConfig.GetItemPackType(itemObj)
        if not itemControl.PutInItem(packIndex, itemObj, event=[ChConfig.ItemGive_Pickup, False, {"NPCID":npcID}]):
            mailItemList.append(mailItem)
        if npcID:
            SendGameServerGoodItemRecord(curPlayer, mapID, lineID, npcID, itemID, equipInfo)
    # 放不下的发邮件
    if mailItemList:
        PlayerControl.SendMailByKey(mailTypeKey, [playerID], mailItemList, [mapID])
    return
################################### NPC掉落 ###################################
Def_NPCMaxDropRate = 1000000 # NPC掉落相关的最大概率, 数值设定
def GetNPCDropIpyData(npcID):
    ipyDataList = IpyGameDataPY.GetIpyGameDataListNotLog("NPCDropItem", npcID)
    if not ipyDataList:
        GameWorld.DebugLog("该NPC没配置掉落!npcID=%s" % npcID)
        return
    ipyDrop = None
    maxWorldLV = 0
    curWorldLV = GameWorld.GetGameWorld().GetGameWorldDictByKey(ShareDefine.Def_Notify_WorldKey_WorldAverageLv)
    if len(ipyDataList) == 1:
        ipyDrop = ipyDataList[0]
        maxWorldLV = ipyDrop.GetMaxWorldLV()
        if maxWorldLV and curWorldLV > maxWorldLV:
            GameWorld.DebugLog("该NPC当前世界等级无法掉落物品!npcID=%s,curWorldLV(%s) > maxWorldLV(%s)" % (npcID, curWorldLV, maxWorldLV))
            return
    else:
        for ipyData in ipyDataList:
            maxWorldLV = ipyData.GetMaxWorldLV()
            if curWorldLV <= maxWorldLV:
                ipyDrop = ipyData
                break
        if not ipyDrop:
            GameWorld.DebugLog("该NPC当前世界等级无法掉落物品!npcID=%s,curWorldLV=%s,maxWorldLV=%s" % (npcID, curWorldLV, maxWorldLV))
            return
    return ipyDrop
def GetNPCDropInfo(dropPlayer, mapID, npcID, ownerPlayerList=[], ipyDrop=None, isSingle=True, isKillCountDrop=True, curGrade=0):
    '''获取NPC掉落信息, 击杀及扫荡通用,调用该函数获得掉落信息,然后再看掉落地板上还是直接放入背包
        @param dropPlayer: 用于判断调用相关用的玩家示例,该玩家并不一定是击杀者,只是按一定规则设定的掉落判断依据的玩家
                            如队伍,取等级最大的玩家,该玩家并不一定是击杀者
        @param mapID: 掉落物品所属地图,注意此地图并不一定是当前地图,如扫荡时需使用目标副本地图
        @param npcID: 掉落物品的NPCID
        @param ownerPlayerList: 有归属的玩家列表
        @param isSingle: 是否只处理单独玩家掉落逻辑,一般都默认True,目前只有场景杀怪掉落需要考虑摸怪的情况下才为False(外层特殊处理)
        @return: dropIDList, auctionIDList, dropMoneyCnt, moneyValue
                None-没有掉落
                ---------------
                dropIDList        -    掉落的物品ID列表, 同个itemID可能在列表中有多个 [itemID, itemID, ...]
                auctionIDList    -    掉落的拍品物品ID列表, [itemID, itemID, ...]
                dropMoneyCnt      -    掉落金币位置数
                moneyValue        -    每个位置的金币数量
    '''
    if not ipyDrop:
        ipyDrop = GetNPCDropIpyData(npcID)
        if not ipyDrop:
            return
    if not ownerPlayerList:
        ownerPlayerList = [dropPlayer]
    playerID = dropPlayer.GetPlayerID()
    playerLV = dropPlayer.GetLV()
    maxDropLV = ipyDrop.GetMaxDropLV()
    realmNPCIpyData = None
    realmMapIDList = IpyGameDataPY.GetFuncEvalCfg("RealmDifficulty", 1)
    realmDifficulty = PlayerControl.GetRealmDifficulty(dropPlayer)
    if mapID in realmMapIDList and realmDifficulty:
        realmLV = PlayerControl.GetDifficultyRealmLV(realmDifficulty)
        realmNPCIpyData = IpyGameDataPY.GetIpyGameDataNotLog("NPCRealmStrengthen", npcID, realmLV)
        if realmNPCIpyData:
            maxDropLV = realmNPCIpyData.GetMaxDrapLV()
    if maxDropLV and playerLV > maxDropLV:
        GameWorld.DebugLog("超过最大可掉落等级,不掉落物品!npcID=%s,playerLV(%s) > maxDropLV(%s)" % (npcID, playerLV, maxDropLV))
        return
    npcData = GameWorld.GetGameData().FindNPCDataByID(npcID)
    if not npcData:
        GameWorld.ErrLog("获取NPC掉落配置错误!表不存在该NPCID=%s" % npcID, playerID)
        return
    tianxuanState = False # 是否有天玄额外掉落状态
    tianxuanBuff = SkillCommon.FindBuffByID(dropPlayer, ChConfig.Def_SkillID_TianxuanBuff)[0]
    curGrade = curGrade if curGrade else GameWorld.GetGameFB().GetGameFBDictByKey(ChConfig.Def_FB_Grade)
    dropIDList = [] # 掉落的ID列表
    auctionIDList = []
    dropMoneyCnt, moneyValue = 0, 0
    itemJobList = [dropPlayer.GetJob()] if ipyDrop.GetIsDropJobSelf() else IpyGameDataPY.GetFuncEvalCfg("OpenJob", 1) # 掉落装备职业列表
    # 通用掉率相关
    gameFB = GameWorld.GetGameFB()
    doCountAdd = gameFB.GetGameFBDictByKey(ChConfig.Def_FB_DropDoCountAdd)
    doCountRate = gameFB.GetGameFBDictByKey(ChConfig.Def_FB_DropDoCountRate) # 直接覆盖基值, 无值时取默认值
    if not doCountRate:
        doCountRate = ChConfig.Def_MaxRateValue
    # 归属者相关信息
    tagJob = 0
    dropEquipKillCountPub = 0 # 装备附加掉落综合击杀次数,取次数最大的那个,至少第一次
    dropItemIDKillCountPub = 0 # 物品ID附加掉落综合击杀次数,取次数最大的那个,至少第一次
    equipDropRatePlus = 0 # 装备掉落概率提升
    equipDropDoCountPlus = 0 # 装备掉落执行次数提升
    for ownerPlayer in ownerPlayerList:
        killCountValue = ownerPlayer.NomalDictGetProperty(ChConfig.Def_PDict_NPCKillCount % npcID)
        equipPubKillCount = killCountValue % 100 + 1
        itemIDPubKillCount = killCountValue % 10000 / 100 + 1
        if dropEquipKillCountPub < equipPubKillCount:
            dropEquipKillCountPub = equipPubKillCount
            tagJob = ownerPlayer.GetJob()
        if dropItemIDKillCountPub < itemIDPubKillCount:
            dropItemIDKillCountPub = itemIDPubKillCount
        equipDropRatePlus = max(equipDropRatePlus, PlayerControl.GetDropEquipPer(ownerPlayer))
        equipDropDoCountPlus = max(equipDropDoCountPlus, PlayerControl.GetDropEquipDoCount(ownerPlayer))
    dropRatePlusValue = ipyDrop.GetCanDropRatePlus()
    if not dropRatePlusValue & pow(2, 0):
        equipDropRatePlus = 0
    if not dropRatePlusValue & pow(2, 1):
        equipDropDoCountPlus = 0
    #GameWorld.DebugLog("NPC掉落: npcID=%s,itemJobList=%s,dropRatePlusValue=%s,equipDropRatePlus=%s,equipDropDoCountPlus=%s,doCountRate=%s,doCountAdd=%s"
    #                   % (npcID, itemJobList, dropRatePlusValue, equipDropRatePlus, equipDropDoCountPlus, doCountRate, doCountAdd), playerID)
    dropEquipInfoList = [] # [(阶,颜色), ...]
    # 1. 装备库 - 饼图概率
    pieRateDoCnt = ipyDrop.GetPieRateDoCnt()
    if pieRateDoCnt:
        pieRateDoCnt = __GetNPCDropDoCountChange(pieRateDoCnt, doCountRate + equipDropDoCountPlus, doCountAdd)
        dropEquipInfoList += __GetNPCPieRateEquipDrop(ipyDrop, pieRateDoCnt, equipDropRatePlus)
    # 2. 装备库 -  独立概率
    indepRateDoCnt = ipyDrop.GetIndepRateDoCnt()
    if indepRateDoCnt:
        indepRateDoCnt = __GetNPCDropDoCountChange(indepRateDoCnt, doCountRate + equipDropDoCountPlus, doCountAdd)
        dropEquipInfoList += __GetNPCIndepRateEquipDrop(mapID, ipyDrop, indepRateDoCnt, equipDropRatePlus, curGrade)
    #GameWorld.DebugLog("阶,颜色 key,dropEquipInfoList=%s" % (dropEquipInfoList))
    # 天玄丹
    tianxuanEquipRateList = ipyDrop.GetTianxuanEquipRateList()
    if tianxuanBuff and tianxuanEquipRateList:
        tianxuanState = True
        dropInfo = GameWorld.GetResultByRandomList(tianxuanEquipRateList)
        if dropInfo:
            dropEquipInfoList.append(dropInfo)
    # 3. 第x次击杀, 归属者公共附加掉落,所有归属者都增加击杀次数;
    tagClassLV, tagColor, tagIsSuit, tagPlaceKey = 0, 0, 0, 0
    killCountDropEquipPub = ipyDrop.GetKillCountDropEquipPub() # 第x次击杀附加必掉装备 {次数:[阶,颜色,是否套装,部位集合key], ...}
    killCountDropItemPub = ipyDrop.GetKillCountDropPub() # 击杀次数必掉(公共){击杀次数:[[物品ID, ...], [随机物品ID, ...]], ...}
    maxRecordDropEquipKillCountPub = 0 if not killCountDropEquipPub else max(killCountDropEquipPub) # 需要记录的最大击杀次数, 超过此击杀次数后暂时不累加记录
    maxRecordDropItemIDKillCountPub = 0 if not killCountDropItemPub else max(killCountDropItemPub)
    #GameWorld.DebugLog("maxRecordDropEquipKillCountPub=%s,maxRecordDropItemIDKillCountPub=%s" % (maxRecordDropEquipKillCountPub, maxRecordDropItemIDKillCountPub))
    #GameWorld.DebugLog("dropEquipKillCountPub=%s,dropItemIDKillCountPub=%s" % (dropEquipKillCountPub, dropItemIDKillCountPub))
    if isKillCountDrop and killCountDropEquipPub and dropEquipKillCountPub in killCountDropEquipPub:
        tagClassLV, tagColor, tagIsSuit, tagPlaceKey = killCountDropEquipPub[dropEquipKillCountPub]
        if (tagClassLV, tagColor) not in dropEquipInfoList:
            dropEquipInfoList.insert(0, (tagClassLV, tagColor, tagIsSuit, tagPlaceKey, tagJob))
        else:
            tagIndex = dropEquipInfoList.index((tagClassLV, tagColor))
            dropEquipInfoList[tagIndex] = (tagClassLV, tagColor, tagIsSuit, tagPlaceKey, tagJob)
        GameWorld.DebugLog("第%s次击杀掉落指定目标装备信息: npcID=%s,tagClassLV=%s,tagColor=%s,tagIsSuit=%s,tagPlaceKey=%s,tagJob=%s,dropEquipInfoList=%s"
                           % (dropEquipKillCountPub, npcID, tagClassLV, tagColor, tagIsSuit, tagPlaceKey, tagJob, dropEquipInfoList), playerID)
    elif isKillCountDrop and killCountDropItemPub and dropItemIDKillCountPub in killCountDropItemPub:
        killCountItemIDList, killCountRandItemIDList = killCountDropItemPub[dropItemIDKillCountPub]
        if killCountItemIDList:
            dropIDList += killCountItemIDList
        if killCountRandItemIDList:
            klllCountRandItemID = random.choice(killCountRandItemIDList)
            dropIDList.append(klllCountRandItemID)
        GameWorld.DebugLog("第%s次击杀掉落指定目标物品信息: npcID=%s,killCountItemIDList=%s,killCountRandItemIDList=%s,dropIDList=%s"
                           % (dropItemIDKillCountPub, npcID, killCountItemIDList, killCountRandItemIDList, dropIDList), playerID)
    for ownerPlayer in ownerPlayerList:
        # 增加击杀次数
        killCountValue = ownerPlayer.NomalDictGetProperty(ChConfig.Def_PDict_NPCKillCount % npcID)
        equipPubKillCount = killCountValue % 100
        itemIDPubKillCount = killCountValue % 10000 / 100
        isUpd = False
        if equipPubKillCount < maxRecordDropEquipKillCountPub:
            equipPubKillCount += 1
            isUpd = True
        if itemIDPubKillCount < maxRecordDropItemIDKillCountPub:
            itemIDPubKillCount += 1
            isUpd = True
        if isUpd:
            itemIDPriKillCount = killCountValue / 10000
            updKillCountValue = itemIDPriKillCount * 10000 + itemIDPubKillCount * 100 + equipPubKillCount
            PlayerControl.NomalDictSetProperty(ownerPlayer, ChConfig.Def_PDict_NPCKillCount % npcID, updKillCountValue)
            GameWorld.DebugLog("更新玩家击杀次数值: npcID=%s,killCountValue=%s,updKillCountValue=%s" % (npcID, killCountValue, updKillCountValue), ownerPlayer.GetPlayerID())
    gradeColorSuitRateDict = {}
    fbGradeColorSuitRateDict = IpyGameDataPY.GetFuncEvalCfg("FBGradeEquipDropRate", 2) # 评级影响颜色套装概率 {npcID:{颜色:[D级影响概率, ..., S级影响概率], ...}, ...}
    if npcID in fbGradeColorSuitRateDict:
        gradeColorSuitRateDict = fbGradeColorSuitRateDict[npcID]
    equipColorDropLimitDay = IpyGameDataPY.GetFuncEvalCfg("ItemDropCountLimit", 1, {}) # 每日个人掉落装备个数限制 {品质:每日掉落个数, ...}
    colorDropCntDict = {} # 装备颜色已经掉落数 {颜色:数量, ...}
    colorMaxDropCntDict = ipyDrop.GetEquipColorMaxDropCount() # {颜色:上限数量,...}
    colorSuitRateDict = ipyDrop.GetEquipColorSuitInfo() # 装备颜色对应套装概率 {颜色:套装概率, ...}
    colorSuitPlaceKeyInfoDict = ipyDrop.GetEquipPartKeyRateInfo() # 装备部位集合信息 {(颜色,是否套装):部位集合key, ...}
    colorSuitPartOptimization = ipyDrop.GetColorSuitPartOptimization() # 部位颜色套评分优选掉落,十位代表颜色,个位代表套装
    optColor, optIsSuit = colorSuitPartOptimization / 10, colorSuitPartOptimization % 10
    optPlace = None # 优选部位
    for dropEquipInfo in dropEquipInfoList:
        classLV, color = dropEquipInfo[:2]
        if realmNPCIpyData:
            classLV = realmNPCIpyData.GetEquipClassLV()
            GameWorld.DebugLog("掉落对应难度境界装备: classLV=%s" % classLV, playerID)
        colorCountToday = 0
        if color in equipColorDropLimitDay:
            colorCountMax = equipColorDropLimitDay[color]
            colorCountToday = dropPlayer.NomalDictGetProperty(ChConfig.Def_PDict_DropColorToday % color)
            if colorCountToday >= colorCountMax:
                GameWorld.DebugLog("已超过该颜色装备今日最大掉落数,不掉!color=%s,colorCountMax=%s" % (color, colorCountMax), playerID)
                continue
        if color in colorMaxDropCntDict:
            maxCount = colorMaxDropCntDict[color]
            dropCount = colorDropCntDict.get(color, 0)
            if dropCount >= maxCount:
                GameWorld.DebugLog("已超过该颜色装备最大掉落数,不掉!color=%s,maxCount=%s" % (color, maxCount), playerID)
                continue
        if len(dropEquipInfo) == 5:
            isSuit, placeKey, tagJob = dropEquipInfo[2:]
            jobList = [tagJob]
        else:
            isSuit = 0
            if color in colorSuitRateDict:
                suitRate = colorSuitRateDict[color]
                # 评级对套装率的影响
                if color in gradeColorSuitRateDict:
                    suitRateEffList = gradeColorSuitRateDict[color]
                    suitRateEff = 10000 if (curGrade <= 0 or curGrade > len(suitRateEffList)) else suitRateEffList[curGrade - 1]
                    suitRate = int(suitRate * suitRateEff / 10000.0)
                isSuit = GameWorld.CanHappen(suitRate, maxRate=Def_NPCMaxDropRate)
            colorSuitKey = (color, isSuit)
            if colorSuitKey not in colorSuitPlaceKeyInfoDict:
                GameWorld.ErrLog("未配置颜色是否套装对应部位集合key! npcID=%s,color=%s,isSuit=%s" % (npcID, color, isSuit))
                continue
            placeKey = colorSuitPlaceKeyInfoDict[colorSuitKey]
            # 掉落优选部位处理
            if color == optColor and isSuit == optIsSuit and optPlace == None:
                allEquipPlaceList = GetAllEquipPlaceByPlaceKey(placeKey)
                #GameWorld.DebugLog("    所有可优选部位: %s" % allEquipPlaceList)
                if allEquipPlaceList:
                    optPlace = __GetOptimizationEquipPlace(dropPlayer, classLV, optColor, optIsSuit, allEquipPlaceList)
            jobList = itemJobList
        if optPlace > 0:
            GameWorld.DebugLog("    最终优选部位: %s" % optPlace)
            placeList = [optPlace]
            jobList = [dropPlayer.GetJob()]
            optPlace = 0 # 只有一次性的,置为0
        else:
            placeList = GetEquipPlaceByPlaceKey(placeKey)
            if not placeList:
                GameWorld.ErrLog("部位集合key不存在!npcID=%s,placeKey=%s" % (npcID, placeKey))
                continue
        randEquipIDList = __GetEquipIDList(npcID, classLV, color, isSuit, placeList, jobList)
        if not randEquipIDList:
            continue
        if color in equipColorDropLimitDay:
            colorCountToday += 1
            PlayerControl.NomalDictSetProperty(dropPlayer, ChConfig.Def_PDict_DropColorToday % color, colorCountToday)
        if color in colorMaxDropCntDict:
            colorDropCntDict[color] = dropCount + 1
        if isSuit and len(jobList) > 1:
            randItemID = __GetRandDropSuitEquipID(dropPlayer, randEquipIDList)
        else:
            randItemID = random.choice(randEquipIDList)
        dropIDList.append(randItemID)
        GameWorld.DebugLog("掉落装备: npcID=%s,itemID=%s,classLV=%s,color=%s,isSuit=%s,placeKey=%s,jobList=%s,randEquipIDList=%s"
                           % (npcID, randItemID, classLV, color, isSuit, placeKey, jobList, randEquipIDList), playerID)
        if colorCountToday:
            GameWorld.DebugLog("    更新掉落特殊品质装备今日次数: color=%s,colorCountToday=%s" % (color, colorCountToday), playerID)
    # 4. 指定物品ID库
    dropIDList += __GetAppointDropItemIDList(dropPlayer, npcID, ipyDrop, doCountRate, doCountAdd, tianxuanBuff)
    if tianxuanBuff and (ipyDrop.GetTianxuanItemKeyRate() or ipyDrop.GetTianxuanItemIDRate()):
        tianxuanState = True
    # 5. 私有掉落
    if isSingle:
        # 击杀次数掉落
        killCountDropInfo = ipyDrop.GetKillCountDropPri()
        if killCountDropInfo:
            needKillCount, killDropItemList = killCountDropInfo[:2]
            killCountDropIDList = GetKillCountDropItemList(dropPlayer, npcID, needKillCount, killDropItemList)
            for kDropItemID in killCountDropIDList:
                dropIDList.append(kDropItemID) # 单独玩家掉落处理的,直接加到掉落列表里,不考虑是否放入背包或掉落的情况,由使用的功能自行处理
                #GameWorld.DebugLog("击杀次数掉落: kDropItemID=%s,needKillCount=%s" % (kDropItemID, needKillCount))
        # 固定产出,所有掉落归属者每人一份,默认绑定,不区分职业
        fbGradePriItemIDDropDict = IpyGameDataPY.GetFuncEvalCfg("FBGradeEquipDropRate", 3)
        if npcID in fbGradePriItemIDDropDict:
            gradePriItemIDDropDict = fbGradePriItemIDDropDict[npcID]
            priDropInfoList = gradePriItemIDDropDict.get(curGrade, [])
            priDropIDList = []
            for priItemID, priItemCount in priDropInfoList:
                priDropIDList += [priItemID] * priItemCount
        else:
            priDropIDList = ipyDrop.GetPriItemIDDrop()
        for priDropID in priDropIDList:
            dropIDList.append(priDropID)
            #GameWorld.DebugLog("私有物品掉落: priDropID=%s" % priDropID)
    # 6. 地图评级额外掉落
    fbGradeDropItemExDict = IpyGameDataPY.GetFuncEvalCfg("FBGradeEquipDropRate2", 2) # 地图评级额外物品掉落 {"mapID":{"评级":[[物品ID,个数], ...], ...}}
    if curGrade and str(mapID) in fbGradeDropItemExDict:
        gradeItemExDict = fbGradeDropItemExDict[str(mapID)]
        gradeItemExList = gradeItemExDict.get(str(curGrade), [])
        #GameWorld.DebugLog("评级额外掉落物品: curGrade=%s,gradeItemExList=%s" % (curGrade, gradeItemExList))
        for gItemExID, gItemExCount in gradeItemExList:
            dropIDList += [gItemExID] * gItemExCount
    # 7.相关活动掉落
    feastWishDropIDList = PlayerFeastWish.GetFeastWishDropItemIDList(dropPlayer, npcData)
    if feastWishDropIDList:
        dropIDList.extend(feastWishDropIDList)
    #bossƾ֤
    killBossCntLimitDict = IpyGameDataPY.GetFuncCfg('KillBossCntLimit', 1)
    if mapID == ChConfig.Def_FBMapID_PersonalBoss:
        limitIndex = ChConfig.Def_FBMapID_PersonalBoss
    else:
        limitIndex = GameWorld.GetDictValueByKey(killBossCntLimitDict, npcID)
    if limitIndex != None:
        bossTrialDrop = PlayerActBossTrial.GetBossTrialDropItemIDList(dropPlayer, limitIndex)
        if bossTrialDrop:
            bossTrialItemID, bossTrialItemCount = bossTrialDrop
            GameWorld.DebugLog("掉落boss凭证: bossTrialItemID=%s, bossTrialItemCount=%s" % (bossTrialItemID, bossTrialItemCount))
            dropIDList += [bossTrialItemID] * bossTrialItemCount
    # 检查掉落互斥ID组
    dropIDList = __RemoveMutexDropID(dropIDList, IpyGameDataPY.GetFuncCfg("MutexDrop", 1))
    # 掉落金币
    dropMoneyDoCnt = ipyDrop.GetDropMoneyDoCnt()
    dropMoneyRate = ipyDrop.GetDropMoneyRate()
    if dropMoneyRate == ChConfig.Def_NPCMapDropRate:
        dropMoneyCnt = dropMoneyDoCnt
    else:
        dropMoneyCnt = 0
        for _ in xrange(dropMoneyDoCnt):
            if GameWorld.CanHappen(dropMoneyRate, ChConfig.Def_NPCMapDropRate):
                dropMoneyCnt += 1
    #GameWorld.DebugLog("NPCID=%s,金币掉率: %s, 执行次数=%s, 掉落金币数=%s" % (npcID, dropMoneyRate, dropMoneyDoCnt, dropMoneyCnt))
    if dropMoneyCnt:
        moneyValue = __GetDropMoneyValue(dropPlayer, ipyDrop, realmNPCIpyData)
        #GameWorld.DebugLog("    掉落金币value=%s" % (moneyValue))
    if dropIDList:
        if ipyDrop.GetAucionItemCanSell():
            for dropID in dropIDList:
                if IpyGameDataPY.GetIpyGameDataNotLog("AuctionItem", dropID):
                    auctionIDList.append(dropID)
        GameWorld.DebugLog("最终掉落物品: npcID=%s,dropIDList=%s" % (npcID, dropIDList), playerID)
        GameWorld.DebugLog("    auctionIDList=%s" % (auctionIDList), playerID)
    elif ChConfig.IsGameBoss(npcData):
        GameWorld.ErrLog("Boss没有掉落物品,NPCID=%s" % (npcID), dropPlayer.GetPlayerID())
    if tianxuanBuff and tianxuanState:
        GameWorld.DebugLog("    去除天玄丹buff: Layer=%s" % tianxuanBuff.GetLayer(), playerID)
        BuffSkill.SetBuffLayer(dropPlayer, tianxuanBuff, max(tianxuanBuff.GetLayer() - 1, 0), True, ChConfig.Def_SkillID_TianxuanBuff, isSync=True)
    return dropIDList, auctionIDList, dropMoneyCnt, moneyValue
def __GetRandDropSuitEquipID(curPlayer, randEquipIDList):
    ## 获取随机掉落的套装ID,为了玩家掉落体验, 当非本职业套装时可至多重新随机X次
    randItemID = 0
    suitRandCountEx = IpyGameDataPY.GetFuncCfg("EquipSuitDrop", 1) + 1
    for _ in xrange(suitRandCountEx):
        randItemID = random.choice(randEquipIDList)
        itemData = GameWorld.GetGameData().GetItemByTypeID(randItemID)
        if not itemData:
            continue
        if ItemCommon.CheckJob(curPlayer, itemData):
            break
    return randItemID
def GetAllEquipPlaceByPlaceKey(placeKey):
    placeKeyRateListDict = IpyGameDataPY.GetFuncEvalCfg("EquipDropPartSets", 2, {}) # {集合数字key1:[[概率1,部位1],...],...}
    if placeKey in placeKeyRateListDict:
        return [rateInfo[1] for rateInfo in placeKeyRateListDict[placeKey]]
    placeKeyListDict = IpyGameDataPY.GetFuncEvalCfg("EquipDropPartSets", 1, {}) # {集合数字key1:[部位1,部位2,...],...}
    if placeKey in placeKeyListDict:
        return placeKeyListDict[placeKey]
    return []
def GetEquipPlaceByPlaceKey(placeKey):
    ## 获取装备位集合对应的部位信息,集合ID重复时,优先饼图
    placeList = []
    placeKeyRateListDict = IpyGameDataPY.GetFuncEvalCfg("EquipDropPartSets", 2, {}) # {集合数字key1:[[概率1,部位1],...],...}
    if placeKey in placeKeyRateListDict:
        placeRateList = placeKeyRateListDict[placeKey]
        place = GameWorld.GetResultByRandomList(placeRateList)
        if place:
            placeList = [place]
            #GameWorld.DebugLog("    掉落部位概率集合: placeKey=%s,placeRateList=%s,place=%s,placeList=%s" % (placeKey, placeRateList, place, placeList))
    if not placeList:
        placeKeyListDict = IpyGameDataPY.GetFuncEvalCfg("EquipDropPartSets", 1, {}) # {集合数字key1:[部位1,部位2,...],...}
        if placeKey in placeKeyListDict:
            placeList = placeKeyListDict[placeKey]
            #GameWorld.DebugLog("    掉落部位均衡集合: placeKey=%s,placeList=%s" % (placeKey, placeList))
    return placeList
def __GetOptimizationEquipPlace(dropPlayer, classLV, optColor, optIsSuit, optPlaceList):
    ''' 获取掉落优选部位
        几个默认规则
    1. 颜色大于指定优选颜色的,无论是否套装都不计算在内
    2. 颜色小于指定优选颜色的,无论是否套装都计算在内
    '''
    #GameWorld.DebugLog("处理优选部位掉落: classLV=%s,optColor=%s,optIsSuit=%s,optPlaceList=%s" % (classLV, optColor, optIsSuit, optPlaceList))
    minGSPlace = None
    minGS = None
    equipPack = dropPlayer.GetItemManager().GetPack(IPY_GameWorld.rptEquip)
    for optPlace in optPlaceList:
        ipyData = IpyGameDataPY.GetIpyGameData('EquipPlaceIndexMap', classLV, optPlace)
        if not ipyData:
            continue
        equipIndex = ipyData.GetGridIndex()
        curEquip = equipPack.GetAt(equipIndex)
        if not curEquip or curEquip.IsEmpty():
            #GameWorld.DebugLog("    没穿装备,直接默认返回该部位: optPlace=%s" % optPlace)
            return optPlace
        curColor = curEquip.GetItemColor()
        curIsSuit = 1 if curEquip.GetSuiteID() else 0
        if curColor > optColor:
            # 超过优选指定颜色的不算,无论是否有套装
            #GameWorld.DebugLog("    颜色超过优选颜色,不算该部位: optPlace=%s,curColor=%s,curIsSuit=%s" % (optPlace, curColor, curIsSuit))
            continue
        if curColor == optColor and curIsSuit >= optIsSuit:
            # 与优选指定颜色相同,且满足是否套装的不算
            #GameWorld.DebugLog("    颜色套装满足优选,不算该部位: optPlace=%s,curColor=%s,curIsSuit=%s" % (optPlace, curColor, curIsSuit))
            continue
        curGS = ItemCommon.GetEquipGearScore(curEquip)
        if minGS == None or curGS < minGS:
            minGS = curGS
            minGSPlace = optPlace
    return minGSPlace
def __GetNPCDropDoCountChange(doCount, doCountRate, doCountAdd):
    ## 获取掉落执行次数变更结果,可能增加 或 减少
    if doCountRate != ChConfig.Def_MaxRateValue:
        doCount = max(1, int(doCount * doCountRate / 10000.0))
    if doCountAdd:
        doCount = max(1, doCount + doCountAdd) # 支持正负,保底1,负值待扩展
    return doCount
def __RemoveMutexDropID(dropIDList, mutexDropInfo):
    ## 移除互斥的掉落物品,每组互斥的ID最多只能掉落一个,列表顺序为掉落优先级 [[互斥组1ID, ID, ...], [互斥组2ID, ID, ...], ...]
    for mutexDropList in mutexDropInfo:
        isMutex = False
        for mutexID in mutexDropList:
            if mutexID not in dropIDList:
                continue
            if not isMutex:
                isMutex = True
                curDropIDCnt = dropIDList.count(mutexID)
                # 如果超过1个,则移除多余的,只保留一个
                if curDropIDCnt > 1:
                    for _ in xrange(curDropIDCnt - 1):
                        dropIDList.remove(mutexID)
                continue
            # 已经是互斥的了,该ID不可掉落,全部移除
            while mutexID in dropIDList:
                dropIDList.remove(mutexID)
    return dropIDList
def __GetAppointDropItemIDList(curPlayer, npcID, ipyDrop, doCountRate, doCountAdd, tianxuanBuff):
    ## 指定物品ID掉落
    dropItemIDList = []
    itemDropLimitDayInfo = IpyGameDataPY.GetFuncEvalCfg("ItemDropCountLimit", 2, {}) # 每日个人掉落物品个数限制 {物品ID:每日掉落上限, ...}
    # 1. 职业物品ID集合
    job = curPlayer.GetJob()
    JobItemDropSets = IpyGameDataPY.GetFuncCfg("JobItemDropSets", 1) # {物品ID集合key:[职业顺序物品ID列表], ...}
    itemDropSets = IpyGameDataPY.GetFuncCfg("JobItemDropSets", 2) # {物品ID集合key:[随机物品ID列表], ...}
    itemDropRateSets = IpyGameDataPY.GetFuncCfg("JobItemDropSets", 3) # {物品ID集合key:[随机物品ID饼图列表], ...}
    ItemKeyMaxDropCountDict = ipyDrop.GetItemKeyMaxDropCount() # {物品ID集合key:随机次数,...}
    #     1.1 只掉本职业的
    ItemKeyDropRateJobDict = ipyDrop.GetItemKeyDropRateJob() # {物品ID集合key:概率, ...}, 只掉本职业的,优先级高
    if ItemKeyDropRateJobDict:
        for jobItemKey, dropRate in ItemKeyDropRateJobDict.items():
            if jobItemKey not in JobItemDropSets:
                continue
            jobItemList = JobItemDropSets[jobItemKey]
            if len(jobItemList) < job:
                GameWorld.ErrLog("职业物品集合key没有配置对应职业ID: npcID=%s,jobItemKey=%s,job=%s" % (npcID, jobItemKey, job))
                continue
            mustDropCount = dropRate / Def_NPCMaxDropRate
            dropRate = dropRate % Def_NPCMaxDropRate # 基础概率
            canDropCount = mustDropCount
            doCnt = ItemKeyMaxDropCountDict.get(jobItemKey, 1) # 默认1个
            doCnt = __GetNPCDropDoCountChange(doCnt, doCountRate, doCountAdd)
            for _ in xrange(doCnt):
                if not GameWorld.CanHappen(dropRate, maxRate=Def_NPCMaxDropRate):
                    continue
                canDropCount += 1
            jobItemID = jobItemList[job - 1]
            for _ in xrange(canDropCount):
                dropItemIDList.append(jobItemID)
                #GameWorld.DebugLog("掉落自身职业指定物品ID: jobItemKey=%s,jobItemID=%s" % (jobItemKey, jobItemID))
    #     1.2 随机掉落一个
    ItemKeyDropRateDict = ipyDrop.GetItemKeyDropRate() # {物品ID集合key:概率, ...}, 随机掉一个,优先级低
    tianxuanItemKeyRateDict = ipyDrop.GetTianxuanItemKeyRate() # 天玄丹指定ID集合Key概率{物品ID集合key:概率, ...}
    if tianxuanBuff and tianxuanItemKeyRateDict:
        ItemKeyDropRateDict = copy.deepcopy(ItemKeyDropRateDict)
        ItemKeyDropRateDict.update(tianxuanItemKeyRateDict)
    if ItemKeyDropRateDict:
        GameWorld.DebugLog("ItemKeyDropRateDict:%s" % ItemKeyDropRateDict)
        for itemKey, dropRate in ItemKeyDropRateDict.items():
            # 在只掉本职业里的不处理
            if itemKey in ItemKeyDropRateJobDict:
                continue
            mustDropCount = dropRate / Def_NPCMaxDropRate
            dropRate = dropRate % Def_NPCMaxDropRate # 基础概率
            canDropCount = mustDropCount
            doCnt = ItemKeyMaxDropCountDict.get(itemKey, 1) # 默认1个
            doCnt = __GetNPCDropDoCountChange(doCnt, doCountRate, doCountAdd)
            for _ in xrange(doCnt):
                if not GameWorld.CanHappen(dropRate, maxRate=Def_NPCMaxDropRate):
                    continue
                canDropCount += 1
            for _ in xrange(canDropCount):
                if itemKey in itemDropRateSets:
                    randItemRateList = itemDropRateSets[itemKey]
                    randItemID = GameWorld.GetResultByRandomList(randItemRateList)
                    #GameWorld.DebugLog("掉落饼图物品ID: itemKey=%s,randItemRateList=%s,randItemID=%s" % (itemKey, randItemRateList, randItemID))
                elif itemKey in itemDropSets:
                    randItemList = itemDropSets[itemKey]
                    randItemID = random.choice(randItemList)
                    #GameWorld.DebugLog("掉落随机物品ID: itemKey=%s,randItemList=%s,randItemID=%s" % (itemKey, randItemList, randItemID))
                else:
                    continue
                if not randItemID:
                    continue
                if __dropIDCountLimitToday(curPlayer, randItemID, itemDropLimitDayInfo):
                    continue
                dropItemIDList.append(randItemID)
                #GameWorld.DebugLog("掉落随机指定物品ID: itemKey=%s,randItemID=%s" % (itemKey, randItemID))
    # 2. 指定掉落ID处理, 受全局设定影响
    itemIDDropRateDict = ipyDrop.GetItemIDDropRate() # {物品ID:概率, ...}
    itemIDDropMaxCntDict = ipyDrop.GetItemIDMaxDropCount() # {物品ID:最大掉落个数,...}
    tianxuanItemIDRate = ipyDrop.GetTianxuanItemIDRate() # 天玄丹指定ID概率 {物品ID:概率, ...}
    if tianxuanBuff and tianxuanItemIDRate:
        itemIDDropRateDict = copy.deepcopy(itemIDDropRateDict)
        itemIDDropRateDict.update(tianxuanItemIDRate)
    # 全局材料掉落控制
    globalDropCDDict = IpyGameDataPY.GetFuncCfg("GlobalDropCD", 1) # {物品ID:分钟, ...}
    globalDropRateDict = IpyGameDataPY.GetFuncCfg("NPCGlobalDropRate", 1) # {物品ID:[[npcID列表], "概率公式"], ...}
    gw = GameWorld.GetGameWorld()
    if itemIDDropRateDict:
        GameWorld.DebugLog("itemIDDropRateDict=%s" % itemIDDropRateDict)
    for itemID, dropRate in itemIDDropRateDict.items():
        if not dropRate:
            continue
        # 根据击杀次数来的另外计算,不在此判断
        if itemID in globalDropRateDict:
            continue
        doCnt = itemIDDropMaxCntDict.get(itemID, 1) # 默认1个
        # 判断是否全局掉落CD中
        if itemID in globalDropCDDict:
            curTime = int(time.time())
            lastDropTime = gw.GetGameWorldDictByKey(ShareDefine.Def_Notify_WorldKey_LastDropTime % itemID)
            cdTime = globalDropCDDict[itemID] * 60
            remainTime = cdTime - (curTime - lastDropTime)
            if remainTime > 0:
                GameWorld.DebugLog("该物品全局掉落CD中,不掉落!itemID=%s,cdTime=%s,curTime=%s,lastDrop=%s,remainTime=%s"
                                   % (itemID, cdTime, curTime, lastDropTime, remainTime))
                continue
        else:
            doCnt = __GetNPCDropDoCountChange(doCnt, doCountRate, doCountAdd)
        #GameWorld.DebugLog("    指定判断: itemID=%s, dropRate=%s, doCnt=%s" % (itemID, dropRate, doCnt))
        for _ in xrange(doCnt):
            if not GameWorld.CanHappen(dropRate, maxRate=Def_NPCMaxDropRate):
                continue
            if __dropIDCountLimitToday(curPlayer, itemID, itemDropLimitDayInfo):
                continue
            dropItemIDList.append(itemID)
            if itemID in globalDropCDDict:
                # 通知GameServer记录
                msgInfo = str([itemID, curTime])
                GameWorld.GetPlayerManager().GameServer_QueryPlayerResult(0, 0, 0, "GlobalDropCD", msgInfo, len(msgInfo))
                DataRecordPack.DR_GlobalDropCD(curPlayer, npcID, itemID)
                break
    # 3. 指定击杀次数全局掉率
    for itemID, dropInfo in globalDropRateDict.items():
        npcIDList, rateFormat = dropInfo
        if npcID not in npcIDList:
            continue
        killedCnt = gw.GetGameWorldDictByKey(ShareDefine.Def_Notify_WorldKey_DropNPCKilledCnt % itemID)
        dropRate = eval(FormulaControl.GetCompileFormula("KilledCntDropRate_%s" % itemID, rateFormat))
        isDrop = GameWorld.CanHappen(dropRate, maxRate=Def_NPCMaxDropRate)
        if isDrop:
            dropItemIDList.append(itemID)
            DataRecordPack.DR_GlobalDropRate(curPlayer, npcID, itemID, killedCnt, dropRate)
        # 通知GameServer记录
        updKilledCnt = 0 if isDrop else (killedCnt + 1)
        msgInfo = str([itemID, updKilledCnt])
        GameWorld.GetPlayerManager().GameServer_QueryPlayerResult(0, 0, 0, "GlobalDropRate", msgInfo, len(msgInfo))
    # 4. 指定全服击杀次数必掉,算额外掉落
    globalKillDropDict = IpyGameDataPY.GetFuncEvalCfg("GlobalDropCD", 2) # {NPCID:{击杀次数:[是否本职业, {物品ID:个数, ...}, [[随机物品ID,个数], ...]]}, ...}
    if npcID in globalKillDropDict:
        killCountDropDict = globalKillDropDict[npcID]
        updNPCKilledCount = min(gw.GetGameWorldDictByKey(ShareDefine.Def_Notify_WorldKey_NPCKilledCount % npcID) + 1, ShareDefine.Def_UpperLimit_DWord)
        GameWorld.Log("更新全服击杀次数:npcID=%s, %s" % (npcID, updNPCKilledCount))
        # 通知GameServer记录
        msgInfo = str([npcID, updNPCKilledCount])
        GameWorld.GetPlayerManager().GameServer_QueryPlayerResult(0, 0, 0, "GlobalKillCount", msgInfo, len(msgInfo))
        if updNPCKilledCount in killCountDropDict:
            isJobLimit, itemIDCountDict, randItemIDCountList = killCountDropDict[updNPCKilledCount]
            for itemID, itemCount in itemIDCountDict.items():
                if isJobLimit:
                    itemData = GameWorld.GetGameData().GetItemByTypeID(itemID)
                    if not itemData:
                        continue
                    itemJob = itemData.GetJobLimit()
                    if itemJob and itemJob != curPlayer.GetJob():
                        # 非本职业可用,不掉落
                        GameWorld.DebugLog("全服击杀次数必掉,非本职业可用,不掉落! itemID=%s" % itemID)
                        continue
                dropItemIDList += [itemID] * itemCount
                GameWorld.Log("全服击杀次数必掉物品: itemID=%s,itemCount=%s" % (itemID, itemCount))
            if randItemIDCountList:
                if isJobLimit:
                    randJobItemList = []
                    for rItemID, rItemCount in randItemIDCountList:
                        itemData = GameWorld.GetGameData().GetItemByTypeID(rItemID)
                        if not itemData:
                            continue
                        itemJob = itemData.GetJobLimit()
                        if itemJob and itemJob != curPlayer.GetJob():
                            # 非本职业可用,不掉落
                            GameWorld.DebugLog("全服击杀次数必掉随机,非本职业可用,不掉落! rItemID=%s" % rItemID)
                            continue
                        randJobItemList.append([rItemID, rItemCount])
                    randItemID, randItemCount = random.choice(randJobItemList)
                else:
                    randItemID, randItemCount = random.choice(randItemIDCountList)
                dropItemIDList += [randItemID] * randItemCount
                GameWorld.Log("全服击杀次数必掉随机物品: randItemID=%s,randItemCount=%s" % (randItemID, randItemCount))
    return dropItemIDList
def __dropIDCountLimitToday(curPlayer, itemID, itemDropLimitDayInfo):
    ## 处理今日掉落物品ID个数限制
    # @return: 是否限制
    if itemID not in itemDropLimitDayInfo:
        return False
    dropCountTodayMax = itemDropLimitDayInfo[itemID]
    if not dropCountTodayMax:
        return False
    dropCountToday = curPlayer.NomalDictGetProperty(ChConfig.Def_PDict_DropCountToday % itemID)
    if dropCountToday >= dropCountTodayMax:
        GameWorld.DebugLog("    物品ID今日掉落次数已达上限: itemID=%s,dropCountToday=%s" % (itemID, dropCountToday), curPlayer.GetPlayerID())
        return True
    dropCountToday += 1
    PlayerControl.NomalDictSetProperty(curPlayer, ChConfig.Def_PDict_DropCountToday % itemID, dropCountToday)
    GameWorld.DebugLog("    更新物品ID今日掉落次数: itemID=%s,dropCountToday=%s" % (itemID, dropCountToday), curPlayer.GetPlayerID())
    return False
def __GetEquipIDList(findID, classLV, color, isSuit, placeList, itemJobList, findType="NPC"):
def __GetEquipIDList(findID, classLV=None, color=None, isSuit=None, placeList=None, itemJobList=None, findType="NPC"):
    #存一个满足要求的所有的物品的列表 然后从当中随机选一个
    #注: 阶、颜色、套装ID、职业、部位,这5个条件可确认唯一一件装备
    
@@ -1451,9 +335,9 @@
                    if not itemData.GetCanNPCDrop():
                        continue
                    
                    if ItemCommon.GetItemClassLV(itemData) != classLV:
                    if classLV != None and ItemCommon.GetItemClassLV(itemData) != classLV:
                        continue
                    if itemData.GetItemColor() != color:
                    if color != None and itemData.GetItemColor() != color:
                        continue
                    suiteID = itemData.GetSuiteID()
                    itemJob = itemData.GetJobLimit()
@@ -1475,7 +359,7 @@
            if itemJob and itemJobList and itemJob not in itemJobList:
                continue
            curIsSuit = suiteID > 0
            if curIsSuit != isSuit:
            if isSuit != None and curIsSuit != isSuit:
                continue
            itemIDList.append(itemID)
            
@@ -1484,128 +368,6 @@
                         % (findType, findID, classLV, color, isSuit, placeList, itemJobList))
    return itemIDList
def __GetNPCPieRateEquipDrop(ipyDrop, doCnt, equipDropPlus):
    ## 获取NPC饼图掉率装备掉落信息
    dropEquipInfoList = []
    pieRateDropList = ipyDrop.GetPieRateDrop()  # 饼图概率掉落信息 [(概率,0),(概率,(阶,颜色)),...]
    dropRateList = pieRateDropList if not equipDropPlus else GameWorld.GetPlusPieList(pieRateDropList, equipDropPlus)
    #GameWorld.DebugLog("掉落饼图概率: %s, equipDropPlus=%s" % (pieRateDropList, equipDropPlus))
    #GameWorld.DebugLog("实际饼图概率: %s" % (dropRateList))
    for _ in xrange(doCnt):
        dropInfo = GameWorld.GetResultByRandomList(dropRateList)
        if dropInfo:
            dropEquipInfoList.append(dropInfo)
    #GameWorld.DebugLog("饼图装备掉落结果: doCnt=%s, %s" % (doCnt, dropEquipInfoList))
    return dropEquipInfoList
def __GetNPCIndepRateEquipDrop(mapID, ipyDrop, doCnt, equipDropPlus, curGrade=0):
    ## 获取NPC独立掉率装备掉落信息
    npcID = ipyDrop.GetNPCID()
    indepRateDict = ipyDrop.GetIndepRateDrop() # 独立概率掉落信息 {(阶,颜色):概率,...}
    #GameWorld.DebugLog("独立概率装备掉落处理: indepRateDict=%s,equipDropPlus=%s" % (indepRateDict, equipDropPlus))
    gradeColorRateDict = {}
    fbGradeColorRateDict = IpyGameDataPY.GetFuncEvalCfg("FBGradeEquipDropRate", 1) #{npcID:{颜色:[D级影响概率, ..., S级影响概率], ...}, ...}
    if npcID in fbGradeColorRateDict:
        gradeColorRateDict = fbGradeColorRateDict[npcID]
    orangeEquipPer = 0
    fbGradeOrangeEquipPerDict = IpyGameDataPY.GetFuncEvalCfg("FBGradeEquipDropRate2", 1) # 地图评级影响橙装概率百分率 {"mapID":[D级影响概率, ..., S级影响概率], ..}
    if str(mapID) in fbGradeOrangeEquipPerDict and curGrade:
        orangeEquipPerList = fbGradeOrangeEquipPerDict[str(mapID)]
        orangeEquipPer = 0 if (curGrade <= 0 or curGrade > len(orangeEquipPerList)) else orangeEquipPerList[curGrade - 1]
    #colorDropCntDict = {} # 装备颜色已经掉落数 {颜色:数量, ...}
    dropEquipInfoList = []
    for _ in xrange(doCnt):
        for dropInfo, rate in indepRateDict.iteritems():
            dropRate = rate
            color = dropInfo[1]
            if color in gradeColorRateDict:
                colorRateList = gradeColorRateDict[color]
                colorRate = 10000 if (curGrade <= 0 or curGrade > len(colorRateList)) else colorRateList[curGrade - 1]
                dropRate = int(dropRate * colorRate / 10000.0)
                #GameWorld.DebugLog("    评级影响颜色概率: curGrade=%s,colorRate=%s,dropRate=%s" % (curGrade, colorRate, dropRate))
            if color == ChConfig.Def_Quality_Orange and orangeEquipPer:
                dropRate = int(dropRate * orangeEquipPer / 100.0)
                #GameWorld.DebugLog("评级橙装掉率加成: orangeEquipPer=%s,dropRate=%s" % (orangeEquipPer, dropRate))
            dropRate = dropRate if not equipDropPlus else (dropRate + int(dropRate * equipDropPlus / 10000.0))
            mustDropCount = dropRate / Def_NPCMaxDropRate
            dropRate = dropRate % Def_NPCMaxDropRate # 基础概率
            #GameWorld.DebugLog("    dropInfo=%s,rate=%s,mustDropCount=%s,dropRate=%s" % (dropInfo, rate, mustDropCount, dropRate))
            curDropCount = mustDropCount
            if GameWorld.CanHappen(dropRate, maxRate=Def_NPCMaxDropRate):
                curDropCount += 1
            if not curDropCount:
                continue
            for _ in xrange(curDropCount):
                dropEquipInfoList.append(dropInfo)
    #GameWorld.DebugLog("独立概率装备掉落结果: doCnt=%s, %s" % (doCnt, dropEquipInfoList))
    return dropEquipInfoList
def __GetDropMoneyValue(curPlayer, ipyDrop, realmNPCIpyData):
    baseMoney = FBLogic.OnGetNPCDropMoney(curPlayer)
    if baseMoney <= 0:
        # 获得掉落数量
        if realmNPCIpyData:
            baseMoney = random.randint(realmNPCIpyData.GetDropMoneyMin(), realmNPCIpyData.GetDropMoneyMax())
        else:
            baseMoney = random.randint(ipyDrop.GetDropMoneyMin(), ipyDrop.GetDropMoneyMax())
    if baseMoney <= 0:
        return 0
    moneyValue = baseMoney
    # 玩家金钱掉落加成
    if curPlayer != None:
        addRateEx = 0
        addRate = float(curPlayer.GetGoldFoundRate() + addRateEx) / ShareDefine.Def_MaxRateValue
        moneyValue = int(moneyValue + moneyValue * addRate)
        #特殊地图杀怪金币外层加成
        outerMoneyRate = FBLogic.OnGetOuterMoneyRate(curPlayer)
        if outerMoneyRate > 0:
            moneyValue = int(moneyValue * outerMoneyRate / float(ShareDefine.Def_MaxRateValue))
    if moneyValue >= 65535:
        moneyValue = random.randint(65000, 65530)
    return moneyValue
def GetKillCountDropItemList(curPlayer, npcID, needKillCount, killDropItemList):
    ## 获取击杀次数额外掉落
    killCountValue = curPlayer.NomalDictGetProperty(ChConfig.Def_PDict_NPCKillCount % npcID)
    killCountPri = killCountValue / 10000
    if killCountPri >= needKillCount:
        #GameWorld.DebugLog("杀掉次数已经掉落过! npcID=%s,killCountValue=%s,dropRecord=%s" % (npcID, killCountValue, dropRecord), curPlayer.GetPlayerID())
        return []
    killCountPri += 1
    updRecordValue = killCountPri * 10000 + killCountValue % 10000
    jobDropInfo = []
    if killCountPri >= needKillCount:
        isJobLimit = 1
        #[itemID,...]
        for dropItemID in killDropItemList:
            itemData = GameWorld.GetGameData().GetItemByTypeID(dropItemID)
            if not itemData:
                GameWorld.ErrLog("掉落物品ID不存在, dropItemID=%s" % dropItemID)
                continue
            itemJob = itemData.GetJobLimit()
            if isJobLimit and itemJob and itemJob != curPlayer.GetJob():
                # 非本职业可用,不掉落
                #GameWorld.DebugLog("非本职业可用,不掉落! dropItemID=%s" % dropItemID)
                continue
            jobDropInfo.append(dropItemID)
        GameWorld.DebugLog("击杀次数必掉落! npcID=%s,needKillCount=%s,jobDropInfo=%s,killDropItemList=%s,updRecordValue=%s"
                           % (npcID, needKillCount, jobDropInfo, killDropItemList, updRecordValue), curPlayer.GetPlayerID())
    PlayerControl.NomalDictSetProperty(curPlayer, ChConfig.Def_PDict_NPCKillCount % npcID, updRecordValue)
    GameWorld.DebugLog("更新击杀次数比掉落值: killCountValue=%s,killCountPri=%s,updRecordValue=%s" % (killCountValue, killCountPri, updRecordValue), curPlayer.GetPlayerID())
    return jobDropInfo
######################################################################
#---------------------------------------------------------------------
#移动相关
@@ -1972,7 +734,7 @@
#===============================================================================
def GetDefaultMaxAngryNPCIDList():
    return GameLogic_FamilyInvade.GetDefaultMaxAngryNPCIDList()
    return []
#---------------------------------------------------------------------
##NPC进入战斗状态
@@ -2261,7 +1023,9 @@
#  @return 
def SetDeadEx(curNPC):
    summon_List = []
    objID = curNPC.GetID()
    npcid = curNPC.GetNPCID()
    GameWorld.DebugLog("SetDeadEx objID=%s,npcID=%s" % (objID, npcid))
    #将涉及到C++中列表删除的功能,统一改成 -> 复制Py列表后,然后进行删除逻辑 
    for index in range(curNPC.GetSummonCount()):
        curSummonNPC = curNPC.GetSummonNPCAt(index)
@@ -2291,9 +1055,7 @@
            #GameWorld.GetGameWorld().SetGameWorldDict(ChConfig.Map_NPC_WorldBossDeadTick % npcid, GameWorld.GetGameWorld().GetTick())
            #因为存在boss分流,所以用gameFB字典,但是存活状态还是用GameWorld字典
            GameWorld.GetGameFB().SetGameFBDict(ChConfig.Map_NPC_WorldBossDeadTick % npcid, GameWorld.GetGameWorld().GetTick())
            if GetDropOwnerType(curNPC) == ChConfig.DropOwnerType_Family:
                FamilyRobBoss.ClearFamilyOwnerBossHurt(curNPC)
        ChNPC.OnNPCSetDead(curNPC)
        
        if npcid == IpyGameDataPY.GetFuncCfg("CrossYaomoBoss", 1):
@@ -2301,10 +1063,6 @@
            
    # 清除队伍成员伤血列表
    AttackCommon.ClearTeamPlayerHurtValue(curNPC)
    # 清除自定义伤血列表
    #BossHurtMng.ClearHurtValueList(curNPC)
    NPCHurtManager.DeletePlayerHurtList(curNPC)
    NPCHurtMgr.DeletePlayerHurtList(curNPC)
    if curNPC.GetType() == ChConfig.ntRobot:
        lineID = GameWorld.GetGameWorld().GetLineID()
        lineRobotJobDict = PyGameData.g_fbRobotJobDict.get(lineID, {})
@@ -2322,9 +1080,19 @@
                break
            
    # C++设置npc死亡
    notifyClient = True
    #tfMgr = TurnAttack.GetTurnFightMgr()
    #turnFight = tfMgr.getNPCTurnFight(objID)
    #if turnFight:
    #    notifyClient = False # 回合制战斗的由py自己通知
    #    # //04 07 NPC消失#tagNPCDisappear 此处通知消失,与回合制死亡区分
    #    clientPack = ChNetSendPack.tagNPCDisappear()
    #    clientPack.NPCID = [objID]
    #    clientPack.Count = len(clientPack.NPCID)
    #    turnFight.addBatPack(clientPack)
    curNPC.SetDead(curNPC.GetDictByKey(ChConfig.Def_NPCDead_Reason),
                   curNPC.GetDictByKey(ChConfig.Def_NPCDead_KillerType),
                   curNPC.GetDictByKey(ChConfig.Def_NPCDead_KillerID))
                   curNPC.GetDictByKey(ChConfig.Def_NPCDead_KillerID), notifyClient)
    return
def GameServer_KillGameWorldBoss(bossID, killPlayerName, hurtValue, isNotify=True, killerIDList=[]):
@@ -2375,7 +1143,6 @@
        key = ChConfig.Def_PDict_Boss_KillCnt % limitIndex
        newCnt = curPlayer.NomalDictGetProperty(key, 0) + 1
        PlayerControl.NomalDictSetProperty(curPlayer, key, newCnt)
        BossHurtMng.NotifyAttackBossCnt(curPlayer, limitIndex)
        GameWorld.DebugLog("更新击杀Boss次数: index=%s, todayCnt=%s, totalCnt=%s" % (limitIndex, newCnt, totalCnt), curPlayer.GetPlayerID())
        
        dataDict = {"objID":npcID, "bossID":npcID, "touchCnt":newCnt, "totalCnt":totalCnt,
@@ -2394,8 +1161,6 @@
        # 每日活动
        PlayerActivity.AddDailyActionFinishCnt(curPlayer, ShareDefine.DailyActionID_WorldBOSS)
        PlayerBossReborn.AddBossRebornActionCnt(curPlayer, ChConfig.Def_BRAct_WorldBOSS, 1)
        PlayerFairyCeremony.AddFCPartyActionCnt(curPlayer, ChConfig.Def_PPAct_WorldBoss, 1)
        PlayerNewFairyCeremony.AddFCPartyActionCnt(curPlayer, ChConfig.Def_PPAct_WorldBoss, 1)
        PlayerWeekParty.AddWeekPartyActionCnt(curPlayer, ChConfig.Def_WPAct_WorldBOSS, 1)
        PlayerFeastTravel.AddFeastTravelTaskValue(curPlayer, ChConfig.Def_FeastTravel_WorldBoss, 1)
        PlayerActLogin.AddLoginAwardActionCnt(curPlayer, ChConfig.Def_LoginAct_WorldBOSS, 1)
@@ -2411,8 +1176,6 @@
        # 每日活动
        PlayerActivity.AddDailyActionFinishCnt(curPlayer, ShareDefine.DailyActionID_BOSSHome)
        PlayerBossReborn.AddBossRebornActionCnt(curPlayer, ChConfig.Def_BRAct_BOSSHome, 1)
        PlayerFairyCeremony.AddFCPartyActionCnt(curPlayer, ChConfig.Def_PPAct_BossHome, 1)
        PlayerNewFairyCeremony.AddFCPartyActionCnt(curPlayer, ChConfig.Def_PPAct_BossHome, 1)
        PlayerWeekParty.AddWeekPartyActionCnt(curPlayer, ChConfig.Def_WPAct_BOSSHome, 1)
        PlayerFeastTravel.AddFeastTravelTaskValue(curPlayer, ChConfig.Def_FeastTravel_BossHome, 1)
        PlayerActTask.AddActTaskValue(curPlayer, ChConfig.ActTaskType_BossHome, 1)
@@ -2900,8 +1663,8 @@
            addAngry += useSkill.GetSkillAngry()
            
        # 玩家攻击增加额外仇恨
        if objDetel.GetGameObjType() == IPY_GameWorld.gotPlayer:
            addAngry += PlayerControl.GetAddAngry(objDetel)
        #if objDetel.GetGameObjType() == IPY_GameWorld.gotPlayer:
        #    addAngry += PlayerControl.GetAddAngry(objDetel)
                    
        self.AddObjToAngryList(objDetel, addAngry)
        return
@@ -3262,8 +2025,6 @@
        #清除所有身上buff
        self.ClearAllBuff(isClearAuraBuff)
        curNPC = self.__Instance
        NPCHurtManager.ClearPlayerHurtList(curNPC)
        NPCHurtMgr.ClearPlayerHurtList(curNPC)
        return True
    
    #---------------------------------------------------------------------
@@ -3612,9 +2373,7 @@
            ipyData = IpyGameDataPY.GetIpyGameDataNotLog('BOSSInfo', curNPCID)
            if ipyData:
                GameServe_GameWorldBossState(curNPCID, 1)
                if GetDropOwnerType(curNPC) == ChConfig.DropOwnerType_Family:
                    FamilyRobBoss.FamilyOwnerBossOnReborn(curNPC)
        # 检查是否有光环, 在重生时处理,不然可能导致有些无战斗逻辑的怪物无法套上光环buff
        skillManager = curNPC.GetSkillManager()
        for index in xrange(skillManager.GetSkillCount()):
@@ -3627,41 +2386,20 @@
            GameWorld.DebugLog("NPC复活,套上光环: objID=%s,npcID=%s,skillID=%s" % (curNPC.GetID(), curNPC.GetNPCID(), useSkill.GetSkillID()))
            SkillShell.NPCUseSkill(curNPC, useSkill, tick)
            
        curNPC.NotifyAppear() # 最终统一通知NPC出现
        self.NotifyNPCShow(curNPCID, tick) # 广播NPC秀
        self.__notifyAppear() # 最终统一通知NPC出现
        return
    
    def NotifyNPCShow(self, npcID, tick):
        ## 广播NPC秀
        mapID = GameWorld.GetMap().GetMapID()
        npcShowIpyData = IpyGameDataPY.GetIpyGameDataNotLog("NPCShow", npcID, mapID)
        if not npcShowIpyData:
            #GameWorld.DebugLog("不需要NPC秀: npcID=%s" % npcID)
            return
        #if npcShowIpyData.GetBindMissionID():
        #    #GameWorld.DebugLog("有绑定任务ID的,前端自己展示NPC秀!mapID=%s,npcID=%s" % (mapID, npcID))
    def __notifyAppear(self):
        ## //04 06 NPC出现#tagNPCAppear,可能也有 04 08 玩家召唤NPC出现#tagPlayerSummonNPCAppear,卡牌先简化,只使用0406
        #curNPC = self.__Instance
        #objID = curNPC.GetID()
        #turnFight = TurnAttack.GetTurnFightMgr().getNPCTurnFight(objID)
        #if not turnFight:
        #    # 非回合制怪保留原通知
        #    curNPC.NotifyAppear()
        #    return
        if npcShowIpyData.GetShowType():
            #GameWorld.DebugLog("前端自己展示的NPC秀!mapID=%s,npcID=%s" % (mapID, npcID))
            return
        endTick = GameWorld.GetGameFB().GetGameFBDictByKey(ChConfig.Def_FBDict_NPCShowEndTick % npcID)
        if endTick:
            #GameWorld.DebugLog("已经存在同个NPCID的NPC秀,不重复展示!npcID=%s" % (npcID))
            return
        endTick = tick + npcShowIpyData.GetProtectTime()
        GameWorld.GetGameFB().SetGameFBDict(ChConfig.Def_FBDict_NPCShowEndTick % npcID, endTick)
        
        # 广播地图内玩家展示NPC秀
        npcShowPack = ChPyNetSendPack.tagMCNPCShow()
        npcShowPack.NPCID = npcID
        playerManager = GameWorld.GetMapCopyPlayerManager()
        for index in xrange(playerManager.GetPlayerCount()):
            player = playerManager.GetPlayerByIndex(index)
            if not player.GetPlayerID():
                continue
            NetPackCommon.SendFakePack(player, npcShowPack)
        GameWorld.DebugLog("开始NPC秀: npcID=%s,tick=%s,endTick=%s" % (npcID, tick, endTick))
        # 回合制怪不通知,统一由 // B4 24 回合战斗初始化 #tagSCTurnFightInit
        return
    
    #---------------------------------------------------------------------
@@ -3771,12 +2509,48 @@
    #  @return 返回值无意义
    #  @remarks 刷新NPC属性和行为状态
    def RefreshNPCState(self, canSyncClient=True, isReborn=False):
        curNPC = self.__Instance
        if curNPC.GetDictByKey(ChConfig.Def_Obj_Dict_TurnFightPosInfo):
            # 回合制怪走自己的刷属性规则
            self.RefreshTurnfightNPCAttr()
            return
        self.RefreshNPCAttrState(canSyncClient, isReborn)
        
        self.RefreshNPCActionState()
    def RefreshTurnfightNPCAttr(self):
        curNPC = self.__Instance
        lineupPlayerID = curNPC.GetDictByKey(ChConfig.Def_Obj_Dict_LineupPlayerID)
        heroAttrDict = {}
        if lineupPlayerID:
            heroAttrDict.update({
                                 ChConfig.AttrID_Atk:500000000,
                                 ChConfig.AttrID_Def:50000000,
                                 ChConfig.AttrID_MaxHP:3000000000,
                                 })
        else:
            npcDataEx = GetNPCDataPy(curNPC.GetNPCID())
            if not npcDataEx:
                return
            heroAttrDict.update({
                                 ChConfig.AttrID_Atk:npcDataEx.GetAtk(),
                                 ChConfig.AttrID_Def:npcDataEx.GetDef(),
                                 ChConfig.AttrID_MaxHP:npcDataEx.GetMaxHP(),
                                 })
        GameWorld.DebugLog("heroAttrDict: ID:%s,NPCID:%s,%s" % (curNPC.GetID(), curNPC.GetNPCID(), heroAttrDict))
        # 重置属性状态
        GameObj.ClearBattleEffect(curNPC)
        curNPC.ResetNPCBattleState()
        
        # 设置属性
        curNPC.SetMinAtk(heroAttrDict.get(ChConfig.AttrID_Atk, 1))
        curNPC.SetMaxAtk(heroAttrDict.get(ChConfig.AttrID_Atk, 1))
        curNPC.SetDef(heroAttrDict.get(ChConfig.AttrID_Def, 1))
        GameObj.SetMaxHP(curNPC, heroAttrDict.get(ChConfig.AttrID_MaxHP, 1))
        return
    ## 刷新NPC属性
    #  @param self 类实例
    #  @param canSyncClient 是否通知客户端刷新信息(宠物)
@@ -3820,29 +2594,6 @@
            
        return
    
    def SetHelpBattleRobotRebornAttr(self, fightPower):
        '''助战机器人只设置血量属性
                        血量算法,(助战玩家=助战机器人):每个副本配置伤害*(助战玩家战力/副本规定战力)*系数值  系数值暂定为50
        '''
        curNPC = self.__Instance
        mapID = FBCommon.GetRecordMapID(GameWorld.GetMap().GetMapID())
        funcLineID = FBCommon.GetFBPropertyMark()
        ipyData = IpyGameDataPY.GetIpyGameData("FBHelpBattle", mapID, funcLineID)
        if not ipyData:
            return
        SetSuppressFightPower(curNPC, fightPower)
        fbFightPower = ipyData.GetFightPowerMin()
        baseHurt = ipyData.GetRobotBaseHurt()
        hpCoefficient = ipyData.GetRobotHPCoefficient()
        maxHP = int(eval(IpyGameDataPY.GetFuncCompileCfg("HelpBattleRobot", 2)))
        GameWorld.DebugLog("设置助战机器人属性: objID=%s,fightPower=%s,maxHP=%s" % (curNPC.GetID(), fightPower, maxHP))
        GameObj.SetMaxHP(curNPC, maxHP)
        GameObj.SetHP(curNPC, maxHP)
        curNPC.Notify_HP()
        curNPC.Notify_MaxHP()
        return
    # NPC移动速度特殊处理,只处理百分比不能处理固定值 
    # 因为 ChConfig.TYPE_Calc_AttrSpeed 非服务端移动速度,偷懒处理法
    def RefreshNPCSpeed(self, allAttrList):
@@ -3856,9 +2607,6 @@
            speed = int(curNPC.GetSpeed() * (ShareDefine.Def_MaxRateValue) / max(100.0, float(ShareDefine.Def_MaxRateValue + speedPer)))
            curNPC.SetSpeed(speed)
            curNPC.SetDict(ChConfig.Def_NPC_Dict_SpeedPer, speedPer)
        if GameWorld.GetMap().GetMapID() == ChConfig.Def_FBMapID_GatherSoul:
            #目前只在聚魂副本里通知
            NPCSpeedChangeNotify(curNPC, curNPC.GetSpeed())
        return
    
    
@@ -3961,9 +2709,6 @@
        npcID = curNPC.GetNPCID()
        #######################特殊NPC的处理
        
        #boss伤血排行榜击杀逻辑
        #BossHurtMng.BossOnKilled(curNPC)
        #掉落需要用到摸怪,所以在处理掉落奖励之前设置
        self.__SetFeelNPCPlayerList()
        
@@ -3978,22 +2723,12 @@
        # 记录boss击杀信息的NPC
        bossIpyData = IpyGameDataPY.GetIpyGameDataListNotLog('BOSSInfo', npcID)
        if bossIpyData and mapID not in [ChConfig.Def_FBMapID_ZhuXianBoss, ChConfig.Def_FBMapID_SealDemon]:
            if GetDropOwnerType(curNPC) == ChConfig.DropOwnerType_Family:
                killerName = FamilyRobBoss.FamilyOwnerBossOnKilled(curNPC, self.__OwnerHurtID)
            #KillerJob = 0 if not self.__Killer else self.__Killer.GetJob()
            killerIDList = [player.GetPlayerID() for player in self.__ownerPlayerList]
            GameServer_KillGameWorldBoss(curNPC.GetNPCID(), killerName, 0, True, killerIDList)
            
        if npcID == IpyGameDataPY.GetFuncCfg("BossRebornServerBoss", 3):
            PlayerControl.WorldNotify(0, "BossRebornBossKilled", [curNPC.GetNPCID()])
            
        #===========================================================================================
        # # 暗金boss
        # if curNPC.GetIsBoss() == ChConfig.Def_NPCType_Boss_Dark:
        #    #PlayerControl.WorldNotify(0, "Old_andyshao_861048", [curNPC.GetNPCID()])
        #    if mapID == ChConfig.Def_MapID_DouHunTan:
        #        NPCCustomRefresh.DoRefreshNeutralBoss(npcID)
        #清空NPC的仇恨
        curNPC.GetNPCAngry().Clear()
        return
@@ -4003,7 +2738,7 @@
        curNPC = self.__Instance
        self.__FeelPlayerList = []
        
        npcHurtList = NPCHurtManager.GetPlayerHurtList(curNPC)
        npcHurtList = [] #NPCHurtManager.GetPlayerHurtList(curNPC)
        if not npcHurtList:
            npcHurtList = curNPC.GetPlayerHurtList()
        #npcHurtList.Sort()  #这里不排序,只要有伤害就算
@@ -4052,68 +2787,6 @@
            self.__MissionOnKillNPC(eventPlayer, True)
        return
    
    #---------------------------------------------------------------------
    def __GetDropMoneyModelID(self, moneyValue):
        ## 获取掉落金币模型ID
        if not moneyValue:
            return 0
        moneyItemList = IpyGameDataPY.GetFuncEvalCfg("GoldModel", 1) # 金币掉落数量对应模型-[(最大金币数,物品模型ID), ...]
        if not moneyItemList:
            return 0
        for count, moneyID in moneyItemList:
            if moneyValue <= count:
                return moneyID
        return moneyItemList[-1][1]
    def __NPCSpecialDropItem(self, dropPlayer, ownerPlayerList, ipyDrop):
        '''特殊掉落 (私有特殊掉落 + 击杀次数特殊掉落), 支持摸怪
        @return: None
        @return: [[ownerPlayer, itemID, isAuctionItem, isDropInItemPack], ...]
        '''
        curNPC = self.__Instance
        npcID = curNPC.GetNPCID()
        specDropItemList = []
        #playerLV = dropPlayer.GetLV()
        #maxDropLV = ipyDrop.GetMaxDropLV()
        #if maxDropLV and playerLV > maxDropLV:
        #    GameWorld.DebugLog("超过最大可掉落等级,不掉落物品,特殊掉落!npcID=%s,playerLV(%s) > maxDropLV(%s)" % (npcID, playerLV, maxDropLV))
        #    return specDropItemList
        auctionItemCanSell = ipyDrop.GetAucionItemCanSell()
        # 击杀次数掉落算摸怪
        killCountDropInfo = ipyDrop.GetKillCountDropPri()
        if killCountDropInfo:
            for feelPlayer in self.__FeelPlayerList:
                needKillCount, killDropItemList, isDropInItemPack = killCountDropInfo
                killCountDropItemList = GetKillCountDropItemList(feelPlayer, npcID, needKillCount, killDropItemList)
                for dropItemID in killCountDropItemList:
                    isAuctionItem = 1 if auctionItemCanSell and IpyGameDataPY.GetIpyGameDataNotLog("AuctionItem", dropItemID) else 0
                    specDropItemList.append([feelPlayer, dropItemID, isAuctionItem, isDropInItemPack])
                    GameWorld.DebugLog("击杀次数必掉(可摸怪): npcID=%s,dropItemID=%s,needKillCount=%s,isDropInItemPack=%s,isAuctionItem=%s"
                                       % (npcID, dropItemID, needKillCount, isDropInItemPack, isAuctionItem), feelPlayer.GetPlayerID())
        # 私有掉落
        isDropInItemPack = False
        fbGradePriItemIDDropDict = IpyGameDataPY.GetFuncEvalCfg("FBGradeEquipDropRate", 3)
        if npcID in fbGradePriItemIDDropDict:
            gradePriItemIDDropDict = fbGradePriItemIDDropDict[npcID]
            curGrade = GameWorld.GetGameFB().GetGameFBDictByKey(ChConfig.Def_FB_Grade)
            priDropInfoList = gradePriItemIDDropDict.get(curGrade, [])
            priDropIDList = []
            for priItemID, priItemCount in priDropInfoList:
                priDropIDList += [priItemID] * priItemCount
        else:
            priDropIDList = ipyDrop.GetPriItemIDDrop()
        for dropItemID in priDropIDList:
            isAuctionItem = 1 if auctionItemCanSell and IpyGameDataPY.GetIpyGameDataNotLog("AuctionItem", dropItemID) else 0
            for ownerPlayer in ownerPlayerList:
                specDropItemList.append([ownerPlayer, dropItemID, isAuctionItem, isDropInItemPack]) # 默认绑定
                #GameWorld.DebugLog("私有物品掉落: dropItemID=%s" % dropItemID, ownerPlayer.GetPlayerID())
        return specDropItemList
    ## 物品掉落
    #  @param self 类实例
    #  @param dropPlayer 掉落判断相关玩家
@@ -4121,106 +2794,6 @@
    #  @param HurtID 伤血ID
    #  @return 返回值无意义
    def __NPCDropItem(self, dropPlayer, hurtType, hurtID, ownerPlayerList=[], isOnlySelfSee=False):
        if not dropPlayer:
            return
        curNPC = self.__Instance
        PlayerActCollectWords.OnKillNPCDrop(dropPlayer, curNPC)
        if curNPC.GetType() in [ChConfig.ntPriWoodPilePVE, ChConfig.ntPriWoodPilePVP]:
            GameWorld.DebugLog("木桩怪,不掉落物品!")
            return
        npcID = curNPC.GetNPCID()
        mapID = GameWorld.GetMap().GetMapID()
        mapID = FBCommon.GetRecordMapID(mapID)
        isGameBoss = ChConfig.IsGameBoss(curNPC)
        if mapID in [ChConfig.Def_FBMapID_MunekadoTrial, ChConfig.Def_FBMapID_DemonKing, ChConfig.Def_FBMapID_SealDemon, ChConfig.Def_FBMapID_ZhuXianBoss,
                     ChConfig.Def_FBMapID_KillDevil]:
            GameWorld.DebugLog("该地图不走直接掉落物品逻辑!mapID=%s" % mapID)
            return
        if isGameBoss:
            GameWorld.Log("NPC开始掉落: npcID=%s,dropPlayerID=%s" % (npcID, dropPlayer.GetPlayerID()), dropPlayer.GetPlayerID())
        ipyDrop = GetNPCDropIpyData(npcID)
        if not ipyDrop:
            if isGameBoss:
                curWorldLV = GameWorld.GetGameWorld().GetGameWorldDictByKey(ShareDefine.Def_Notify_WorldKey_WorldAverageLv)
                GameWorld.ErrLog("取不到NPC掉落信息!npcID=%s,curWorldLV=%s" % (npcID, curWorldLV))
            return
        dropIDList, auctionIDList, dropMoneyCnt, moneyValue = [], [], 0, 0
        dropInfo = GetNPCDropInfo(dropPlayer, mapID, npcID, ownerPlayerList, ipyDrop, False)
        if dropInfo:
            dropIDList, auctionIDList, dropMoneyCnt, moneyValue = dropInfo
        moneyID = self.__GetDropMoneyModelID(moneyValue)
        if moneyID and dropMoneyCnt:
            dropIDList += [moneyID] * dropMoneyCnt
        specItemSign = "SpecItem"
        playerSpecDropList = []
        if dropInfo:
            playerSpecDropList = self.__NPCSpecialDropItem(dropPlayer, ownerPlayerList, ipyDrop) # 特殊掉落 [[ownerPlayer, itemID, isAuctionItem, isDropInItemPack], ...]  私有特殊掉落 + 击杀次数特殊掉落
            dropIDList += [specItemSign] * len(playerSpecDropList)
        if len(dropIDList) > 5:
            #打乱物品顺序
            random.shuffle(playerSpecDropList)
            random.shuffle(dropIDList)
        if not dropIDList and isGameBoss:
            GameWorld.ErrLog("Boss没有掉落: dropPlayerLV=%s,ipyWorldLV=%s,maxDropLV=%s"
                             % (dropPlayer.GetLV(), ipyDrop.GetMaxWorldLV(), ipyDrop.GetMaxDropLV()), dropPlayer.GetPlayerID())
        sightLevel = PlayerControl.GetMapRealmDifficulty(dropPlayer)
        gameMap = GameWorld.GetMap()
        dropPosX, dropPosY = curNPC.GetPosX(), curNPC.GetPosY() # 以NPC为中心点开始掉落
        index = 0
        for posX, posY in ChConfig.Def_DropItemAreaMatrix:
            resultX = dropPosX + posX
            resultY = dropPosY + posY
            if not gameMap.CanMove(resultX, resultY):
                #玩家不可移动这个点
                continue
            if index > len(dropIDList) - 1:
                break
            isDropInItemPack = False
            itemID = dropIDList[index]
            index += 1
            if itemID == specItemSign:
                if not playerSpecDropList:
                    continue
                itemCnt = 1
                ownerPlayer, itemID, isAuctionItem, isDropInItemPack = playerSpecDropList[0]
                ownerType, ownerID = ChConfig.Def_NPCHurtTypePlayer, ownerPlayer.GetPlayerID()
                playerSpecDropList = playerSpecDropList[1:]
            else:
                ownerPlayer = dropPlayer
                ownerType, ownerID = hurtType, hurtID
                itemCnt = moneyValue if itemID == moneyID else 1
                isAuctionItem = itemID in auctionIDList
            curItem = self.__CreateDropItem(curNPC, itemID, itemCnt, isAuctionItem, dropPlayer)
            if not curItem:
                continue
            if mapID == ChConfig.Def_FBMapID_GatherSoul:#聚魂副本特殊处理
                GameLogic_GatherSoul.KillGatherSoulNPCDropAward(itemID, itemCnt, isAuctionItem)
                dropItemDataStr = ChItem.GetMapDropItemDataStr(curItem)
                SendVirtualItemDrop(ownerPlayer, itemID, resultX, resultY, dropItemDataStr)
                curItem.Clear()
                continue
            if isDropInItemPack:
                curItem.SetUserAttr(ShareDefine.Def_IudetSource, ShareDefine.Item_Source_VirtualItemDrop)
                dropItemDataStr = ChItem.GetMapDropItemDataStr(curItem)
                #可以放入背包
                if ItemControler.DoLogic_PutItemInPack(ownerPlayer, curItem, event=["NPCDrop", False, {"npcID":npcID}]):
                    #通知客户端
                    SendVirtualItemDrop(ownerPlayer, itemID, resultX, resultY, dropItemDataStr)
            else:
                self.__MapCreateItem(curItem, resultX, resultY, ownerType, ownerID, isOnlySelfSee=isOnlySelfSee, sightLevel=sightLevel)
        return
    #---------------------------------------------------------------------
    ## NPC被杀死逻辑处理
@@ -4425,12 +2998,6 @@
            return
        
        curNPC = self.__Instance
        # VIP杀怪加攻
        PlayerVip.DoAddVIPKillLVExp(lastHurtPlayer, GetNPCLV(curNPC))
        # SPֵ
        PlayerControl.AddZhenQiByKillNPC(lastHurtPlayer, curNPC.GetSP())
        return
    
    #---------------------------------------------------------------------
@@ -4544,13 +3111,6 @@
                hurtID = killerDict.keys()[0]
                if isGameBoss:
                    GameWorld.Log("    归属默认玩家, npcID=%s,playerID=%s" % (npcID, hurtID))
            elif GameWorld.GetMap().GetMapID() == ChConfig.Def_FBMapID_GatherSoul:
                player = FBCommon.GetCurSingleFBPlayer()
                if player:
                    hurtID = player.GetPlayerID()
                    killerDict[hurtID] = player
                    hurtType = ChConfig.Def_NPCHurtTypePlayer
                    #GameWorld.Log("    聚魂副本归属默认玩家, npcID=%s,playerID=%s" % (npcID, hurtID))
                
        return killerDict, killTeam, hurtType, hurtID
    
@@ -4912,7 +3472,6 @@
        ## 测试查错日志,临时用
        ## 相关bug: 仙界秘境无经验、boss无掉落
        return ChConfig.IsGameBoss(self.__Instance)
        #return GameWorld.GetMap().GetMapID() == ChConfig.Def_FBMapID_BZZD or ChConfig.IsGameBoss(self.__Instance)
    #---------------------------------------------------------------------
    ## 普通组队给经验
@@ -5055,19 +3614,14 @@
            GameWorld.Log("检查Boss死亡: lineID=%s,objID=%s,npcID=%s,dropOwnerType=%s" 
                          % (GameWorld.GetGameWorld().GetLineID(), curNPC.GetID(), curNPC.GetNPCID(), dropOwnerType))
        #if dropOwnerType == ChConfig.DropOwnerType_MaxHurt:
        maxHurtInfo = NPCHurtManager.RefreshHurtList(curNPC, tick, refreshInterval, isDead, checkCanDead)
        if not maxHurtInfo:
            maxHurtInfo = NPCHurtMgr.RefreshHurtList(curNPC, tick, refreshInterval, isDead)
        if maxHurtInfo:
            tagObj, ownerType, ownerID = maxHurtInfo
        #maxHurtInfo = NPCHurtManager.RefreshHurtList(curNPC, tick, refreshInterval, isDead, checkCanDead)
        #
        #if maxHurtInfo:
        #    tagObj, ownerType, ownerID = maxHurtInfo
            
        elif dropOwnerType == ChConfig.DropOwnerType_Family:
            ownerInfo = FamilyRobBoss.RefreshFamilyOwnerNPCHurt(self, curNPC, tick, refreshInterval)
            if ownerInfo:
                tagObj, ownerFamilyID = ownerInfo
                ownerType, ownerID = ChConfig.Def_NPCHurtTypeFamily, ownerFamilyID
            pass
        elif dropOwnerType == ChConfig.DropOwnerType_Contend:
            tagObj = self.__RefreshContendOwner()
            if tagObj:
@@ -5191,7 +3745,7 @@
        if isDead:
            GameWorld.Log("Boss归属: key=%s,ownerType=%s,ownerID=%s" % (key, ownerType, ownerID))
            
        hurtList = NPCHurtManager.GetPlayerHurtList(curNPC)
        hurtList = [] #NPCHurtManager.GetPlayerHurtList(curNPC)
        # 刷新归属
        if ownerType == ChConfig.Def_NPCHurtTypePlayer:
            curPlayer = GameWorld.GetObj(ownerID, IPY_GameWorld.gotPlayer)
@@ -5513,9 +4067,6 @@
    
    # 采集耗时
    prepareTime = collectNPCIpyData.GetPrepareTime() * 1000
    collTimeReduceRate = PlayerVip.GetPrivilegeValue(curPlayer, ChConfig.VIPPrivilege_CollTimeReduceRate)
    if collTimeReduceRate:
        prepareTime = max(1000, int(prepareTime * (ShareDefine.Def_MaxRateValue - collTimeReduceRate) / float(ShareDefine.Def_MaxRateValue)))
    prepareType = IPY_GameWorld.pstCollecting if curNPC.GetType() == IPY_GameWorld.ntCollection else IPY_GameWorld.pstMissionCollecting
    PlayerControl.Sync_PrepareBegin(curPlayer, prepareTime, prepareType, prepareID=curNPC.GetID())
    if collectNPCIpyData.GetLostHPPer():
@@ -5649,93 +4200,7 @@
    return
def DoGiveCollectNPCAward(curPlayer, npcID, collectNPCIpyData, collectCnt=1, crossCollectOK=False, isSweep=False):
    GameWorld.DebugLog("给采集奖励: npcID=%s,collectCnt=%s,crossCollectOK=%s" % (npcID, collectCnt, crossCollectOK))
    if collectCnt <= 0:
        return
    if collectNPCIpyData.GetIsMissionCollectNPC():
        #GameWorld.DebugLog("任务采集物暂不处理")
        return
    isMaxTime = False # 是否达到了采集最大次数
    limitMaxTime = collectNPCIpyData.GetMaxCollectCount()
    if limitMaxTime > 0:
        todayCollTime = GetTodayCollectCount(curPlayer, npcID)
        canCollectCnt = max(0, limitMaxTime - todayCollTime)
        collectCnt = min(collectCnt, canCollectCnt)
        if collectCnt <= 0:
            GameWorld.DebugLog("    该NPC已达到最大采集次数: npcID=%s,todayCollTime=%s,limitMaxTime=%s" % (npcID, todayCollTime, limitMaxTime))
            return
        curCollTime = curPlayer.NomalDictGetProperty(ChConfig.Def_PDict_CollNpcIDCollTime % npcID)
        updCollTime = curCollTime + collectCnt
        PlayerControl.NomalDictSetProperty(curPlayer, ChConfig.Def_PDict_CollNpcIDCollTime % npcID, updCollTime)
        SyncCollNPCTime(curPlayer, [npcID])
        GameWorld.DebugLog("    增加采集次数: npcID=%s,todayCollTime=%s,curCollTime=%s,updCollTime=%s" % (npcID, todayCollTime, curCollTime, updCollTime))
        isMaxTime = todayCollTime + collectCnt >= limitMaxTime
    awardItemList = []
    collectAwardCfg = collectNPCIpyData.GetCollectAward()
    collectAppointAwardCfg = collectNPCIpyData.GetCollectAppointAward()
    if collectAppointAwardCfg:
        #缥缈草园的采集定制由缥缈寻访次数决定
        if collectNPCIpyData.GetCollectResetType() in [12, 14]:
            fairyDomainVisitCnt = curPlayer.NomalDictGetProperty(ChConfig.Def_PDict_FairyDomainVisitCnt)
            grasslandCollectAppointCfg = collectAppointAwardCfg.get(fairyDomainVisitCnt, {})
            curCollTime = curPlayer.NomalDictGetProperty(ChConfig.Def_PDict_CollNpcIDCollTime % npcID)
            if curCollTime in grasslandCollectAppointCfg:
                awardItemList.append(grasslandCollectAppointCfg[curCollTime])
            GameWorld.DebugLog("    草园采集定制奖励: fairyDomainVisitCnt=%s,curCollTime=%s,awardItemList=%s" % (fairyDomainVisitCnt, curCollTime, awardItemList))
        else:
            collTotalTime = min(curPlayer.NomalDictGetProperty(ChConfig.Def_PDict_CollNpcIDCollTimeTotal % npcID) + 1, ChConfig.Def_UpperLimit_DWord)
            if collTotalTime in collectAppointAwardCfg:
                awardItemList.append(collectAppointAwardCfg[collTotalTime])
            PlayerControl.NomalDictSetProperty(curPlayer, ChConfig.Def_PDict_CollNpcIDCollTimeTotal % npcID, collTotalTime)
            GameWorld.DebugLog("    采集次数定制奖励: collTotalTime=%s,awardItemList=%s" % (collTotalTime, awardItemList))
    if not awardItemList:
        alchemyDiffLV = collectNPCIpyData.GetAlchemyDiffLV()
        giveItemWeightList = ItemCommon.GetWeightItemListByAlchemyDiffLV(curPlayer, collectAwardCfg, alchemyDiffLV)
        GameWorld.DebugLog("    常规采集物品权重列表: alchemyDiffLV=%s,collectAwardCfg=%s,giveItemWeightList=%s" % (alchemyDiffLV, collectAwardCfg, giveItemWeightList))
        giveItemInfo = GameWorld.GetResultByWeightList(giveItemWeightList)
        if giveItemInfo:
            awardItemList.append(giveItemInfo)
    GameWorld.DebugLog("    最终采集奖励: awardItemList=%s" % awardItemList)
    jsonItemList = []
    if awardItemList:
        for itemID, itemCount, isAuctionItem in awardItemList:
            if ItemControler.GivePlayerItem(curPlayer, itemID, itemCount, isAuctionItem, [IPY_GameWorld.rptItem]):
                jsonItemList.append(ItemCommon.GetJsonItem([itemID, itemCount, isAuctionItem]))
        if not isSweep:
            if collectNPCIpyData.GetNotifyCollectResult():
                awardPack = ChPyNetSendPack.tagMCCollectAwardItemInfo()
                awardPack.CollectNPCID = npcID
                for itemID, itemCount, isAuctionItem in awardItemList:
                    awardItem = ChPyNetSendPack.tagMCCollectAwardItem()
                    awardItem.ItemID = itemID
                    awardItem.Count = itemCount
                    awardItem.IsAuctionItem = isAuctionItem
                    awardPack.AwardItemList.append(awardItem)
                awardPack.Count = len(awardPack.AwardItemList)
                NetPackCommon.SendFakePack(curPlayer, awardPack)
            GameLogic_CrossGrassland.RecordGrasslandAward(curPlayer, awardItemList)
    else:
        GameWorld.ErrLog("采集物品没有奖励!npcID=%s" % (npcID))
    #采集成就
    PlayerSuccess.DoAddSuccessProgress(curPlayer, ShareDefine.SuccType_Collect, collectCnt, [npcID])
    if crossCollectOK:
        PlayerActGarbageSorting.AddActGarbageTaskProgress(curPlayer, ChConfig.Def_GarbageTask_CrossCollect)
    #SyncCollectionItemInfo(curPlayer, addExp, addMoney, addZhenQi, giveItemInfoList, npcID)
    if not isSweep:
        GameLogic_CrossGrassland.DecCustomSceneNPCCount(curPlayer, npcID)
        if isMaxTime:
            GameLogic_CrossGrassland.DoCheckUpdateGrasslandEnd(curPlayer)
    return jsonItemList
    return
## 采集结果同步
#  @param None
@@ -5783,7 +4248,7 @@
def PlayerOnDay(curPlayer):
    #采集次数重置
    CollNPCTimeOnDay(curPlayer)
    itemDropLimitDayInfo = IpyGameDataPY.GetFuncEvalCfg("ItemDropCountLimit", 2, {})
    itemDropLimitDayInfo = {} #IpyGameDataPY.GetFuncEvalCfg("ItemDropCountLimit", 2, {})
    for itemID in itemDropLimitDayInfo.keys():
        if curPlayer.NomalDictGetProperty(ChConfig.Def_PDict_DropCountToday % itemID):
            PlayerControl.NomalDictSetProperty(curPlayer, ChConfig.Def_PDict_DropCountToday % itemID, 0)
@@ -5967,42 +4432,6 @@
    return
def SendGameServerGoodItemRecord(curPlayer, mapID, lineID, npcID, itemID, equipInfo=[]):
    # @param equipInfo: [equipPlace, itemClassLV, itemColor, itemQuality, itemUserData]
#    GameWorld.DebugLog("检查物品是否发送GameServer: mapID=%s, npcID=%s, playerName=%s, itemID=%s"
#                       % (mapID, npcID, playerName, itemID))
    recBossIDList = IpyGameDataPY.GetFuncEvalCfg('DropRecordBossID', 1)
    if npcID not in recBossIDList:
        return
    recDropEquipInfoDict = IpyGameDataPY.GetFuncEvalCfg('DropRecordEquipment', 1, {})
    recSpecialItemIDList = IpyGameDataPY.GetFuncEvalCfg('DropRecordValue', 1)
    recItemIDList = IpyGameDataPY.GetFuncEvalCfg('DropRecordNormal', 1)
    # weightValue  珍惜度 数值越大越珍贵(用于排序)
    needRecord = False
    itemUserData = ""
    if itemID in recItemIDList:
        needRecord = True
        weightValue = recItemIDList.index(itemID)
    elif itemID in recSpecialItemIDList:
        needRecord = True
        weightValue = recSpecialItemIDList.index(itemID) + 10000
    else:
        equipPlace, itemClassLV, itemColor, suiteID, itemUserData = equipInfo
        isSuit = 1 if suiteID else 0
        weightValue = itemColor*1000+isSuit*100+itemClassLV
        recordCondition = GameWorld.GetDictValueByKey(recDropEquipInfoDict, equipPlace)
        if recordCondition:
            needClassLV, needItemColor, needItemSuite = recordCondition
            if itemClassLV >= needClassLV and itemColor >= needItemColor and isSuit >= needItemSuite:
                needRecord = True
    if not needRecord:
        return
    playerID = curPlayer.GetID()
    playerName = curPlayer.GetName()
    serverGroupID = PlayerControl.GetPlayerServerGroupID(curPlayer)
    dropEquipMsg = str([playerID, playerName, mapID, lineID, npcID, itemID, itemUserData, weightValue, serverGroupID, curPlayer.GetLV()])
    GameWorld.GetPlayerManager().GameServer_QueryPlayerResult(0, 0, 0, 'BossDropGoodItem', dropEquipMsg, len(dropEquipMsg))
    GameWorld.DebugLog("发送GameServer记录拾取掉落好物品: %s" % dropEquipMsg, playerID)
    return
#// A5 52 购买功能NPC采集次数 #tagCMBuyCollectionCnt
@@ -6033,16 +4462,12 @@
    buyTimesVIPPriID = IpyGameDataPY.GetFuncEvalCfg("KillBossCntLimit1", 1, {}).get(killBossMark)
    if not buyTimesVIPPriID:
        return
    canBuyCnt = PlayerVip.GetPrivilegeValue(curPlayer, buyTimesVIPPriID)
    canBuyCnt = 0
    canBuyCnt += PlayerGoldInvest.GetAddBossBuyCnt(curPlayer, killBossMark)
    hasBuyCnt = curPlayer.NomalDictGetProperty(ChConfig.Def_PDict_Boss_KillCntBuyCnt%killBossMark, 0)
    playerID = curPlayer.GetPlayerID()
    if hasBuyCnt >= canBuyCnt:
        GameWorld.DebugLog('购买BOSS可击杀次数, 已达到今日最大可购买次数,hasBuyCnt=%s, canBuyCnt=%s'%(hasBuyCnt, canBuyCnt), playerID)
        return
    canKillCnt, dayTimesLimit = BossHurtMng.GetCanKillBossCnt(curPlayer, killBossMark)
    if canKillCnt >= dayTimesLimit:
        GameWorld.DebugLog('购买BOSS可击杀次数, 剩余次数已满!,canKillCnt=%s'%(canKillCnt), playerID)
        return
    
    costGold = IpyGameDataPY.GetFuncEvalCfg("KillBossCntLimit1", 2, {}).get(killBossMark)
@@ -6068,7 +4493,6 @@
        return
    # 增加购买次数
    PlayerControl.NomalDictSetProperty(curPlayer, ChConfig.Def_PDict_Boss_KillCntBuyCnt%killBossMark, hasBuyCnt + 1)
    BossHurtMng.NotifyAttackBossCnt(curPlayer, killBossMark)
    
    CrossPlayerData.SendMergePlayerDataNow(curPlayer)
    return
@@ -6117,32 +4541,10 @@
    GameWorld.DebugLog("通知GameServer地图Boss分流信息: mapID=%s,lineID=%s,shuntPlayerDict=%s" % (mapID, lineID, shuntPlayerDict), lineID)
    return
def NPCSpeedChangeNotify(curNPC, speed):
    ##通知NPC速度
    sendPack = ChNetSendPack.tagObjInfoRefresh()
    sendPack.Clear()
    sendPack.ObjID = curNPC.GetID()
    sendPack.ObjType = curNPC.GetGameObjType()
    sendPack.RefreshType = IPY_GameWorld.CDBPlayerRefresh_Speed
    sendPack.Value = speed
    curNPC.NotifyAll(sendPack.GetBuffer(), sendPack.GetLength())
    return
def UpdateNPCAttackCount(curPlayer, npcID, attackCount, maxCount=0):
    ## 更新玩家攻击NPC次数
    if not npcID:
        return
    GameWorld.DebugLog("更新玩家攻击NPC次数: npcID=%s,attackCount=%s,maxCount=%s" % (npcID, attackCount, maxCount))
    PlayerControl.NomalDictSetProperty(curPlayer, ChConfig.Def_PDict_NPCAttackCount % npcID, attackCount)
    if GameWorld.IsCrossServer():
        serverGroupID = PlayerControl.GetPlayerServerGroupID(curPlayer)
        msgInfo = {"PlayerID":curPlayer.GetPlayerID(), "NPCID":npcID, "AttackCount":attackCount, "MaxCount":maxCount}
        GameWorld.SendMsgToClientServer(ShareDefine.CrossServerMsg_NPCAttackCount, msgInfo, [serverGroupID])
    else:
        SyncNPCAttackCount(curPlayer, [npcID])
        if attackCount and attackCount >= maxCount:
            GameLogic_CrossGrassland.DoCheckUpdateGrasslandEnd(curPlayer)
    return
def CrossServerMsg_NPCAttackCount(curPlayer, msgData):
@@ -6180,64 +4582,6 @@
#  @param curNPC 被攻击NPC
#  @return None
def __OnAttackedDropItem(atkObj, curNPC):
    attackPlayer, npcObjType = AttackCommon.GetAttackPlayer(atkObj)
    if npcObjType:
        return
    if not attackPlayer:
        return
    npcID = curNPC.GetNPCID()
    ipyData = IpyGameDataPY.GetIpyGameDataNotLog("TreasureNPC", npcID)
    if not ipyData:
        return
    attackCountDropWeightInfo = ipyData.GetAttackCountDropWeightInfo()
    attackDropWeightList = ipyData.GetAttackDropWeightList()
    attackDropWeightListEx = ipyData.GetAttackDropWeightListEx()
    dropCountEx = ipyData.GetDropCountEx()
    alchemyDiffLV = ipyData.GetAlchemyDiffLV()
    mainItemWeightList = []
    if attackCountDropWeightInfo:
        maxCount = max(attackCountDropWeightInfo)
        attackCount = attackPlayer.NomalDictGetProperty(ChConfig.Def_PDict_NPCAttackCount % npcID) + 1
        if attackCount <= maxCount:
            if attackCount in attackCountDropWeightInfo:
                mainItemWeightList = attackCountDropWeightInfo[attackCount]
            UpdateNPCAttackCount(attackPlayer, npcID, attackCount, maxCount)
    if mainItemWeightList:
        mainItemWeightList = ItemCommon.GetWeightItemListByAlchemyDiffLV(attackPlayer, mainItemWeightList, alchemyDiffLV)
    elif attackDropWeightList:
        mainItemWeightList = ItemCommon.GetWeightItemListByAlchemyDiffLV(attackPlayer, attackDropWeightList, alchemyDiffLV)
    mainItemInfo = GameWorld.GetResultByWeightList(mainItemWeightList)
    if not mainItemInfo:
        notDropNotify = ipyData.GetNotDropNotify()
        if notDropNotify:
            PlayerControl.NotifyCode(attackPlayer, notDropNotify)
        return
    dropItemList = []
    if mainItemInfo:
        dropItemList.append(mainItemInfo)
    if attackDropWeightListEx and dropCountEx:
        weightListEx = ItemCommon.GetWeightItemListByAlchemyDiffLV(attackPlayer, attackDropWeightListEx, alchemyDiffLV)
        for _ in xrange(dropCountEx):
            itemInfo = GameWorld.GetResultByWeightList(weightListEx)
            if itemInfo:
                dropItemList.append(itemInfo)
    if not dropItemList:
        return
    mapID = PlayerControl.GetCustomMapID(attackPlayer)
    if mapID:
        DoGiveItemByVirtualDrop(attackPlayer, dropItemList, npcID)
        GameLogic_CrossGrassland.RecordGrasslandAward(attackPlayer, dropItemList)
    else:
        dropPosX, dropPosY = curNPC.GetPosX(), curNPC.GetPosY()
        ChItem.DoMapDropItem(attackPlayer, dropItemList, npcID, dropPosX, dropPosY, isOnlySelfSee=False)
    return