hxp
2025-10-24 ff401cef9cd3ca3c0c76542cbd2a35207cb9b83a
ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Player/PlayerOnline.py
@@ -9,36 +9,256 @@
# @date 2025-07-02
# @version 1.0
#
# 详细描述: 在线玩家管理,用于管理在线玩家、准在线玩家的临时数据
# 详细描述: 在线玩家管理,用于管理在线玩家、准在线玩家的临时数据,重读不会被重置
#         准在线玩家 - 实际不在线,x分钟内离线的玩家,用于支持断线重连,短时间内临时数据可持续
#
#-------------------------------------------------------------------------------
#"""Version = 2025-07-02 17:30"""
#-------------------------------------------------------------------------------
import BattleObj
import TurnAttack
import PyGameData
import ShareDefine
import PlayerControl
import IpyGameDataPY
import FormulaControl
import PlayerPrestigeSys
import PlayerFamily
import PlayerGubao
import PlayerHero
import PlayerHJG
import GameWorld
import ChConfig
import ChEquip
import time
class LineupHero():
    ## 阵容武将,注意:同一个武将在不同阵容中可能属性不一样
    def __init__(self):
        self.Clear()
        return
    def Clear(self):
        self.itemIndex = 0
        self.heroID = 0
        self.skinID = 0
        self.posNum = 0
        self.heroBatAttrDict = {} # 武将的最终战斗属性字典, {attrID:value, ...}
        self.heroSkillIDList = [] # 武将拥有的技能ID列表 [skillID, ...]
        self.fightPower = 0 # 武将最终战力
        self.skillFightPower = 0 # 技能战力
        return
class Lineup():
    ## 阵容
    def __init__(self, playerID, lineupID):
        self.playerID = playerID
        self.lineupID = lineupID
        self.olPlayer = None
        self.shapeType = 0
        self.heroItemDict = {} # 阵容武将背包索引信息  {itemIndex:posNum, ...}
        self.lineupChange = False # 是否已变更阵容标记,在刷新属性后重置标记
        self.__refreshState = 0 # 刷属性标记, 0-不需要刷新了,1-需要刷新
        self.__freeLineupHeroObjs = [] # 释放的空闲对象[LineupHero, ...]
        self.__lineupHeroDict = {} # 刷新阵容后的武将信息 {posNum:LineupHero, ...}
        self.fightPower = 0 # 阵容总战力
        return
    def UpdLineup(self, heroItemDict, shapeType=0, refreshForce=False, isReload=False):
        '''变更阵容时更新
        @param heroItemDict: 武将背包索引信息  {itemIndex:posNum, ...}
        @param shapeType: 阵型
        @param refreshForce: 是否强制刷属性
        '''
        if not isReload: # 非重读阵容的视为变更
            self.lineupChange = True
        self.shapeType = shapeType
        self.heroItemDict = heroItemDict
        GameWorld.DebugLog("更新阵容: lineupID=%s,%s" % (self.lineupID, heroItemDict), self.playerID)
        self.RefreshLineupAttr(refreshForce)
        if not isReload and self.olPlayer.curPlayer:
            PlayerHero.Sync_Lineup(self.olPlayer.curPlayer, self.lineupID)
        return
    def IsEmpty(self): return (not self.__lineupHeroDict or not self.heroItemDict)
    def GetPosNumList(self): return self.__lineupHeroDict.keys()
    def FreeLineupHero(self):
        ## 释放阵容武将对象,重新计算
        for freeObj in self.__lineupHeroDict.values():
            if freeObj not in self.__freeLineupHeroObjs:
                self.__freeLineupHeroObjs.append(freeObj)
        self.__lineupHeroDict = {}
        self.fightPower = 0
        return
    def GetLineupHero(self, posNum):
        lineupHero = None
        if posNum in self.__lineupHeroDict:
            lineupHero = self.__lineupHeroDict[posNum]
        elif self.__freeLineupHeroObjs:
            lineupHero = self.__freeLineupHeroObjs.pop(0)
            lineupHero.Clear()
            self.__lineupHeroDict[posNum] = lineupHero
        else:
            lineupHero = LineupHero()
            self.__lineupHeroDict[posNum] = lineupHero
        return lineupHero
    def GetLineupHeroByID(self, heroID):
        lineupHero = None
        for posNum in self.__lineupHeroDict.keys():
            lineupHero = self.GetLineupHero(posNum)
            if lineupHero.heroID == heroID:
                return lineupHero
        if False:
            lineupHero = LineupHero()
        return lineupHero
    def SetNeedRefreshState(self):
        ## 设置需要刷属性
        self.__refreshState = 1
        return
    def RefreshLineupAttr(self, refreshForce=False):
        self.__refreshState = 1 # 标记要刷新
        if refreshForce:
            self.CheckRefreshLineupAttr()
        return
    def CheckRefreshLineupAttr(self):
        ## 检查刷新阵容属性
        if not self.__refreshState:
            return False
        self.__refreshState = 0
        doRefreshLineupAttr(self.olPlayer.curPlayer, self.olPlayer, self)
        self.lineupChange = False
        return True
    def CheckHeroItemUpdate(self, itemIndex):
        if itemIndex not in self.heroItemDict:
            return
        self.RefreshLineupAttr()
        return True
class OnlinePlayer():
    ## 准在线玩家临时数据
    ## 玩家在线临时数据,主要时数据的缓存,逻辑可不在类中处理,方便重读脚本时测试
    
    def __init__(self, playerID):
        self.playerID = playerID
        self.curPlayer = None
        # 属性、阵容
        self.calcAttrDict = {} # 非武将功能点属性统计 {calcIndex:{attrID:value, ...}, ...}
        self.lineupDict = {} # 上阵阵容 {lineupID:Lineup, ...}
        # 主线战斗
        self.mainFight = TurnAttack.MainFight(playerID)
        return
    def OnPlayerLogin(self, curPlayer):
        self.mainFight.playerLogin(curPlayer)
        return
    def OnPlayerOffline(self, curPlayer):
        self.mainFight.playerOffline(curPlayer)
        self._lastBatBufferInfo = [] # 最后一场战斗临时回放 ["guid", "buffer"]
        return
    
    def OnClear(self):
        self.mainFight.clear()
        self.mainFight.turnFight.exitFight()
        return
    def SetPlayer(self, curPlayer):
        self.curPlayer = curPlayer
        self.mainFight.turnFight.curPlayer = curPlayer
        return
    def IsRealOnline(self):
        ## 是否真的在线
        return self.curPlayer != None
    def GetLineup(self, lineupID, checkAttr=True):
        # @param checkAttr: 检查刷新到最新阵容属性
        lineup = None
        if lineupID in self.lineupDict:
            lineup = self.lineupDict[lineupID]
        else:
            lineup = Lineup(self.playerID, lineupID)
            self.lineupDict[lineupID] = lineup
        lineup.olPlayer = self
        if checkAttr:
            lineup.CheckRefreshLineupAttr()
        return lineup
    def GetCalcAttr(self, calcIndex): return self.calcAttrDict.get(calcIndex, {})
    def SetCalcAttr(self, calcIndex, attrDict):
        ## 设置某个功能点计算的属性
        self.calcAttrDict[calcIndex] = attrDict
        return
    def ReCalcAllAttr(self):
        ## 重置所有功能点计算的属性,一般登录的时候调用一次即可,其他单功能刷新的话一般使用 RefreshRoleAttr
        curPlayer = self.curPlayer
        GameWorld.DebugLog("ReCalcAllAttr...", self.playerID)
        self.calcAttrDict = {}
        self.lineupDict = {}
        doCalcAllAttr(curPlayer)
        doReloadLineup(curPlayer, self)
        self.RefreshRoleAttr()
        return
    def RefreshRoleAttr(self, refreshForce=False, isAllLineup=False):
        '''刷新主公属性,影响主公属性的功能点属性变化时统一调用此函数
        @param refreshForce: 是否强制立马刷新
        @param isAllLineup: 是否只同步刷所有阵容属性,如果设置False则默认仅刷主阵容属性
        '''
        GameWorld.DebugLog("请求刷属性: refreshForce=%s" % (refreshForce), self.playerID)
        # 主公属性刷新时,所有阵容都要同步刷新
        for lineup in self.lineupDict.values():
            lineup.SetNeedRefreshState()
        if refreshForce:
            self.DoRefreshRoleAttr(isAllLineup)
        return
    def DoRefreshRoleAttr(self, isAllLineup=False):
        '''执行刷属性,默认额外刷主阵容,其他阵容可以用到的时候再刷新
        @param isAllLineup: 是否刷所有阵容,如果设置False则默认仅刷主阵容属性
        @return: 是否有刷属性,0-无;1-有
        '''
        isRefresh = False
        # 同步执行阵容属性刷新
        for lineupID, lineup in self.lineupDict.items():
            if not isAllLineup and lineupID != ShareDefine.Lineup_Main:
                continue
            if lineup.CheckRefreshLineupAttr():
                isRefresh = True
        return isRefresh
    def OnHeroItemUpate(self, itemIndexList):
        '''武将物品养成更新
        @param itemIndexList: 变化武将物品所在武将背包格子索引列表
        @param return: 影响的阵容ID列表
        '''
        effLineupIDList = []
        for lineupID, lineup in self.lineupDict.items():
            for itemIndex in itemIndexList:
                if lineup.CheckHeroItemUpdate(itemIndex):
                    if lineupID not in effLineupIDList:
                        effLineupIDList.append(lineupID)
        GameWorld.DebugLog("武将物品养成更新索引: %s, 影响阵容:%s" % (itemIndexList, effLineupIDList), self.playerID)
        return effLineupIDList
    def GetLastBatBuffer(self): return self._lastBatBufferInfo
    def SetLastBatBuffer(self, guid, batBuffer):
        self._lastBatBufferInfo = [guid, batBuffer]
        return
    
class OnlineMgr():
@@ -57,7 +277,7 @@
        else:
            olPlayer = OnlinePlayer(playerID)
            self.__onlinePlayerDict[playerID] = olPlayer
        olPlayer.SetPlayer(curPlayer)
        return olPlayer
    
    def SetPlayerOnline(self, curPlayer):
@@ -69,7 +289,7 @@
            self.__onlinePlayerDict[playerID] = olPlayer
        else:
            olPlayer = self.__onlinePlayerDict[playerID]
        olPlayer.OnPlayerLogin(curPlayer)
        olPlayer.SetPlayer(curPlayer)
        return
    
    def SetPlayerOffline(self, curPlayer):
@@ -78,7 +298,7 @@
        if playerID not in self.__onlinePlayerDict:
            return
        olPlayer = self.__onlinePlayerDict[playerID]
        olPlayer.OnPlayerOffline(curPlayer)
        olPlayer.SetPlayer(None)
        self.__offlinePlayerTimeDict[playerID] = int(time.time())
        return
    
@@ -104,15 +324,511 @@
        mgr = OnlineMgr()
        PyGameData.g_onlineMgr = mgr
    return mgr
def GetOnlinePlayer(curPlayer): return GetOnlineMgr().GetOnlinePlayer(curPlayer)
def OnPlayerLogin(curPlayer):
    ## 需登录逻辑最早调用
    GetOnlineMgr().SetPlayerOnline(curPlayer)
    return
def OnPlayerLogoff(curPlayer):
    ## 需下线逻辑最后调用
    GetOnlineMgr().SetPlayerOffline(curPlayer)
    return
def OnMinute():
    GetOnlineMgr().ProcessOffline()
    return
def CalcRoleBase(curPlayer):
    playerID = curPlayer.GetID()
    playerLV = curPlayer.GetLV()
    lvIpyData = PlayerControl.GetPlayerLVIpyData(playerLV)
    lvAttrDict = {}
    if lvIpyData:
        lvAttrDict = {ChConfig.AttrID_Atk:lvIpyData.GetAtk(),
                    ChConfig.AttrID_Def:lvIpyData.GetDef(),
                    ChConfig.AttrID_MaxHP:lvIpyData.GetMaxHP()
                    }
    GameWorld.DebugLog("角色等级属性: %s" % lvAttrDict, playerID)
    GetOnlinePlayer(curPlayer).SetCalcAttr(ChConfig.Def_CalcAttr_LV, lvAttrDict)
    return
def doReloadLineup(curPlayer, olPlayer):
    ## 重新载入阵容
    loadLineupIDList = ShareDefine.LineupList
    lineupDict = {} # {阵容ID:{itemIndex:posNum, ...}, ...}
    lineShapeTypeDict = {} # {阵容ID:阵型, ...}
    syncItemDict = {} # 需要同步的异常物品 {index:heroItem, ...}
    curPack = curPlayer.GetItemManager().GetPack(ShareDefine.rptHero)
    for index in range(curPack.GetCount()):
        heroItem = curPack.GetAt(index)
        if not heroItem or heroItem.IsEmpty():
            continue
        lineupCount = heroItem.GetUserAttrCount(ShareDefine.Def_IudetHeroLineup)
        if not lineupCount:
            continue
        delValueList = []
        for lpIndex in range(lineupCount)[::-1]:
            lineupValue = heroItem.GetUserAttrByIndex(ShareDefine.Def_IudetHeroLineup, lpIndex)
            lineupID, shapeType, posNum = PlayerHero.GetLineupValue(lineupValue)
            if lineupID not in loadLineupIDList:
                continue
            # 任意取一个武将保存的阵型即可,同阵容的武将理论上保存的阵型是一样的
            if lineupID not in lineShapeTypeDict:
                lineShapeTypeDict[lineupID] = shapeType
            if lineupID not in lineupDict:
                lineupDict[lineupID] = {}
            heroItemDict = lineupDict[lineupID]
            # 超出人数限制或位置异常
            if len(heroItemDict) >= ShareDefine.LineupObjMax or posNum in heroItemDict.values() or index in heroItemDict:
                delValueList.append(lineupValue)
            else:
                heroItemDict[index] = posNum
        if delValueList:
            item = heroItem.GetItem()
            for lineupValue in delValueList:
                item.DelUserAttr(ShareDefine.Def_IudetHeroLineup, lineupValue)
            syncItemDict[index] = heroItem
    for syncItem in syncItemDict.values():
        syncItem.Sync_Item()
    GameWorld.DebugLog("重载阵容: %s" % lineupDict, curPlayer.GetPlayerID())
    for lineupID, heroItemDict in lineupDict.items():
        lineup = olPlayer.GetLineup(lineupID, False)
        # 获取其他绑定该阵容的功能,如红颜、灵兽等
        shapeType = lineShapeTypeDict.get(lineupID, 0)
        lineup.UpdLineup(heroItemDict, shapeType, isReload=True)
    PlayerHero.Sync_Lineup(curPlayer)
    return
def doCalcAllAttr(curPlayer):
    ## 计算所有属性
    GameWorld.DebugLog("doCalcAllAttr...", curPlayer.GetPlayerID())
    CalcRoleBase(curPlayer)
    ChEquip.CalcRoleEquipAttr(curPlayer)
    PlayerHero.CalcHeroAddAttr(curPlayer)
    PlayerPrestigeSys.CalcOfficialRankAttr(curPlayer)
    PlayerGubao.CalcGubaoAttr(curPlayer)
    PlayerHJG.CalcHJGAttr(curPlayer)
    return
def doRefreshLineupAttr(curPlayer, olPlayer, lineup):
    ''' 刷新某个阵容属性
        基础属性-面板显示:
        1.全体基础固定值=所有穿戴装备【装备基础固定值】+【法宝基础固定值】+【红颜基础固定值】+【其它模块的固定值】
        2.全体百分比加成=图鉴加成+【灵兽模块】+【红颜模块】+【其它模块】+所有上阵卡牌【初始加成+升级加成+突破加成+吞噬加成】
        3.卡牌继承比例=卡牌品质及职业继承比例不同
        4.卡牌自身培养加成=【羁绊加成%+突破词条加成%+天赋加成%】
        最终面板生命=【E全体基础固定值】*【1+E全体百分比加成】*【卡牌自身继承比例+ 卡牌自身培养%加成】+【卡牌自身固定值】
        战斗属性/战斗抗性/特殊属性-面板显示:
        1.全体战斗属性值=所有穿戴装备【装备战斗属性值】+【法宝战斗属性值】+【红颜战斗属性值】+【其它模块的战斗属性】
        2.卡牌继承比例=默认100%
        3.卡牌自身培养战斗属性=【卡牌初始战斗属性+突破词条战斗属性+天赋战斗属性+觉醒战斗属性】+【法则洗炼】+【秘能装备】+【其它模块】
        最终面板战斗属性=【E全体战斗属性值】*【卡牌继承比例】+【卡牌自身培养战斗属性】
    '''
    playerID = curPlayer.GetPlayerID()
    lineupID = lineup.lineupID
    GameWorld.DebugLog("刷新阵容属性: lineupID=%s" % lineupID, playerID)
    GameWorld.DebugLog("    itemIndex-posNum : %s" % lineup.heroItemDict, playerID)
    lineup.FreeLineupHero()
    # 因为同阵容的武将ID不能重复,所以字典key可以用武将ID
    countryHeroInfo = {} # 国家武将统计 {country:[heroID, ...], ...}
    fetterHeroInfo = {} # 阵容羁绊武将统计信息 {fetterID:[heroID, ...], ...}
    heroSelfAttrInfo = {} # 武将自身属性 {heroID:{attrID:value, ...}, ...}
    heroStarTalentInfo = {} # 武将星级天赋属性 {heroID:{attrID:value, ...}, ...}
    heroBreakAttrInfo = {} # 武将突破潜能属性 {heroID:{attrID:value, ...}, ...}
    heroAwakeTalentInfo = {} # 武将觉醒天赋属性 {heroID:{attrID:value, ...}, ...}
    # 上阵卡牌【初始加成+升级加成+突破加成+吞噬加成】
    InitAddPer, LVAddPer, BreakLVAddPer, StarAddPer = 0, 0, 0, 0
    curPack = curPlayer.GetItemManager().GetPack(ShareDefine.rptHero)
    for itemIndex, posNum in lineup.heroItemDict.items():
        if itemIndex < 0 or itemIndex >= curPack.GetCount():
            continue
        heroItem = curPack.GetAt(itemIndex)
        if not heroItem or heroItem.IsEmpty():
            continue
        heroID = heroItem.GetItemTypeID()
        heroIpyData = IpyGameDataPY.GetIpyGameData("Hero", heroID)
        if not heroIpyData:
            continue
        quality = heroIpyData.GetQuality()
        qualityIpyData = IpyGameDataPY.GetIpyGameData("HeroQuality", quality)
        if not qualityIpyData:
            continue
        heroLV = heroItem.GetUserAttr(ShareDefine.Def_IudetHeroLV)
        star = heroItem.GetUserAttr(ShareDefine.Def_IudetHeroStar)
        breakLV = heroItem.GetUserAttr(ShareDefine.Def_IudetHeroBreakLV)
        awakeLV = heroItem.GetUserAttr(ShareDefine.Def_IudetHeroAwakeLV)
        skinIndex = heroItem.GetUserAttr(ShareDefine.Def_IudetHeroSkin)
        skinID = 0
        skinIDList = heroIpyData.GetSkinIDList()
        if skinIndex < 0 or skinIndex >= len(skinIDList):
            skinID = skinIDList[skinIndex]
        elif skinIDList:
            skinID = skinIDList[0]
        starMax = PlayerHero.GetHeroStarMax(heroItem)
        InitAddPer += qualityIpyData.GetInitAddPer()
        LVAddPer += qualityIpyData.GetLVAddPer() * max(0, heroLV - 1)
        BreakLVAddPer += qualityIpyData.GetBreakLVAddPer() * breakLV
        StarAddPer += qualityIpyData.GetStarAddPer() * min(star, starMax)
        lineupHero = lineup.GetLineupHero(posNum)
        #if False:
        #    lineupHero = LineupHero()
        lineupHero.itemIndex = itemIndex
        lineupHero.posNum = posNum
        lineupHero.heroID = heroID
        lineupHero.skinID = skinID
        lineupHero.heroBatAttrDict = {}
        lineupHero.heroSkillIDList = []
        lineupHero.fightPower = 0
        normalSkillID = heroIpyData.GetNormalSkillID()
        angerSkillID = heroIpyData.GetAngerSkillID()
        lineupHero.heroSkillIDList.extend([normalSkillID, angerSkillID])
        # 自身属性
        selfAttrDict = {}
        selfAttrDict.update({ChConfig.AttrID_AtkInheritPer:heroIpyData.GetAtkInheritPer(),
                             ChConfig.AttrID_DefInheritPer:heroIpyData.GetDefInheritPer(),
                             ChConfig.AttrID_HPInheritPer:heroIpyData.GetHPInheritPer(),
                             })
        for k, v in heroIpyData.GetBatAttrDict().items():
            selfAttrDict[int(k)] = v
        heroSelfAttrInfo[heroID] = selfAttrDict
        # 突破潜能
        breakAttrDict = {}
        breakIpyDataList = IpyGameDataPY.GetIpyGameDataList("HeroBreak", heroID)
        if breakIpyDataList:
            for breakIpyData in breakIpyDataList:
                if breakIpyData.GetBreakLV() > breakLV:
                    break
                attrIDList = breakIpyData.GetAttrIDList()
                attrValueList = breakIpyData.GetAttrValueList()
                for aIndex in range(min(len(attrIDList), len(attrValueList))):
                    attrID = attrIDList[aIndex]
                    attrValue = attrValueList[aIndex]
                    breakAttrDict[attrID] = breakAttrDict.get(attrID, 0) + attrValue
                skillID = breakIpyData.GetSkillID()
                if skillID:
                    lineupHero.heroSkillIDList.append(skillID)
        heroBreakAttrInfo[heroID] = breakAttrDict
        # 觉醒天赋
        maxUnlockSlot = IpyGameDataPY.GetFuncCfg("HeroStarTalent", 1) # 常规天赋槽个数
        awakeTalentAttrDict = {}
        awakeIpyDataList = IpyGameDataPY.GetIpyGameDataListNotLog("HeroAwake", heroID)
        if awakeIpyDataList:
            for awakeIpyData in awakeIpyDataList:
                if awakeIpyData.GetAwakeLV() > awakeLV:
                    break
                unlockTalentSlot = awakeIpyData.GetUnlockTalentSlot()
                maxUnlockSlot = max(maxUnlockSlot, unlockTalentSlot)
                attrIDList = awakeIpyData.GetAttrIDList()
                attrValueList = awakeIpyData.GetAttrValueList()
                for aIndex in range(min(len(attrIDList), len(attrValueList))):
                    attrID = attrIDList[aIndex]
                    attrValue = attrValueList[aIndex]
                    awakeTalentAttrDict[attrID] = awakeTalentAttrDict.get(attrID, 0) + attrValue
                skillID = awakeIpyData.GetSkillID()
                if skillID:
                    lineupHero.heroSkillIDList.append(skillID)
        heroAwakeTalentInfo[heroID] = awakeTalentAttrDict
        # 星级天赋
        starTalentAttrDict = {}
        idCount = heroItem.GetUserAttrCount(ShareDefine.Def_IudetHeroTalentID)
        lvCount = heroItem.GetUserAttrCount(ShareDefine.Def_IudetHeroTalentIDLV)
        for aIndex in range(min(idCount, lvCount, maxUnlockSlot)): # 重生导致已觉醒槽位失效时属性也无效
            talentID = heroItem.GetUserAttrByIndex(ShareDefine.Def_IudetHeroTalentID, aIndex)
            talentLV = heroItem.GetUserAttrByIndex(ShareDefine.Def_IudetHeroTalentIDLV, aIndex)
            stIpyData = IpyGameDataPY.GetIpyGameData("HeroTalent", talentID)
            if not stIpyData:
                continue
            attrID = stIpyData.GetAttrID()
            attrValue = stIpyData.GetAttrValue() * talentLV
            starTalentAttrDict[attrID] = starTalentAttrDict.get(attrID, 0) + attrValue
        heroStarTalentInfo[heroID] = starTalentAttrDict
        # 羁绊统计
        for fetterID in heroIpyData.GetFetterIDList():
            if fetterID not in fetterHeroInfo:
                fetterHeroInfo[fetterID] = []
            fetterHeroIDList = fetterHeroInfo[fetterID]
            if heroID not in fetterHeroIDList:
                fetterHeroIDList.append(heroID)
        # 国家统计
        country = heroIpyData.GetCountry()
        if country not in countryHeroInfo:
            countryHeroInfo[country] = []
        countryHeroIDList = countryHeroInfo[country]
        if heroID not in countryHeroIDList:
            countryHeroIDList.append(heroID)
    # 羁绊属性 - 仅羁绊相关武将有效
    heroFetterAttrInfo = {} # 武将羁绊属性 {heroID:{attrID:value, ...}, ...}
    for fetterID, fetterHeroIDList in fetterHeroInfo.items():
        fetterIpyData = IpyGameDataPY.GetIpyGameData("HeroFetter", fetterID)
        if not fetterIpyData:
            continue
        needHeroIDList = fetterIpyData.GetHeroIDList()
        canFetter = True
        for needHeroID in needHeroIDList:
            if needHeroID not in fetterHeroIDList:
                canFetter = False
                break
        if not canFetter:
            continue
        attrIDList = fetterIpyData.GetAttrIDList()
        attrValueList = fetterIpyData.GetAttrValueList()
        for aIndex in range(min(len(attrIDList), len(attrValueList))):
            attrID = attrIDList[aIndex]
            attrValue = attrValueList[aIndex]
            for heroID in needHeroIDList:
                if heroID not in heroFetterAttrInfo:
                    heroFetterAttrInfo[heroID] = {}
                heroFetterAttrDict = heroFetterAttrInfo[heroID]
                heroFetterAttrDict[attrID] = heroFetterAttrDict.get(attrID, 0) + attrValue
    # 阵容属性 - 阵容所有武将有效
    lineupHaloAttrInfo = {} # 阵容光环属性 {attrID:value, ...}
    for country, countryHeroIDList in countryHeroInfo.items():
        haloIpyDataList = IpyGameDataPY.GetIpyGameDataList("HeroLineupHalo", country)
        if not haloIpyDataList:
            continue
        attrIDList, attrValueList = [], []
        countryHeroCnt = len(countryHeroIDList)
        for haloIpyData in haloIpyDataList:
            needHeroCount = haloIpyData.GetNeedHeroCount()
            if countryHeroCnt < needHeroCount:
                break
            attrIDList = haloIpyData.GetAttrIDList()
            attrValueList = haloIpyData.GetAttrValueList()
        # 每个国家最多仅生效一条属性,不同国家属性可叠加
        for aIndex in range(min(len(attrIDList), len(attrValueList))):
            attrID = attrIDList[aIndex]
            attrValue = attrValueList[aIndex]
            lineupHaloAttrInfo[attrID] = lineupHaloAttrInfo.get(attrID, 0) + attrValue
    # --------------------------- 上面统计好了,下面计算武将最终属性 --------------------------------
    baseAttrFormula = IpyGameDataPY.GetFuncCfg("HeroAttrFormula", 1)
    otherAttrFormula = IpyGameDataPY.GetFuncCfg("HeroAttrFormula", 2)
    fightPowerFormula = IpyGameDataPY.GetFuncCfg("HeroAttrFormula", 3)
    skillFPFormula = IpyGameDataPY.GetFuncCfg("HeroAttrFormula", 4)
    lvAttrDict = olPlayer.GetCalcAttr(ChConfig.Def_CalcAttr_LV)
    equipAttrDict = olPlayer.GetCalcAttr(ChConfig.Def_CalcAttr_MainEquip)
    bookAttrDict = olPlayer.GetCalcAttr(ChConfig.Def_CalcAttr_HeroBook)
    realmAttrDict = olPlayer.GetCalcAttr(ChConfig.Def_CalcAttr_Realm)
    gubaoAttrDict = olPlayer.GetCalcAttr(ChConfig.Def_CalcAttr_Gubao)
    hjgAttrDict = olPlayer.GetCalcAttr(ChConfig.Def_CalcAttr_HJG)
    GameWorld.DebugLog("    国家武将统计=%s" % countryHeroInfo, playerID)
    GameWorld.DebugLog("    羁绊武将统计=%s" % fetterHeroInfo, playerID)
    GameWorld.DebugLog("    武将自身属性=%s" % heroSelfAttrInfo, playerID)
    GameWorld.DebugLog("    武将吞噬属性=%s" % heroStarTalentInfo, playerID)
    GameWorld.DebugLog("    武将突破潜能=%s" % heroBreakAttrInfo, playerID)
    GameWorld.DebugLog("    武将觉醒天赋=%s" % heroAwakeTalentInfo, playerID)
    GameWorld.DebugLog("    武将羁绊属性=%s" % heroFetterAttrInfo, playerID)
    GameWorld.DebugLog("    阵容光环属性=%s" % lineupHaloAttrInfo, playerID)
    GameWorld.DebugLog("    阵容上阵加成=InitAddPer=%s,LVAddPer=%s,BreakLVAddPer=%s,StarAddPer=%s" % (InitAddPer, LVAddPer, BreakLVAddPer, StarAddPer), playerID)
    GameWorld.DebugLog("    主公等级属性=%s" % lvAttrDict, playerID)
    GameWorld.DebugLog("    主公装备属性=%s" % equipAttrDict, playerID)
    GameWorld.DebugLog("    主公图鉴属性=%s" % bookAttrDict, playerID)
    GameWorld.DebugLog("    主公官职属性=%s" % realmAttrDict, playerID)
    GameWorld.DebugLog("    主公古宝属性=%s" % gubaoAttrDict, playerID)
    GameWorld.DebugLog("    主幻境阁属性=%s" % hjgAttrDict, playerID)
    PlayerLV = curPlayer.GetLV()
    OfficialLV = curPlayer.GetOfficialRank()
    GameWorld.DebugLog("    PlayerLV=%s,OfficialLV=%s" % (PlayerLV, OfficialLV), playerID)
    fpRatioIpyData = IpyGameDataPY.GetIpyGameData("FightPowerRatio", OfficialLV)
    lineupFightPower = 0 # 阵容总战力
    InitAddPer, LVAddPer, BreakLVAddPer, StarAddPer = InitAddPer / 10000.0, LVAddPer / 10000.0, BreakLVAddPer / 10000.0, StarAddPer / 10000.0
    for heroID, selfAttrDict in heroSelfAttrInfo.items():
        lineupHero = lineup.GetLineupHeroByID(heroID)
        if not lineupHero:
            continue
        lineupHero.heroBatAttrDict = {}
        lineupHero.fightPower = 0
        starTalentAttrDict = heroStarTalentInfo.get(heroID, {})
        breakAttrDict = heroBreakAttrInfo.get(heroID, {})
        awakeTalentAttrDict = heroAwakeTalentInfo.get(heroID, {})
        fetterAttrDict = heroFetterAttrInfo.get(heroID, {})
        logAttrDict = {}
        fightPowerParamDict = {}
        for attrID in ChConfig.CalcBattleAttrIDList:
            attrPerID = ChConfig.AttrPerDict.get(attrID, 0) # 对应百分比提升的属性ID
            lvValue = lvAttrDict.get(attrID, 0)
            equipValue = equipAttrDict.get(attrID, 0)
            bookValue = bookAttrDict.get(attrID, 0)
            bookPer = bookAttrDict.get(attrPerID, 0) / 10000.0 if attrPerID else 0
            realmValue = realmAttrDict.get(attrID, 0)
            realmPer = realmAttrDict.get(attrPerID, 0) / 10000.0 if attrPerID else 0
            gubaoValue = gubaoAttrDict.get(attrID, 0)
            gubaoPer = gubaoAttrDict.get(attrPerID, 0) / 10000.0 if attrPerID else 0
            hjgValue = hjgAttrDict.get(attrID, 0)
            hjgPer = hjgAttrDict.get(attrPerID, 0) / 10000.0 if attrPerID else 0
            lineupInitAddPer, lineupLVAddPer, lineupBreakLVAddPer, lineupStarAddPer = 0, 0, 0, 0
            if attrID in ChConfig.BaseAttrIDList:
                lineupInitAddPer, lineupLVAddPer, lineupBreakLVAddPer, lineupStarAddPer = InitAddPer, LVAddPer, BreakLVAddPer, StarAddPer
            heroSelfValue, heroSelfPer = selfAttrDict.get(attrID, 0), 0 # 武将自身基值
            inheritPer = 1 # 继承比例,默认100%
            if attrID in ChConfig.AttrInheritPerDict:
                attrInheritPerID = ChConfig.AttrInheritPerDict[attrID] # 继承ID
                inheritPer = selfAttrDict.get(attrInheritPerID, 10000) # 继承比例从武将自身属性中取
                inheritPer /= 10000.0
            lineupHaloValue, lineupHaloPer = lineupHaloAttrInfo.get(attrID, 0), 0
            fetterValue, fetterPer = fetterAttrDict.get(attrID, 0), 0
            starTalentValue, starTalentPer = starTalentAttrDict.get(attrID, 0), 0
            breakLVValue, breakLVPer = breakAttrDict.get(attrID, 0), 0
            awakeTalentValue, awakeTalentPer = awakeTalentAttrDict.get(attrID, 0), 0
            if attrPerID:
                heroSelfPer = selfAttrDict.get(attrPerID, 0) / 10000.0
                lineupHaloPer = lineupHaloAttrInfo.get(attrPerID, 0) / 10000.0
                fetterPer = fetterAttrDict.get(attrPerID, 0) / 10000.0
                starTalentPer = starTalentAttrDict.get(attrPerID, 0) / 10000.0
                breakLVPer = breakAttrDict.get(attrPerID, 0) / 10000.0
                awakeTalentPer = awakeTalentAttrDict.get(attrPerID, 0) / 10000.0
            # 计算
            attrParamDict = {"lvValue":lvValue, "equipValue":equipValue, "bookValue":bookValue, "bookPer":bookPer, "realmValue":realmValue, "realmPer":realmPer,
                             "gubaoValue":gubaoValue, "gubaoPer":gubaoPer, "hjgValue":hjgValue, "hjgPer":hjgPer,
                             "lineupInitAddPer":lineupInitAddPer, "lineupLVAddPer":lineupLVAddPer, "lineupBreakLVAddPer":lineupBreakLVAddPer, "lineupStarAddPer":lineupStarAddPer,
                             "heroSelfValue":heroSelfValue, "heroSelfPer":heroSelfPer, "inheritPer":inheritPer,
                             "lineupHaloValue":lineupHaloValue, "lineupHaloPer":lineupHaloPer, "fetterValue":fetterValue, "fetterPer":fetterPer,
                             "starTalentValue":starTalentValue, "starTalentPer":starTalentPer, "breakLVValue":breakLVValue, "breakLVPer":breakLVPer,
                             "awakeTalentValue":awakeTalentValue, "awakeTalentPer":awakeTalentPer,
                             }
            if attrID in ChConfig.BaseAttrIDList:
                attrValue = FormulaControl.Eval("baseAttrFormula", baseAttrFormula, attrParamDict, toInt=False, ndigits=3)
            else:
                attrValue = FormulaControl.Eval("otherAttrFormula", otherAttrFormula, attrParamDict, toInt=False, ndigits=3)
            #GameWorld.DebugLog("    attrID=%s,attrValue=%s,attrParamDict=%s" % (attrID, attrValue, attrParamDict))
            attrIpyData = IpyGameDataPY.GetIpyGameData("PlayerAttr", attrID)
            attrName = attrIpyData.GetParameter() if attrIpyData else "%s" % attrID
            attrRatioName = "%sRatio" % attrName
            ratioValue = 0
            if attrValue and hasattr(fpRatioIpyData, "Get%s" % attrRatioName):
                ratioValue = getattr(fpRatioIpyData, "Get%s" % attrRatioName)()
            fightPowerParamDict[attrName] = attrValue
            fightPowerParamDict[attrRatioName] = ratioValue
            if attrValue:
                lineupHero.heroBatAttrDict[attrID] = attrValue
                logAttrDict["%s-%s" % (attrID, attrName)] = attrValue
        # 计算战力
        fightPower = FormulaControl.Eval("fightPowerFormula", fightPowerFormula, fightPowerParamDict, toInt=True)
        GameWorld.DebugLog("    heroID=%s,fightPower=%s,heroSkillIDList=%s" % (heroID, fightPower, lineupHero.heroSkillIDList), playerID)
        skillTypeIDDict = {}
        for skillID in lineupHero.heroSkillIDList:
            skillData = IpyGameDataPY.GetIpyGameData("Skill", skillID)
            if not skillData:
                continue
            skillTypeID = skillData.GetSkillTypeID()
            if skillTypeID not in skillTypeIDDict:
                skillTypeIDDict[skillTypeID] = skillData
            else:
                befSkillData = skillTypeIDDict[skillTypeID]
                befSkillID = befSkillData.GetSkillID()
                if befSkillID >= skillID:
                    continue
                skillTypeIDDict[skillTypeID] = skillData
        skillFightPower = 0
        lineupHero.heroSkillIDList = []
        for skillData in skillTypeIDDict.values():
            skillID = skillData.GetSkillID()
            lineupHero.heroSkillIDList.append(skillID)
            paramDict = {"SkillPower":skillData.GetFightPower(), "PlayerLV":PlayerLV, "OfficialLV":OfficialLV}
            sFightPower = FormulaControl.Eval("skillFPFormula", skillFPFormula, paramDict, toInt=True)
            skillFightPower += sFightPower
        GameWorld.DebugLog("    skillFightPower=%s,heroSkillIDList=%s" % (skillFightPower, lineupHero.heroSkillIDList), playerID)
        # 最终战力
        fightPowerTotal = fightPower + skillFightPower
        lineupHero.skillFightPower = skillFightPower
        lineupHero.fightPower = fightPowerTotal
        lineupFightPower += fightPowerTotal
        GameWorld.DebugLog("    武将最终战力: heroID=%s,fightPower=%s(%s+%s),%s,skillIDList=%s"
                           % (heroID, fightPowerTotal, fightPower, skillFightPower, logAttrDict, lineupHero.heroSkillIDList), playerID)
    lineup.fightPower = lineupFightPower
    GameWorld.DebugLog("    阵容最终战力: lineupID=%s,lineupFightPower=%s" % (lineupID, lineupFightPower), playerID)
    # 非主线阵容不处理以下内容
    if lineupID != ShareDefine.Lineup_Main:
        return
    PlayerControl.SetFightPower(curPlayer, lineupFightPower)
    mainFightMgr = TurnAttack.GetMainFightMgr(curPlayer)
    mainTurnFight = mainFightMgr.turnFight
    # 主线战斗如果有在战斗中,实时更新
    if mainTurnFight and mainTurnFight.isInFight():
        # 如果是阵容变化的,重新开始战斗
        if lineup.lineupChange:
            GameWorld.DebugLog("主阵容变化,重新开始战斗", playerID)
            if mainTurnFight.mapID == ChConfig.Def_FBMapID_Main:
                TurnAttack.__doMainLevelWave(curPlayer, True)
        # 否则只重新设置战斗属性
        else:
            GameWorld.DebugLog("主阵容卡牌属性变更,更新战斗武将属性", playerID)
            # lineup        为卡牌的阵容,仅有阵容属性相关,没有战斗对象
            # batLineup    为卡牌阵容体现到具体战斗的战斗阵容,有具体的战斗对象
            faction, num = ChConfig.Def_FactionA, 1 # 主线战斗玩家自己默认阵营A的第1个战斗阵容
            batLineup = mainTurnFight.getBatFaction(faction).getBatlineup(num)
            batObjMgr = BattleObj.GetBatObjMgr()
            for posNum, objID in batLineup.posObjIDDict.items():
                batObj = batObjMgr.getBatObj(objID)
                if not batObj:
                    continue
                lineupHero = lineup.GetLineupHero(posNum)
                if lineupHero.heroBatAttrDict:
                    batObj.UpdInitBatAttr(lineupHero.heroBatAttrDict, lineupHero.heroSkillIDList)
    else:
        GameWorld.DebugLog("主阵容没有在战斗中,不需要处理", playerID)
    PlayerFamily.RefreshFamilyMember(curPlayer) # 更新公会
    # 更新排行榜
    return