From 5a91f4aa6a702388948d551158a2e92a65329834 Mon Sep 17 00:00:00 2001
From: hxp <ale99527@vip.qq.com>
Date: 星期五, 14 十一月 2025 18:52:41 +0800
Subject: [PATCH] 129 【战斗】战斗系统-服务端(buff持续时间增加大回合支持;增加光环buff;优化攻击、治疗计算逻辑,甄宓、曹仁、董白平摊伤害技能支持;优化B427封包支持额外目标;优化dot、持续治疗结算逻辑及通知;)其他: 1. TurnFight 命令支持指定敌方武将阵容测试,直接在主线战斗 2. B424初始化前的血量变更0418通知屏蔽

---
 ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Skill/PassiveTrigger/PassiveEff_5001.py |    8 
 ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/ChPyNetSendPack.py                      |  145 +++++
 ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Attack/TurnAttack.py                    |  171 ++++++
 ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Skill/TurnSkill.py                      |  900 ++++++++++++++++++++++++++---------
 ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Skill/TurnBuff.py                       |   98 +++
 ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Attack/BattleObj.py                     |   71 ++
 ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/IpyGameDataPY.py                        |   12 
 ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Skill/TurnPassive.py                    |    2 
 ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Skill/TurnBuffs/BuffAtkType_1001.py     |    2 
 PySysDB/PySysDBPY.h                                                                                         |    1 
 ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/GM/Commands/TurnFight.py                |   16 
 ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/ChConfig.py                             |   25 
 ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Skill/TurnBuffs/BuffAtkType_1002.py     |    2 
 13 files changed, 1,138 insertions(+), 315 deletions(-)

diff --git a/PySysDB/PySysDBPY.h b/PySysDB/PySysDBPY.h
index 76208c7..7a1597e 100644
--- a/PySysDB/PySysDBPY.h
+++ b/PySysDB/PySysDBPY.h
@@ -115,6 +115,7 @@
 	list		BuffStateLimit;	//Buff状态限制组
 	BYTE		CurBuffState;	//Buff状态值
 	WORD		LastTime;	//持续时间
+	BYTE		LastTimeType;	//持续时间规则
 	BYTE		LayerCnt;	//Buff层数
 	BYTE		LayerMax;	//最大层数
 	DWORD		BuffRepeat;	//Buff叠加规则
diff --git a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Attack/BattleObj.py b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Attack/BattleObj.py
index 2e25105..32ab297 100644
--- a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Attack/BattleObj.py
+++ b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Attack/BattleObj.py
@@ -239,11 +239,11 @@
         self._objID = 0
         self._hurtTypes = 0 # 本次伤血类型,如闪避、暴击、格挡等,通过二进制或运算得到最终值,支持多种同时出现,如暴击的同时被格挡
         self._hurtHP = 0 # 公式最终计算的伤害值,一般用于飘血
-        self._realHurtHP = 0 # 真实伤血值,被承伤盾抵扣后的可伤血的值
         self._lostHP = 0 # 实际掉血值
         self._curHP = 0 # 更新血量
         self._suckHP = 0 # 吸血量
         self._bounceHP = 0 # 反弹血量
+        self._hurtListEx = [] # 额外伤血列表,一般后端单技能执行多次伤害逻辑时有用,如弹射的平摊伤害 [HurtObj, ...]
         return
     
     def GetObjID(self): return self._objID
@@ -253,14 +253,12 @@
     def AddHurtType(self, hurtType):
         ## 添加伤血类型,单次伤害支持多种类型同时出现
         self._hurtTypes |= pow(2, hurtType)
-        return
+        return self._hurtTypes
     def HaveHurtType(self, hurtType):
         ## 判断是否存在某种伤血类型
         return self._hurtTypes & pow(2, hurtType) 
     def GetHurtHP(self): return self._hurtHP
     def SetHurtHP(self, hurtHP): self._hurtHP = hurtHP
-    def GetRealHurtHP(self): return self._realHurtHP
-    def SetRealHurtHP(self, realHurtHP): self._realHurtHP = realHurtHP
     def GetLostHP(self): return self._lostHP
     def SetLostHP(self, lostHP): self._lostHP = lostHP
     def GetCurHP(self): return self._curHP
@@ -269,6 +267,18 @@
     def SetSuckHP(self, suckHP): self._suckHP = suckHP
     def GetBounceHP(self): return self._bounceHP
     def SetBounceHP(self, bounceHP): self._bounceHP = bounceHP
+    def ClearHurtObjEx(self):
+        poolMgr = ObjPool.GetPoolMgr()
+        for hurtObjEx in self._hurtListEx:
+            poolMgr.release(hurtObjEx)
+        self._hurtListEx = []
+        return
+    def AddHurtObjEx(self, tagID):
+        hurtObj = ObjPool.GetPoolMgr().acquire(HurtObj)
+        hurtObj.SetObjID(tagID)
+        self._hurtListEx.append(hurtObj)
+        return hurtObj
+    def GetHurtObjListEx(self): return self._hurtListEx # 某个伤害的额外伤害列表
     
 class SkillEffect():
     
@@ -342,10 +352,11 @@
     def GetBuffStateLimit(self): return self._ipyData.GetBuffStateLimit()
     def GetCurBuffState(self): return self._ipyData.GetCurBuffState()
     def GetLastTime(self): return self._ipyData.GetLastTime() # 持续时间
+    def GetLastTimeType(self): return self._ipyData.GetLastTimeType() # 持续时间规则
     def GetLayerCnt(self): return self._ipyData.GetLayerCnt()
     def GetLayerMax(self): return self._ipyData.GetLayerMax()
     def GetBuffRepeat(self): return self._ipyData.GetBuffRepeat() # Buff叠加规则
-    def GetDispersedLimit(self): return self._ipyData.GetDispersedLimit() # 驱散限制
+    def GetDispersedLimit(self): return self._ipyData.GetDispersedLimit() or self._ipyData.GetSkillType() == ChConfig.Def_SkillType_Halo # 驱散限制
     def GetFightPower(self): return self._ipyData.GetFightPower()
     
 class PyBuff():
@@ -363,6 +374,7 @@
         self._value3 = 0
         self._isCopy = 0 # 是否复制的buff
         self._effExDict = {} # 效果ID额外数值 {effID:value, ...} # 计算方式取决于本buff技能中属性效果ID的配置
+        self._haloObjIDList = [] # 光环有效目标ID列表 [objID, ...],ownerID为自己时即为光源,包含光源
         return
     
     def onRelease(self):
@@ -404,6 +416,11 @@
     def GetEffectValueEx(self, effID): return self._effExDict.get(effID, 0)
     def ResetEffectValueEx(self): self._effExDict = {}
     def AddEffectValueEx(self, effID, valueEx): self._effExDict[effID] = self._effExDict.get(effID, 0) + valueEx
+    def GetHaloObjIDList(self): return self._haloObjIDList
+    def SetHaloObjIDList(self, haloObjIDList): self._haloObjIDList = haloObjIDList
+    def AddHaloObjID(self, objID):
+        if objID not in self._haloObjIDList:
+            self._haloObjIDList.append(objID)
     
 class BuffManager():
     ## 战斗对象buff管理器
@@ -503,6 +520,16 @@
                 continue
             buffs.append(self._buffIDDict[buffID])
         return buffs
+    def FindBuffBySkillID(self, skillID, ownerID=0):
+        ## 查找某个技能ID的buff
+        # @param ownerID: 可指定查找指定来源的buff
+        buffList = self.FindBuffListBySkillID(skillID)
+        if not ownerID:
+            return buffList[0] if buffList else None
+        for buff in buffList:
+            if buff.GetOwnerID() == ownerID:
+                return buff
+        return
     def FindBuffByState(self, state):
         ## 查找某种buff状态的buff
         buffIDList = self._buffStateDict.get(state, [])
@@ -552,10 +579,12 @@
         self._skillData = ObjPool.GetPoolMgr().acquire(SklllData, ipyData)
         self._remainTime = 0
         self._batType = 0 # 战斗类型,普通、连击、反击、追击等
-        self._tagObjList = [] # 本次技能目标列表 [BatObj, ...]
+        self._tagObjList = [] # 本次技能主要目标列表 [BatObj, ...]
+        self._tagObjListEx = [] # 本次技能额外目标列表,平摊伤害、溅射等 [BatObj, ...]
         self._killObjList = [] # 本次技能击杀目标列表 [BatObj, ...]
         self._effIgnoreObjIDList = [] # 额外技能效果无效的目标ID列表,一般是被闪避、免疫等
-        self._hurtList = [] # 本次伤血列表,可能同一个对象有多个伤害,如弹射等 [HurtObj, ...]
+        self._hurtList = [] # 主要伤血列表,可能同一个对象有多个伤害,如弹射等 [HurtObj, ...]
+        self._hurtListEx = [] # 额外伤血列表,如平摊、溅射 [HurtObj, ...]
         self._bySkill = None # 由哪个技能触发的
         self._byBuff = None # 由哪个buff触发的
         self._afterLogicList = [] # 技能释放后需要处理逻辑 [[logicType, logicParams], ...]
@@ -578,6 +607,7 @@
         ## 注:有用到对象池相关对象的一定要重置,不然再回收技能对象时会连同该技能下的所有用到的对象池对象一并回收,导致后续使用对象错误
         self._batType = 0
         self._tagObjList = []
+        self._tagObjListEx = []
         self._killObjList = []
         self._effIgnoreObjIDList = []
         self._bySkill = None
@@ -616,6 +646,7 @@
     def GetBuffStateLimit(self): return self._skillData.GetBuffStateLimit()
     def GetCurBuffState(self): return self._skillData.GetCurBuffState()
     def GetLastTime(self): return self._skillData.GetLastTime() # 持续时间
+    def GetLastTimeType(self): return self._skillData.GetLastTimeType() # 持续时间规则
     def GetLayerCnt(self): return self._skillData.GetLayerCnt()
     def GetLayerMax(self): return self._skillData.GetLayerMax()
     def GetBuffRepeat(self): return self._skillData.GetBuffRepeat() # Buff叠加规则
@@ -631,8 +662,12 @@
     def SetBySkill(self, bySkill): self._bySkill = bySkill
     def GetByBuff(self): return self._byBuff
     def SetByBuff(self, byBuff): self._byBuff = byBuff
-    def GetTagObjList(self): return self._tagObjList # 技能目标列表
+    def GetTagObjList(self): return self._tagObjList # 技能主要目标列表
     def SetTagObjList(self, tagObjList): self._tagObjList = tagObjList
+    def GetTagObjListEx(self): return self._tagObjListEx # 技能额外目标列表
+    def AddTagObjEx(self, tagObj):
+        if tagObj not in self._tagObjList and tagObj not in self._tagObjListEx:
+            self._tagObjListEx.append(tagObj)
     def GetKillObjList(self): return self._killObjList # 击杀目标列表
     def SetKillObjList(self, killObjList): self._killObjList = killObjList
     def GetEffIgnoreObjIDList(self): return self._effIgnoreObjIDList # 额外技能效果无效的目标ID列表
@@ -649,16 +684,28 @@
         ## 清空伤血统计
         poolMgr = ObjPool.GetPoolMgr()
         for hurtObj in self._hurtList:
+            hurtObj.ClearHurtObjEx()
             poolMgr.release(hurtObj)
         self._hurtList = []
+        
+        for hurtObjEx in self._hurtListEx:
+            hurtObjEx.ClearHurtObjEx()
+            poolMgr.release(hurtObjEx)
+        self._hurtListEx = []
         return
-    def AddHurtObj(self, tagID):
+    def AddHurtObj(self, tagID, isEx=False):
         ## 添加某个伤血
+        # @param isEx: 是否额外伤血
         hurtObj = ObjPool.GetPoolMgr().acquire(HurtObj)
         hurtObj.SetObjID(tagID)
-        self._hurtList.append(hurtObj)
+        if isEx:
+            self._hurtListEx.append(hurtObj)
+        else:
+            self._hurtList.append(hurtObj)
         return hurtObj
-    def GetHurtObjList(self): return self._hurtList
+    def GetHurtObjList(self): return self._hurtList # 技能主要伤血列表
+    def GetHurtObjListEx(self): return self._hurtListEx # 技能额外伤血列表
+    def GetHurtObjListAll(self): return self._hurtList + self._hurtListEx # 技能所有伤血列表
     
     def __commboClear(self):
         ## 连击相关清空
@@ -817,7 +864,7 @@
         self._skillTempAttrDict = {}
         self.SetXP(initXP, False)
         self.SetHPFull(False)
-        TurnBuff.RefreshBuffAttr(self)
+        TurnBuff.RefreshBuffAttr(self, isInit=True)
         TurnPassive.RefreshPassive(self)
         return
     
diff --git a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Attack/TurnAttack.py b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Attack/TurnAttack.py
index 408a0bd..3e741e5 100644
--- a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Attack/TurnAttack.py
+++ b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Attack/TurnAttack.py
@@ -48,6 +48,8 @@
 import time
 import json
 
+g_gmTestFightReq = []
+
 PosNumMax = 7 # 最大站位编号
 ActionNumStart = -1 # 起始行动位置编号,一般是从1开始,如果有加主公、红颜等则扣除相应位置值,如从0或-1开始
 
@@ -73,6 +75,7 @@
         self.shapeType = 0 # 阵型
         self.fightPower = 0 # 阵容总战力
         self.posObjIDDict = {} # 站位对应战斗实体 {站位编号:batObjID, ...}, 站位编号小于0为非主战单位,如主公、红颜等
+        self.heroObjIDDict = {} # 武将ID对应ObjID {heroID:batObjID, ...}
         self.lingshouObjIDDict = {} # 灵兽战斗单位 {位置编号:batObjID, ...}
         self.beautyObjIDDict = {} # 红颜战斗单位 {位置编号:batObjID, ...}
         self.actionNum = ActionNumStart # 行动位置,从1开始
@@ -109,6 +112,7 @@
         for objID in self.beautyObjIDDict.values():
             batObjMgr.delBatObj(objID)
         self.posObjIDDict = {}
+        self.heroObjIDDict = {}
         self.lingshouObjIDDict = {}
         self.beautyObjIDDict = {}
         self.fightPower = 0
@@ -621,7 +625,10 @@
     # @param lineupID: 阵容ID
     # @param npcLV: 成长NPC等级
     # @param difficulty: 成长NPC难度系数
-    # @return: 阵容全部信息json字典,前端通用格式
+    # @return: 阵容全部信息json字典,前端通用格式    
+    lineupInfo = GetGMTestNPCLineupInfo(lineupID, strongerLV, difficulty)
+    if lineupInfo:
+        return lineupInfo
     ipyData = IpyGameDataPY.GetIpyGameData("NPCLineup", lineupID)
     if not ipyData:
         return {}
@@ -643,6 +650,93 @@
     lineupInfo = {"NPCLineupID":lineupID, "Hero":heroDict, "BossID":bossID, "BossPosView":bossPosView}
     return lineupInfo
 
+def GMTestFight(curPlayer, heroIDList, isAllSkill):
+    ## GM测试战斗,指定武将
+    global g_gmTestFightReq
+    g_gmTestFightReq = [heroIDList, isAllSkill]
+    __doMainLevelWave(curPlayer, True)
+    g_gmTestFightReq = []
+    return
+
+def GetGMTestNPCLineupInfo(lineupID, strongerLV=0, difficulty=0):
+    ## 获取GM测试战斗阵容信息
+    if not g_gmTestFightReq:
+        return
+    heroIDList, isAllSkill = g_gmTestFightReq
+    
+    lineupIpyData = IpyGameDataPY.GetIpyGameData("NPCLineup", lineupID)
+    if not lineupIpyData:
+        return
+    
+    npcDict = {}
+    heroNPCIDDict = {}
+    for posNum in range(1, 1 + 6):
+        if not hasattr(lineupIpyData, "GetPosNPCID%s" % posNum):
+            break
+        npcID = getattr(lineupIpyData, "GetPosNPCID%s" % posNum)()
+        if not npcID:
+            continue
+        battleDict = GetNPCBattleDict(lineupIpyData, npcID, strongerLV, difficulty)
+        if not battleDict:
+            continue
+        npcDict[npcID] = battleDict
+        heroID = battleDict["HeroID"]
+        heroNPCIDDict[heroID] = npcID
+        
+    if not npcDict:
+        return
+    
+    heroDict = {}
+    # 原先阵容刚好有的直接用
+    for posNum, heroID in enumerate(heroIDList, 1):
+        if heroID not in heroNPCIDDict:
+            continue
+        npcID = heroNPCIDDict[heroID]
+        heroDict[str(posNum)] = npcDict[npcID]
+        heroIDList[posNum - 1] = 0
+        
+    lineupNPCID = npcDict.keys()[0]
+    ipyNPCIDList = []
+    ipyHeroIDList = []
+    ipyDataMgr = IpyGameDataPY.IPY_Data()
+    for index in xrange(ipyDataMgr.GetNPCCount()):
+        ipyData = ipyDataMgr.GetNPCByIndex(index)
+        heroID = ipyData.GetRelatedHeroID()
+        npcID = ipyData.GetNPCID()
+        ipyNPCIDList.append(npcID)
+        ipyHeroIDList.append(heroID)
+        
+    lineupNPCIndex = ipyNPCIDList.index(lineupNPCID)
+    # 从参考阵容先往前检索,再往后检索,还没有对应武将的NPC
+    loopIndexList = range(lineupNPCIndex + 1)[::-1] + range(lineupNPCIndex + 1, ipyDataMgr.GetNPCCount())
+    for index in loopIndexList:
+        npcID = ipyNPCIDList[index]
+        heroID = ipyHeroIDList[index]
+        if heroID not in heroIDList:
+            continue
+        battleDict = GetNPCBattleDict(lineupIpyData, npcID, strongerLV, difficulty)
+        if not battleDict:
+            continue
+        
+        for posNum, posHeroID in enumerate(heroIDList, 1):
+            if not posHeroID or heroID != posHeroID:
+                continue
+            heroIDList[posNum - 1] = 0
+            heroDict[str(posNum)] = battleDict
+        
+        if heroIDList.count(0) == len(heroIDList):
+            break
+        
+    if isAllSkill:
+        for battleDict in heroDict.values():
+            heroID = battleDict["HeroID"]
+            heroIpyData = IpyGameDataPY.GetIpyGameData("Hero", heroID) if heroID else None
+            skillIDList = GetNPCHeroSkillIDList(heroID, heroIpyData, 99999, 99999) # 默认取突破、觉醒都满级时的技能
+            battleDict["SkillIDList"] = skillIDList
+            
+    lineupInfo = {"NPCLineupID":lineupID, "Hero":heroDict, "BossID":0, "BossPosView":0}
+    return lineupInfo
+    
 def GetNPCBattleDict(lineupIpyData, npcID, strongerLV=0, difficulty=0):
     ## 获取NPC战斗相关字典,支持成长NPC
     # @param strongerLV: 成长等级
@@ -771,7 +865,9 @@
     @param lineupInfo: 阵容信息
     @param playerID: 发起的玩家ID,系统场次为0
     '''
-    GameWorld.DebugLog("SummonLineupObjs faction:%s,num:%s,lineupInfo=%s" % (faction, num, lineupInfo), playerID)
+    lineupPlayerID = lineupInfo.get("PlayerID", 0) # 阵容所属玩家ID
+    npcLineupID = lineupInfo.get("NPCLineupID", 0)
+    GameWorld.DebugLog("SummonLineupObjs faction:%s,num:%s,npcLineupID=%s,lineupPlayerID=%s" % (faction, num, npcLineupID, lineupPlayerID), playerID)
     if playerID:
         curPlayer = GameWorld.GetPlayerManager().FindPlayerByID(playerID)
         if not curPlayer:
@@ -779,7 +875,6 @@
         
     turnFight = batLineup.turnFight
     tfGUID = turnFight.guid
-    lineupPlayerID = lineupInfo.get("PlayerID", 0) # 阵容所属玩家ID
     heroDict = lineupInfo.get("Hero", {})
     
     batObjMgr = BattleObj.GetBatObjMgr()
@@ -845,6 +940,7 @@
             skillManager.LearnSkillByID(skillID)
             
         batLineup.posObjIDDict[posNum] = objID
+        batLineup.heroObjIDDict[heroID] = objID
         GameWorld.DebugLog("AddBatObj %s,skill=%s" % (GetObjName(batObj), skillManager.GetSkillIDList()))
         ResetObjSkill(batObj)
         
@@ -1462,7 +1558,7 @@
     
     batObjMgr = BattleObj.GetBatObjMgr()
     for faction, num in turnFight.actionSortList:
-        GameWorld.DebugLog("大回合开始逻辑: turnNum=%s,faction=%s, num=%s" % (turnNum, faction, num))
+        GameWorld.DebugLog("大回合开始逻辑: turnNum=%s,faction=%s,num=%s" % (turnNum, faction, num))
         batFaction = turnFight.getBatFaction(faction)
         batLineup = batFaction.getBatlineup(num)
         batLineup.actionNum = 1
@@ -1476,7 +1572,8 @@
             turnFight.ResetOneActionUseSkillCnt()
             batObj.SetTiming(ChConfig.TurnTiming_Before) # 重置时机到回合前
             if turnNum > 1: # 第1回合不用刷新技能
-                RefreshObjSkillByTurn(batObj)
+                RefreshObjSkillByBigTurn(batObj)
+                RefreshObjByBigTurn(turnFight, batObj)
                 
             TurnPassive.OnTriggerPassiveEffect(turnFight, batObj, ChConfig.TriggerWay_BigTurnStart)
             
@@ -1552,7 +1649,7 @@
         
     return
 
-def RefreshObjSkillByTurn(batObj):
+def RefreshObjSkillByBigTurn(batObj):
     '''按回合刷新技能:默认以大回合统一减1回合
     '''
     curID = batObj.GetID()
@@ -1576,6 +1673,31 @@
     batObj.ResetSkillTurnUseCnt() # 重置回合使用次数,放刷新CD后重置
     return
 
+def RefreshObjByBigTurn(turnFight, batObj):
+    ## 根据大回合开始刷新buff持续时间,每个大回合-1,第1回合不处理
+    curID = batObj.GetID()
+    buffMgr = batObj.GetBuffManager()
+    for index in range(buffMgr.GetBuffCount())[::-1]:
+        buff = buffMgr.GetBuffByIndex(index)
+        buffID = buff.GetBuffID()
+        skillID = buff.GetSkillID()
+        skillData = buff.GetSkillData()
+        if skillData.GetLastTimeType() != ChConfig.BuffLastTimeType_BigTurn:
+            continue
+        if skillData.GetSkillType() in ChConfig.Def_LstBuff_List:
+            #GameWorld.DebugLog("    持续类buff由触发时机决定剩余时间! curID=%s,index=%s,skillID=%s,buffID=%s" % (curID, index, skillID, buffID))
+            continue
+        if skillData.GetSkillType() == ChConfig.Def_SkillType_Halo and buff.GetOwnerID() != curID:
+            GameWorld.DebugLog("    光环buff非光源不处理! curID=%s,index=%s,skillID=%s,buffID=%s" % (curID, index, skillID, buffID))
+            continue
+        remainTime = buff.GetRemainTime()
+        if remainTime <= 0:
+            continue
+        remainTime -= 1
+        GameWorld.DebugLog("    更新buff回合: curID=%s,buffID=%s,skillID=%s,remainTime=%s" % (curID, buffID, skillID, remainTime))
+        TurnBuff.SetBuffRemainTime(turnFight, batObj, buff, remainTime)
+    return
+
 def RefreshObjBuffTime(turnFight, batObj):
     '''刷新buff持续时间:以武将自身回合前、回合后处理buff持续时间
     回合前添加的buff在回合开始减1,回合后添加的buff在回合结束减1
@@ -1588,8 +1710,13 @@
         buffID = buff.GetBuffID()
         skillID = buff.GetSkillID()
         skillData = buff.GetSkillData()
+        if skillData.GetLastTimeType() != ChConfig.BuffLastTimeType_Default:
+            continue
         if skillData.GetSkillType() in ChConfig.Def_LstBuff_List:
             #GameWorld.DebugLog("    持续类buff由触发时机决定剩余时间! curID=%s,index=%s,skillID=%s,buffID=%s" % (curID, index, skillID, buffID))
+            continue
+        if skillData.GetSkillType() == ChConfig.Def_SkillType_Halo and buff.GetOwnerID() != curID:
+            GameWorld.DebugLog("    光环buff非光源不处理! curID=%s,index=%s,skillID=%s,buffID=%s" % (curID, index, skillID, buffID))
             continue
         remainTime = buff.GetRemainTime()
         if remainTime <= 0:
@@ -1606,13 +1733,9 @@
             continue
         remainTime -= 1
         GameWorld.DebugLog("    更新buff回合: curID=%s,buffID=%s,skillID=%s,remainTime=%s,addTiming=%s" % (curID, buffID, skillID, remainTime, addTiming))
-        if remainTime > 0:
-            buff.SetRemainTime(remainTime)
-            TurnBuff.SyncBuffRefresh(turnFight, batObj, buff)
-        else:
-            TurnBuff.DoBuffDel(turnFight, batObj, buff)
+        TurnBuff.SetBuffRemainTime(turnFight, batObj, buff, remainTime)
     return
-                        
+
 def AddTurnObjCureHP(curObj, srcObj, addValue, cureHP, skillID=0):
     ## 回合对象添加治疗值
     # @param curObj: 获得治疗的对象
@@ -1629,22 +1752,21 @@
         
     return
 
-def AddTurnObjHurtValue(curBatObj, tagBatObj, hurtValue, lostHP, skillID=0, isBounce=False):
+def AddTurnObjHurtValue(curBatObj, tagBatObj, hurtValue, lostHP, skillID=0, lostType=""):
     ## 回合对象添加伤害值
-    # @param isBounce: 是否反弹伤害
     if hurtValue <= 0:
         return
     curID = curBatObj.GetID()
     tagID = tagBatObj.GetID()
     if curID != tagID:
         updStatValue = curBatObj.StatHurtValue(hurtValue)
-        GameWorld.DebugLog("        统计伤血: curID=%s,tagID=%s,skillID=%s,hurtValue=%s,lostHP=%s,updStatValue=%s,tagHP=%s,isBounce=%s" 
-                       % (curID, tagID, skillID, hurtValue, lostHP, updStatValue, tagBatObj.GetHP(), isBounce))
+        GameWorld.DebugLog("        统计输出: curID=%s,tagID=%s,skillID=%s,hurtValue=%s,lostHP=%s,updStatValue=%s,tagHP=%s,lostType=%s" 
+                       % (curID, tagID, skillID, hurtValue, lostHP, updStatValue, tagBatObj.GetHP(), lostType))
         
         if tagBatObj:
             updStatValue = tagBatObj.StatDefValue(hurtValue)
-            GameWorld.DebugLog("        统计承伤: curID=%s,tagID=%s,skillID=%s,hurtValue=%s,lostHP=%s,updStatValue=%s,curHP=%s,isBounce=%s" 
-                           % (tagID, curID, skillID, hurtValue, lostHP, updStatValue, tagBatObj.GetHP(), isBounce))
+            GameWorld.DebugLog("        统计承伤: curID=%s,tagID=%s,skillID=%s,hurtValue=%s,lostHP=%s,updStatValue=%s,curHP=%s,lostType=%s" 
+                           % (tagID, curID, skillID, hurtValue, lostHP, updStatValue, tagBatObj.GetHP(), lostType))
             
     else:
         # 如换血类技能,自残的伤害不算输出
@@ -1748,6 +1870,19 @@
     # 暂时只算主线小怪
     if curPlayer and turnFight.mapID == ChConfig.Def_FBMapID_Main and gameObj.GetFaction() != ChConfig.Def_FactionA:
         GetMainFightMgr(curPlayer).killNPCCnt += 1
+        
+    # 清除光源buff
+    buffMgr = gameObj.GetBuffManager()
+    for index in range(buffMgr.GetBuffCount())[::-1]:
+        buff = buffMgr.GetBuffByIndex(index)
+        skillID = buff.GetSkillID()
+        skillData = buff.GetSkillData()
+        if skillData.GetSkillType() != ChConfig.Def_SkillType_Halo:
+            continue
+        if buff.GetOwnerID() != objID:
+            continue
+        GameWorld.DebugLog("删除光环buff: objID=%s,skillID=%s" % (objID, skillID))
+        TurnBuff.DoBuffDel(turnFight, gameObj, buff)
     return True
 
 def OnTurnAllOver(guid):
diff --git a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/ChConfig.py b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/ChConfig.py
index a6ca73e..65cf0f8 100644
--- a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/ChConfig.py
+++ b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/ChConfig.py
@@ -915,7 +915,7 @@
 (
 HurtType_Fail,              # 失败 - 如概率没有触发 0
 HurtType_Normal,            # 伤害 1
-HurtTYpe_Recovery,          # 回血 2
+HurtTYpe_Cure,              # 治疗 2
 HurtType_3,
 HurtType_Immune,            # 免疫 4
 HurtType_Parry,             # 格挡 5
@@ -926,7 +926,8 @@
 HurtType_PoisonCureHurt,    # 伤害毒奶 10
 HurtType_PoisonCureSuck,    # 吸血毒奶 11
 HurtType_HarmSelf,          # 自残 12
-) = range(13)
+HurtType_CaorenProtect,     # 本次伤害有受曹仁防护标记 13
+) = range(14)
 
 #伤害类型
 (
@@ -1448,7 +1449,7 @@
    Def_SkillType_Passive      ,  #被动技能(与被动BUFF无直接关系)   7
    Def_SkillType_Revive       ,  #复活     8
    Def_SkillType_Increment    ,  #增值技能(不可清除)9  
-   Def_SkillType_Aura         ,  #光环技能  10
+   Def_SkillType_Halo         ,  #光环技能  10
    Def_SkillType_Equip        ,  #装备技能  11
    Def_SkillType_Area         ,  #场景技能(buff)  12
    Def_SkillType_Summon       ,  #召唤  13
@@ -1457,6 +1458,7 @@
 ) = range(0, 1 + 15)
 
 # 以下废弃
+Def_SkillType_Aura = 10 #光环技能,旧命名  10
 (
    Def_SkillType_LstPlsBuffAtk,  #持续攻击类BUFF 15
    Def_SkillType_PassivePlsBuff,  #被动触发增益类buff 16
@@ -1532,6 +1534,10 @@
 Def_BuffLayer_Add = 0   # 每次触发层级递增
 Def_BuffLayer_Sub = 1   # 每次触发层级递减
 
+# Buff时间计算规则
+#【注】光环类buff默认与光源(施法者)同步,其他受光环影响的目标同步该buff持续时间,持续时间及效果由施法者决定
+BuffLastTimeType_Default = 0 # 默认以获得buff时自身回合前后判断
+BuffLastTimeType_BigTurn = 1 # 大回合buff,每大回合开始固定减1回合
 
 #动作类区分标识
 (
@@ -2865,7 +2871,9 @@
 TurnBattleType_Pursue, # 追击 3
 TurnBattleType_Enhance, # 额外技能(一般是主技能拆分为多个效果的技能) 4
 TurnBattleType_Passive, # 被动触发的技能 5
-) = range(6)
+TurnBattleType_Dot, # 持续伤害触发 6
+TurnBattleType_Cot, # 持续治疗触发 7
+) = range(8)
 
 Def_PerTurnTick = 1000 # 每回合等同于常规tick时长
 
@@ -2971,7 +2979,9 @@
     BatObjState_RebornLimit, # 无法复活 25
     BatObjState_26, # 脆弱 26
     BatObjState_27, # 阴咒 27
-) = range(1 + 27)
+    BatObjState_Zhiming, # 织命(甄宓) 28
+    BatObjState_Link, # 链接(董白) 29
+) = range(1 + 29)
 
 #玩家状态定义,不能超过31个,如超过,需扩展多个key支持
 Def_PlayerStateList = (
@@ -4698,6 +4708,11 @@
 HeroSpecialty_SuckHP,       # 吸血 6
 ) = range(1, 1 + 6)
 
+# 部分武将ID
+HeroID_Zhenfu = 510013
+HeroID_Caoren = 510015
+HeroID_Dongbai = 540009
+
 # 经验倍率限制类型
 (
 ExpRateLimitType_Recover, # 资源找回
diff --git a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/ChPyNetSendPack.py b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/ChPyNetSendPack.py
index c2c0878..7f140e2 100644
--- a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/ChPyNetSendPack.py
+++ b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/ChPyNetSendPack.py
@@ -40034,17 +40034,16 @@
 #------------------------------------------------------
 # B4 27 使用技能 #tagSCUseSkill
 
-class  tagSCUseSkillHurt(Structure):
+class  tagSCUseSkillHurtEx(Structure):
     _pack_ = 1
     _fields_ = [
-                  ("ObjID", c_int),    
-                  ("AttackTypes", c_int),    # 飘血类型汇总,支持多种类型并存,如无视防御且暴击同时被格挡,二进制或运算最终值;0-失败;1-普通;2-回血;5-格挡;6-无视防御;7-暴击;9-闪避
+                  ("ObjID", c_int),    # 额外目标,如溅射伤害、平摊伤害目标,非技能自身的主要目标
+                  ("AttackTypes", c_int),    # 飘血类型汇总,支持多种类型并存,如无视防御且暴击同时被格挡,二进制或运算最终值;0-失败;1-普通;2-回血;4-免疫;5-格挡;6-无视防御;7-暴击;8-击晕;9-闪避
                   ("HurtHP", c_int),    # 飘血值,求余亿部分
                   ("HurtHPEx", c_int),    # 飘血值,整除亿部分
                   ("CurHP", c_int),    # 更新剩余血量,求余亿部分
                   ("CurHPEx", c_int),    # 更新剩余血量,整除亿部分
                   ("SuckHP", c_int),    # 本次伤害转化的吸血量
-                  ("BounceHP", c_int),    # 本次伤害反弹的伤害量
                   ]
 
     def __init__(self):
@@ -40064,11 +40063,10 @@
         self.CurHP = 0
         self.CurHPEx = 0
         self.SuckHP = 0
-        self.BounceHP = 0
         return
 
     def GetLength(self):
-        return sizeof(tagSCUseSkillHurt)
+        return sizeof(tagSCUseSkillHurtEx)
 
     def GetBuffer(self):
         return string_at(addressof(self), self.GetLength())
@@ -40081,8 +40079,110 @@
                                 HurtHPEx:%d,
                                 CurHP:%d,
                                 CurHPEx:%d,
+                                SuckHP:%d
+                                '''\
+                                %(
+                                self.ObjID,
+                                self.AttackTypes,
+                                self.HurtHP,
+                                self.HurtHPEx,
+                                self.CurHP,
+                                self.CurHPEx,
+                                self.SuckHP
+                                )
+        return DumpString
+
+
+class  tagSCUseSkillHurt(Structure):
+    ObjID = 0    #(DWORD ObjID)// 技能自身目标ID
+    AttackTypes = 0    #(DWORD AttackTypes)// 飘血类型汇总,支持多种类型并存,如无视防御且暴击同时被格挡,二进制或运算最终值;0-失败;1-普通;2-回血;4-免疫;5-格挡;6-无视防御;7-暴击;8-击晕;9-闪避
+    HurtHP = 0    #(DWORD HurtHP)// 飘血值,求余亿部分
+    HurtHPEx = 0    #(DWORD HurtHPEx)// 飘血值,整除亿部分
+    CurHP = 0    #(DWORD CurHP)// 更新剩余血量,求余亿部分
+    CurHPEx = 0    #(DWORD CurHPEx)// 更新剩余血量,整除亿部分
+    SuckHP = 0    #(DWORD SuckHP)// 本次伤害转化的吸血量
+    BounceHP = 0    #(DWORD BounceHP)// 本次伤害反弹的伤害量
+    HurtCountEx = 0    #(BYTE HurtCountEx)
+    HurtListEx = list()    #(vector<tagSCUseSkillHurtEx> HurtListEx)// 额外伤害目标列表,仅后端多次伤害的有效,如弹射的平摊伤害
+    data = None
+
+    def __init__(self):
+        self.Clear()
+        return
+
+    def ReadData(self, _lpData, _pos=0, _Len=0):
+        self.Clear()
+        self.ObjID,_pos = CommFunc.ReadDWORD(_lpData, _pos)
+        self.AttackTypes,_pos = CommFunc.ReadDWORD(_lpData, _pos)
+        self.HurtHP,_pos = CommFunc.ReadDWORD(_lpData, _pos)
+        self.HurtHPEx,_pos = CommFunc.ReadDWORD(_lpData, _pos)
+        self.CurHP,_pos = CommFunc.ReadDWORD(_lpData, _pos)
+        self.CurHPEx,_pos = CommFunc.ReadDWORD(_lpData, _pos)
+        self.SuckHP,_pos = CommFunc.ReadDWORD(_lpData, _pos)
+        self.BounceHP,_pos = CommFunc.ReadDWORD(_lpData, _pos)
+        self.HurtCountEx,_pos = CommFunc.ReadBYTE(_lpData, _pos)
+        for i in range(self.HurtCountEx):
+            temHurtListEx = tagSCUseSkillHurtEx()
+            _pos = temHurtListEx.ReadData(_lpData, _pos)
+            self.HurtListEx.append(temHurtListEx)
+        return _pos
+
+    def Clear(self):
+        self.ObjID = 0
+        self.AttackTypes = 0
+        self.HurtHP = 0
+        self.HurtHPEx = 0
+        self.CurHP = 0
+        self.CurHPEx = 0
+        self.SuckHP = 0
+        self.BounceHP = 0
+        self.HurtCountEx = 0
+        self.HurtListEx = list()
+        return
+
+    def GetLength(self):
+        length = 0
+        length += 4
+        length += 4
+        length += 4
+        length += 4
+        length += 4
+        length += 4
+        length += 4
+        length += 4
+        length += 1
+        for i in range(self.HurtCountEx):
+            length += self.HurtListEx[i].GetLength()
+
+        return length
+
+    def GetBuffer(self):
+        data = ''
+        data = CommFunc.WriteDWORD(data, self.ObjID)
+        data = CommFunc.WriteDWORD(data, self.AttackTypes)
+        data = CommFunc.WriteDWORD(data, self.HurtHP)
+        data = CommFunc.WriteDWORD(data, self.HurtHPEx)
+        data = CommFunc.WriteDWORD(data, self.CurHP)
+        data = CommFunc.WriteDWORD(data, self.CurHPEx)
+        data = CommFunc.WriteDWORD(data, self.SuckHP)
+        data = CommFunc.WriteDWORD(data, self.BounceHP)
+        data = CommFunc.WriteBYTE(data, self.HurtCountEx)
+        for i in range(self.HurtCountEx):
+            data = CommFunc.WriteString(data, self.HurtListEx[i].GetLength(), self.HurtListEx[i].GetBuffer())
+        return data
+
+    def OutputString(self):
+        DumpString = '''
+                                ObjID:%d,
+                                AttackTypes:%d,
+                                HurtHP:%d,
+                                HurtHPEx:%d,
+                                CurHP:%d,
+                                CurHPEx:%d,
                                 SuckHP:%d,
-                                BounceHP:%d
+                                BounceHP:%d,
+                                HurtCountEx:%d,
+                                HurtListEx:%s
                                 '''\
                                 %(
                                 self.ObjID,
@@ -40092,7 +40192,9 @@
                                 self.CurHP,
                                 self.CurHPEx,
                                 self.SuckHP,
-                                self.BounceHP
+                                self.BounceHP,
+                                self.HurtCountEx,
+                                "..."
                                 )
         return DumpString
 
@@ -40101,13 +40203,15 @@
     Head = tagHead()
     ObjID = 0    #(DWORD ObjID)
     PMType = 0    #(BYTE PMType)// 物法类型 0或1-物理;2-法术
-    BattleType = 0    #(BYTE BattleType)// 战斗类型 0-常规;1-连击;2-反击;3-追击;4-子技能;5-被动触发的
+    BattleType = 0    #(BYTE BattleType)// 战斗类型 0-常规;1-连击;2-反击;3-追击;4-子技能;5-被动触发的;6-dot结算;7-持续治疗结算
     CurHP = 0    #(DWORD CurHP)// 释放技能后剩余血量,吸血、反弹可能引起变化,求余亿部分
     CurHPEx = 0    #(DWORD CurHPEx)// 释放技能后剩余血量,吸血、反弹可能引起变化,整除亿部分
     SkillID = 0    #(DWORD SkillID)
     RelatedSkillID = 0    #(DWORD RelatedSkillID)// 关联的技能ID,一般是主技能ID或由于某个技能释放引起的
-    HurtCount = 0    #(BYTE HurtCount)//伤害数目
-    HurtList = list()    #(vector<tagSCUseSkillHurt> HurtList)//size = HurtCount
+    HurtCount = 0    #(BYTE HurtCount)
+    HurtList = list()    #(vector<tagSCUseSkillHurt> HurtList)// 主要伤害目标列表
+    HurtCountEx = 0    #(BYTE HurtCountEx)
+    HurtListEx = list()    #(vector<tagSCUseSkillHurtEx> HurtListEx)// 额外伤害目标列表,仅后端单次伤害的有效,如平摊或溅射伤害
     data = None
 
     def __init__(self):
@@ -40131,6 +40235,11 @@
             temHurtList = tagSCUseSkillHurt()
             _pos = temHurtList.ReadData(_lpData, _pos)
             self.HurtList.append(temHurtList)
+        self.HurtCountEx,_pos = CommFunc.ReadBYTE(_lpData, _pos)
+        for i in range(self.HurtCountEx):
+            temHurtListEx = tagSCUseSkillHurtEx()
+            _pos = temHurtListEx.ReadData(_lpData, _pos)
+            self.HurtListEx.append(temHurtListEx)
         return _pos
 
     def Clear(self):
@@ -40147,6 +40256,8 @@
         self.RelatedSkillID = 0
         self.HurtCount = 0
         self.HurtList = list()
+        self.HurtCountEx = 0
+        self.HurtListEx = list()
         return
 
     def GetLength(self):
@@ -40162,6 +40273,9 @@
         length += 1
         for i in range(self.HurtCount):
             length += self.HurtList[i].GetLength()
+        length += 1
+        for i in range(self.HurtCountEx):
+            length += self.HurtListEx[i].GetLength()
 
         return length
 
@@ -40178,6 +40292,9 @@
         data = CommFunc.WriteBYTE(data, self.HurtCount)
         for i in range(self.HurtCount):
             data = CommFunc.WriteString(data, self.HurtList[i].GetLength(), self.HurtList[i].GetBuffer())
+        data = CommFunc.WriteBYTE(data, self.HurtCountEx)
+        for i in range(self.HurtCountEx):
+            data = CommFunc.WriteString(data, self.HurtListEx[i].GetLength(), self.HurtListEx[i].GetBuffer())
         return data
 
     def OutputString(self):
@@ -40191,7 +40308,9 @@
                                 SkillID:%d,
                                 RelatedSkillID:%d,
                                 HurtCount:%d,
-                                HurtList:%s
+                                HurtList:%s,
+                                HurtCountEx:%d,
+                                HurtListEx:%s
                                 '''\
                                 %(
                                 self.Head.OutputString(),
@@ -40203,6 +40322,8 @@
                                 self.SkillID,
                                 self.RelatedSkillID,
                                 self.HurtCount,
+                                "...",
+                                self.HurtCountEx,
                                 "..."
                                 )
         return DumpString
diff --git a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/GM/Commands/TurnFight.py b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/GM/Commands/TurnFight.py
index f89ad1e..9458607 100644
--- a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/GM/Commands/TurnFight.py
+++ b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/GM/Commands/TurnFight.py
@@ -41,6 +41,7 @@
         GameWorld.DebugAnswer(curPlayer, "阵营: 1-左边;2-右边")
         GameWorld.DebugAnswer(curPlayer, "位置: 1~6号位")
         GameWorld.DebugAnswer(curPlayer, "属性ID: 6-攻,7-防,8-HPMax,9-HP,12-怒")
+        GameWorld.DebugAnswer(curPlayer, "测试战斗: TurnFight f 是否满技能 [武将ID ...]")
         return
     
     value = msgList[0]
@@ -52,6 +53,8 @@
         __doAddBuff(curPlayer, msgList)
     elif value == "p":
         __printInfo(curPlayer, msgList)
+    elif value == "f":
+        __doFightTest(curPlayer, msgList)
     elif value > 0 and value != ChConfig.Def_FBMapID_Main:
         __reqTurnFight(curPlayer, msgList)
     return
@@ -230,7 +233,8 @@
             batObj = batObjMgr.getBatObj(objID)
             objName = TurnAttack.GetObjName(batObj)
             GameWorld.DebugAnswer(curPlayer, "--- %s%s" % (objName, "" if batObj.IsAlive() else " [被击杀]"))
-            GameWorld.DebugAnswer(curPlayer, "HP:%s/%s, Atk:%s, Def:%s" % (batObj.GetHP(), batObj.GetMaxHP(), batObj.GetAtk(), batObj.GetDef()))
+            GameWorld.DebugAnswer(curPlayer, "HP:%s/%s" % (batObj.GetHP(), batObj.GetMaxHP()))
+            GameWorld.DebugAnswer(curPlayer, "攻:%s,防:%s,怒:%s" % (batObj.GetAtk(), batObj.GetDef(), batObj.GetXP()))
             attrDict = batObj.GetBatAttrDict()
             GameWorld.DebugAnswer(curPlayer, "属性:%s" % attrDict)
             skillMgr = batObj.GetSkillManager()
@@ -240,11 +244,17 @@
             GameWorld.DebugAnswer(curPlayer, "Buff: %s" % buffMgr.GetBuffCount())
             for index in range(buffMgr.GetBuffCount()):
                 buff = buffMgr.GetBuffByIndex(index)
-                GameWorld.DebugAnswer(curPlayer, "ID:%s,SkillID:%s,回合:%s,层:%s,V:%s" 
+                GameWorld.DebugAnswer(curPlayer, "ID:%s,SkillID:%s,回合:%s,层:%s,V:%s,来源:%s,光环:%s" 
                                       % (buff.GetBuffID(), buff.GetSkillID(), buff.GetRemainTime(), buff.GetLayer(), 
-                                         [buff.GetValue1(), buff.GetValue2(), buff.GetValue3()]
+                                         [buff.GetValue1(), buff.GetValue2(), buff.GetValue3()], buff.GetOwnerID(), buff.GetHaloObjIDList()
                                          ))
                 
     return
 
+def __doFightTest(curPlayer, msgList):
+    ## 测试战斗: TurnFight f 是否满技能 [位置1武将ID ...]
+    isAllSkill = msgList[1] if len(msgList) > 1 else 1
+    heroIDList = msgList[2:]
+    TurnAttack.GMTestFight(curPlayer, heroIDList, isAllSkill)
+    return
 
diff --git a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/IpyGameDataPY.py b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/IpyGameDataPY.py
index ca7e456..618e017 100644
--- a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/IpyGameDataPY.py
+++ b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/IpyGameDataPY.py
@@ -137,6 +137,7 @@
                         ("list", "BuffStateLimit", 0),
                         ("BYTE", "CurBuffState", 0),
                         ("WORD", "LastTime", 0),
+                        ("BYTE", "LastTimeType", 0),
                         ("BYTE", "LayerCnt", 0),
                         ("BYTE", "LayerMax", 0),
                         ("DWORD", "BuffRepeat", 0),
@@ -2358,11 +2359,12 @@
     def GetBuffStateLimit(self): return self.attrTuple[32] # Buff状态限制组 list
     def GetCurBuffState(self): return self.attrTuple[33] # Buff状态值 BYTE
     def GetLastTime(self): return self.attrTuple[34] # 持续时间 WORD
-    def GetLayerCnt(self): return self.attrTuple[35] # Buff层数 BYTE
-    def GetLayerMax(self): return self.attrTuple[36] # 最大层数 BYTE
-    def GetBuffRepeat(self): return self.attrTuple[37] # Buff叠加规则 DWORD
-    def GetDispersedLimit(self): return self.attrTuple[38] # 驱散限制 BYTE
-    def GetFightPower(self): return self.attrTuple[39] # 技能战斗力 DWORD
+    def GetLastTimeType(self): return self.attrTuple[35] # 持续时间规则 BYTE
+    def GetLayerCnt(self): return self.attrTuple[36] # Buff层数 BYTE
+    def GetLayerMax(self): return self.attrTuple[37] # 最大层数 BYTE
+    def GetBuffRepeat(self): return self.attrTuple[38] # Buff叠加规则 DWORD
+    def GetDispersedLimit(self): return self.attrTuple[39] # 驱散限制 BYTE
+    def GetFightPower(self): return self.attrTuple[40] # 技能战斗力 DWORD
 
 # 武将表
 class IPY_Hero():
diff --git a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Skill/PassiveTrigger/PassiveEff_5001.py b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Skill/PassiveTrigger/PassiveEff_5001.py
index d9422e6..047d585 100644
--- a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Skill/PassiveTrigger/PassiveEff_5001.py
+++ b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Skill/PassiveTrigger/PassiveEff_5001.py
@@ -29,10 +29,6 @@
         return True
     
     remainTime -= 1
-    if remainTime <= 0:
-        TurnBuff.DoBuffDel(turnFight, batObj, effBuff)
-    else:
-        effBuff.SetRemainTime(remainTime)
-        TurnBuff.SyncBuffRefresh(turnFight, batObj, effBuff)
-        
+    TurnBuff.SetBuffRemainTime(turnFight, batObj, effBuff, remainTime)
+    
     return True
diff --git a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Skill/TurnBuff.py b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Skill/TurnBuff.py
index ca8d91f..17425ce 100644
--- a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Skill/TurnBuff.py
+++ b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Skill/TurnBuff.py
@@ -87,18 +87,27 @@
         buffOwner = batObj
     ownerID = buffOwner.GetID()
     
+    skillType = buffSkill.GetSkillType()
     #无敌免疫持续减益buff、控制类buff
-    if buffSkill.GetSkillType() in [ChConfig.Def_SkillType_LstDepBuff, ChConfig.Def_SkillType_Action] \
+    if skillType in [ChConfig.Def_SkillType_LstDepBuff, ChConfig.Def_SkillType_Action] \
         and batObj.CheckInState(ChConfig.BatObjState_Wudi):
         GameWorld.DebugLog("无敌状态下免疫该buff: curID=%s,skillID=%s,ownerID=%s,relatedSkillID=%s" 
                            % (curID, skillID, ownerID, relatedSkillID))
         return
     
     #被动触发免疫控制buff
-    if buffSkill.GetSkillType() == ChConfig.Def_SkillType_Action:
+    if skillType == ChConfig.Def_SkillType_Action:
         if TurnPassive.GetTriggerEffectValue(turnFight, batObj, buffOwner, ChConfig.PassiveEff_ImmuneControlBuff, buffSkill):
             GameWorld.DebugLog("血量低于百分x时免疫控制buff: curID=%s,skillID=%s,ownerID=%s,relatedSkillID=%s,hp:%s/%s" 
                                % (curID, skillID, ownerID, relatedSkillID, batObj.GetHP(), batObj.GetMaxHP()))
+            return
+        
+    #光环技能,默认施法者身上也必须有,时长与施法者身上的同步
+    haloSrcBuff = None # 光源buff
+    if skillType == ChConfig.Def_SkillType_Halo and ownerID != curID:
+        haloSrcBuff = buffOwner.GetBuffManager().FindBuffBySkillID(skillID, ownerID)
+        if not haloSrcBuff:
+            GameWorld.ErrLog("添加光环技能时找不到光源! skillID=%s,ownerID=%s" % (skillID, ownerID))
             return
         
     buffValueList = GetAddBuffValue(turnFight, buffOwner, batObj, buffSkill)
@@ -193,7 +202,10 @@
             RefreshBuffEffect(turnFight, batObj, buff, buffSkill, buffOwner, refreshType=2)
             return buff
         
-    return __addNewBuff(turnFight, batObj, buffMgr, buffSkill, buffValueList, buffOwner, bySkill, afterLogic, setLayerCnt=addLayerCnt, isSync=isSync)
+    newBuff = __addNewBuff(turnFight, batObj, buffMgr, buffSkill, buffValueList, buffOwner, bySkill, afterLogic, setLayerCnt=addLayerCnt, isSync=isSync)
+    if skillType == ChConfig.Def_SkillType_Halo and newBuff:
+        __addHaloBuffEffObjID(curID, newBuff, skillID, ownerID, haloSrcBuff)
+    return newBuff
 
 def __addNewBuff(turnFight, batObj, buffMgr, buffSkill, buffValueList, buffOwner, bySkill=None, afterLogic=False, setLayerCnt=0, isSync=True):
     curID = batObj.GetID()
@@ -257,6 +269,60 @@
         
     return
 
+def __addHaloBuffEffObjID(curID, newBuff, skillID, ownerID, haloSrcBuff):
+    ## 添加光环buff有效的目标ID,添加光环buff时添加,不移除,由光源管理移除(光源武将死亡时删除)
+    if curID == ownerID:
+        newBuff.AddHaloObjID(curID) # 光源直接添加目标
+        return
+    haloSrcBuff.AddHaloObjID(curID) # 光源先添加新目标
+    haloObjIDList = haloSrcBuff.GetHaloObjIDList()
+    newBuff.SetHaloObjIDList(haloObjIDList) # 新buff直接同步设置为光源有效目标
+    
+    batObjMgr = BattleObj.GetBatObjMgr()
+    for haloObjID in haloObjIDList:
+        if haloObjID in [ownerID, curID]:
+            continue
+        haloObj = batObjMgr.getBatObj(haloObjID)
+        if not haloObj:
+            continue
+        haloBuff = haloObj.GetBuffManager().FindBuffBySkillID(skillID, ownerID)
+        if not haloBuff:
+            continue
+        haloBuff.SetHaloObjIDList(haloObjIDList) # 除光源及新加入的直接同步最新的有效目标
+    return
+
+def SetBuffRemainTime(turnFight, batObj, curBuff, remainTime):
+    if remainTime <= 0:
+        DoBuffDel(turnFight, batObj, curBuff)
+        return
+    
+    buffObjID = batObj.GetID()
+    ownerID = curBuff.GetOwnerID()
+    skillData = curBuff.GetSkillData()
+    skillID = skillData.GetSkillID()
+    skillType = skillData.GetSkillType()
+    
+    curBuff.SetRemainTime(remainTime)
+    SyncBuffRefresh(turnFight, batObj, curBuff)
+    
+    if skillType == ChConfig.Def_SkillType_Halo and ownerID == buffObjID:
+        haloObjIDList = curBuff.GetHaloObjIDList()
+        GameWorld.DebugLog("光环buff回合变更同步其他有效目标该光环: skillID=%s,ownerID=%s,haloObjIDList=%s" % (skillID, ownerID, haloObjIDList))
+        batObjMgr = BattleObj.GetBatObjMgr()
+        for haloObjID in haloObjIDList:
+            if haloObjID == buffObjID:
+                continue
+            haloObj = batObjMgr.getBatObj(haloObjID)
+            if not haloObj:
+                continue
+            haloBuff = haloObj.GetBuffManager().FindBuffBySkillID(skillID, ownerID)
+            if not haloBuff:
+                continue
+            haloBuff.SetRemainTime(remainTime)
+            SyncBuffRefresh(turnFight, haloObj, haloBuff)
+            
+    return
+
 def DoBuffLayerChange(turnFight, batObj, curBuff, updLayer, relatedSkill=None):
     ## buff层级变更
     if updLayer > 0:
@@ -288,7 +354,10 @@
     buffObjID = batObj.GetID()
     buffMgr = batObj.GetBuffManager()
     buffID = curBuff.GetBuffID()
+    ownerID = curBuff.GetOwnerID()
     skillData = curBuff.GetSkillData()
+    skillID = skillData.GetSkillID()
+    skillType = skillData.GetSkillType()
     
     # 先删除buff再触发其他内容,防止当前要删除的buff影响后续触发的内容,如无敌buff等,理论上触发的后续内容无敌buff不应该再生效
     curBuffState = skillData.GetCurBuffState()
@@ -326,6 +395,21 @@
         
     if isRefreshAttr:
         RefreshBuffAttr(batObj)
+        
+    if skillType == ChConfig.Def_SkillType_Halo and ownerID == buffObjID:
+        haloObjIDList = curBuff.GetHaloObjIDList()
+        GameWorld.DebugLog("光环buff删除同步删除其他有效目标该光环: skillID=%s,ownerID=%s,haloObjIDList=%s" % (skillID, ownerID, haloObjIDList))
+        batObjMgr = BattleObj.GetBatObjMgr()
+        for haloObjID in haloObjIDList:
+            if haloObjID == buffObjID:
+                continue
+            haloObj = batObjMgr.getBatObj(haloObjID)
+            if not haloObj:
+                continue
+            haloBuff = haloObj.GetBuffManager().FindBuffBySkillID(skillID, ownerID)
+            if not haloBuff:
+                continue
+            DoBuffDel(turnFight, haloObj, haloBuff, relatedSkill, afterLogic, tagObj)
     return
 
 def DoBuffDelAfterLogicOver(turnFight, buffObjID, curBuff, relatedSkill):
@@ -344,7 +428,7 @@
         callFunc(turnFight, batObj, curBuff, **kwargs)
     return
     
-def RefreshBuffAttr(batObj):
+def RefreshBuffAttr(batObj, isInit=False):
     ''' 刷新buff属性,如果有涉及到buff属性变更的,只能全部buff重新刷
     '''
     
@@ -438,15 +522,17 @@
     aftHP = batObj.GetHP()
     aftMaxHP = batObj.GetMaxHP()
     if aftMaxHP != befMaxHP:
-        batObj.SetMaxHP(aftMaxHP, True)
+        isNotify = not isInit
+        batObj.SetMaxHP(aftMaxHP, isNotify)
         if befHP and aftMaxHP > befMaxHP:
             aftHP += (aftMaxHP - befMaxHP)
-            batObj.SetHP(aftHP, True)
+            batObj.SetHP(aftHP, isNotify)
     GameWorld.DebugLog("    befHP=%s/%s, aftHP=%s/%s" % (befHP, befMaxHP, aftHP, aftMaxHP))
     GameWorld.DebugLog("    最终属性 ID:%s,atk=%s,def=%s,hp=%s/%s,%s" % (objID, batObj.GetAtk(), batObj.GetDef(), aftHP, aftMaxHP, batObj.GetBatAttrDict()))
     return
 
 def SyncBuffRefresh(turnFight, curBatObj, curBuff, relatedSkillID=0, isNewAdd=False):
+    ## @param curBatObj: 该buff的持有者,即在谁身上
     clientPack = ObjPool.GetPoolMgr().acquire(ChPyNetSendPack.tagSCBuffRefresh)
     clientPack.ObjID = curBatObj.GetID()
     clientPack.BuffID = curBuff.GetBuffID()
diff --git a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Skill/TurnBuffs/BuffAtkType_1001.py b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Skill/TurnBuffs/BuffAtkType_1001.py
index feafe51..093e5d9 100644
--- a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Skill/TurnBuffs/BuffAtkType_1001.py
+++ b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Skill/TurnBuffs/BuffAtkType_1001.py
@@ -23,7 +23,7 @@
     skillPer = curSkill.GetSkillPer()
     skillValue = curSkill.GetSkillValue()
     
-    hurtValue, hurtTypes = TurnSkill.CalcHurtHP(turnFight, attacker, defender, curSkill, skillValue, skillPer, damageoftime=1)
+    hurtValue, hurtTypes = TurnSkill.CalcFormatHurt(turnFight, attacker, defender, curSkill, skillValue, skillPer, damageoftime=1)
     return [hurtValue % ChConfig.Def_PerPointValue, hurtValue / ChConfig.Def_PerPointValue, hurtTypes]
 
 def DoBuffProcess(turnFight, batObj, curBuff, **kwargs):
diff --git a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Skill/TurnBuffs/BuffAtkType_1002.py b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Skill/TurnBuffs/BuffAtkType_1002.py
index 9c3b5e2..9bce0d9 100644
--- a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Skill/TurnBuffs/BuffAtkType_1002.py
+++ b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Skill/TurnBuffs/BuffAtkType_1002.py
@@ -19,7 +19,7 @@
 import ChConfig
 
 def CalcBuffValue(turnFight, attacker, defender, curSkill):
-    cureHP = TurnSkill.CalcCureHP(turnFight, attacker, defender, curSkill, largeNum=True)
+    cureHP = TurnSkill.CalcCureHP(turnFight, attacker, defender, curSkill)
     return [cureHP % ChConfig.Def_PerPointValue, cureHP / ChConfig.Def_PerPointValue]
 
 def DoBuffProcess(turnFight, batObj, curBuff, **kwargs):
diff --git a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Skill/TurnPassive.py b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Skill/TurnPassive.py
index 45f2372..2daa23e 100644
--- a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Skill/TurnPassive.py
+++ b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Skill/TurnPassive.py
@@ -34,6 +34,8 @@
 def OnTriggerPassiveEffect(turnFight, batObj, triggerWay, tagObj=None, connSkill=None, connSkillTypeID=0, connBuff=None, **kwargs):
     ''' 触发被动效果,可能触发技能、buff,需根据优先级触发
     '''
+    if not batObj.IsAlive():
+        return
     passiveEffMgr = batObj.GetPassiveEffManager()
     effInfoList = passiveEffMgr.GetPassiveEffByTrigger(triggerWay, connSkill, connSkillTypeID, connBuff)
     if not effInfoList:
diff --git a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Skill/TurnSkill.py b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Skill/TurnSkill.py
index bdb1453..aab62f4 100644
--- a/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Skill/TurnSkill.py
+++ b/ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/Skill/TurnSkill.py
@@ -60,6 +60,9 @@
     if not skillID:
         return
     
+    if not curBatObj.IsAlive():
+        return
+    
     objID = curBatObj.GetID()
     
     if hasattr(useSkill, "GetRemainTime") and useSkill.GetRemainTime() > 0:
@@ -134,11 +137,7 @@
     if IsNeedSyncSkill(useSkill):
         # 因为可能触发连击,所以标记需带上累计使用技能次数,确保唯一
         useTag = "Skill_%s_%s_%s" % (objID, skillID, curBatObj.GetSkillUseCnt(skillID) + 1)
-        clientPack = poolMgr.acquire(ChPyNetSendPack.tagSCTurnFightTag)
-        clientPack.Tag = useTag
-        clientPack.Len = len(clientPack.Tag)
-        clientPack.Sign = 0
-        turnFight.addBatPack(clientPack)
+        Sync_TurnFightTag(turnFight, useTag, 0)
         
     #这个技能是Buff
     if SkillCommon.IsBuff(useSkill):
@@ -149,11 +148,7 @@
     DoAttackResult(turnFight, curBatObj, useSkill)
     
     if useTag:
-        clientPack = poolMgr.acquire(ChPyNetSendPack.tagSCTurnFightTag)
-        clientPack.Tag = useTag
-        clientPack.Len = len(clientPack.Tag)
-        clientPack.Sign = 1
-        turnFight.addBatPack(clientPack)
+        Sync_TurnFightTag(turnFight, useTag, 1)
         
     # 处理反击 或 连击
     if isTurnNormalSkill:
@@ -596,8 +591,19 @@
 def __doAddBuff(turnFight, curBatObj, useSkill):
     #执行添加buff
     
-    for tagBatObj in useSkill.GetTagObjList():
-        TurnBuff.OnAddBuff(turnFight, tagBatObj, useSkill, buffOwner=curBatObj)
+    #光环技能,需先添加施法者
+    if useSkill.GetSkillType() == ChConfig.Def_SkillType_Halo:
+        curID = curBatObj.GetID()
+        skillID = useSkill.GetSkillID()
+        GameWorld.DebugLog("光环技能先给施法者添加光源buff! skillID=%s,ownerID=%s" % (skillID, curID))
+        if TurnBuff.OnAddBuff(turnFight, curBatObj, useSkill, buffOwner=curBatObj):
+            for tagBatObj in useSkill.GetTagObjList():
+                if tagBatObj.GetID() == curBatObj.GetID():
+                    continue
+                TurnBuff.OnAddBuff(turnFight, tagBatObj, useSkill, buffOwner=curBatObj)
+    else:
+        for tagBatObj in useSkill.GetTagObjList():
+            TurnBuff.OnAddBuff(turnFight, tagBatObj, useSkill, buffOwner=curBatObj)
         
     return
 
@@ -639,6 +645,7 @@
     return
 
 def __doHarmSelf(turnFight, curBatObj, useSkill):
+    ## 施法前自残
     harmEff = useSkill.GetEffectByID(ChConfig.SkillEff_UseSkillHarmSelf)
     if not harmEff:
         return
@@ -671,21 +678,52 @@
 def SkillModule_1(turnFight, curBatObj, useSkill):
     ## 通用攻击,单攻、群攻
     
+    # 计算伤害
+    calcHurtResults = []
     for tagBatObj in useSkill.GetTagObjList():
-        __doSkillHurtHP(turnFight, curBatObj, tagBatObj, useSkill)
+        hurtValue, hurtTypes, immuneHurt = __calcSkillHurt(turnFight, curBatObj, tagBatObj, useSkill)
+        calcHurtResults.append([tagBatObj, hurtValue, hurtTypes, immuneHurt])
         
+    DoSkillHurtHP(turnFight, curBatObj, useSkill, calcHurtResults)        
     return
+
+def __calcSkillHurt(turnFight, atkObj, defObj, curSkill):
+    ## 计算技能伤害,只计算值,不做实际处理
+    # @return: hurtValue, hurtTypes, immuneHurt
+    atkSkillPer = curSkill.GetSkillPer()
+    atkSkillValue = curSkill.GetSkillValue()
+    hurtValue, hurtTypes = CalcFormatHurt(turnFight, atkObj, defObj, curSkill, atkSkillValue, atkSkillPer)
+    hurtValue, hurtTypes, immuneHurt = CalcHurtWithBuff(turnFight, atkObj, defObj, hurtValue, hurtTypes)
+    return hurtValue, hurtTypes, immuneHurt
 
 def SkillModule_5(turnFight, curBatObj, useSkill):
     ## 弹射(多次攻击,切换目标,单目标可多次)
     
-    ricochetObjList = GetRicochetObjList(turnFight, curBatObj, useSkill)
+    ricochetObjList = GetRicochetObjRankList(turnFight, curBatObj, useSkill)
+    
+    # 弹射技能需先从弹射目标里预选好曹仁要防护的对象
+    caorenProtectObj = None # 曹仁保护的目标,血量最低的,曹仁专用,不挡自己,不挡无敌,顺延直到一个可以挡的最低血量目标,如果没有就不挡
+    for tagObj in ricochetObjList:
+        tagID = tagObj.GetID()
+        if tagID == ChConfig.HeroID_Caoren:
+            continue
+        if tagObj.CheckInState(ChConfig.BatObjState_Wudi):
+            continue
+        if not caorenProtectObj or tagObj.GetHP() < caorenProtectObj.GetHP():
+            caorenProtectObj = tagObj
+    if caorenProtectObj == None:
+        caorenProtectObj = 0 # 标记为已筛选过
+        
+    # 按顺序一个个弹伤害
     for tagBatObj in ricochetObjList:
-        __doSkillHurtHP(turnFight, curBatObj, tagBatObj, useSkill)
+        hurtValue, hurtTypes, immuneHurt = __calcSkillHurt(turnFight, curBatObj, tagBatObj, useSkill)
+        
+        calcHurtResults = [[tagBatObj, hurtValue, hurtTypes, immuneHurt]]
+        DoSkillHurtHP(turnFight, curBatObj, useSkill, calcHurtResults, "RicochetHurt", caorenProtectObj, isManyTimes=True)
         
     return
 
-def GetRicochetObjList(turnFight, curBatObj, useSkill):
+def GetRicochetObjRankList(turnFight, curBatObj, useSkill):
     ## 获取弹射目标队列
     ricochetCnt = useSkill.GetTagCount() # 弹射次数,默认取技能目标数
     randEffect = useSkill.GetEffectByID(ChConfig.SkillEff_RandRicochetCnt)
@@ -695,63 +733,120 @@
         ricochetCnt = random.randint(valueA, valueB)
         GameWorld.DebugLog("随机弹射次数: %s,valueA=%s,valueB=%s" % (ricochetCnt, valueA, valueB))
     ricochetCnt += TurnPassive.GetTriggerEffectValue(turnFight, curBatObj, None, ChConfig.PassiveEff_AddRicochetCnt, useSkill)
-    GameWorld.DebugLog("弹射次数: %s" % (ricochetCnt)) 
     
     # 弹射:优先弹射次数少的目标,相同目标可弹射多次
     tagObjList = useSkill.GetTagObjList()
     random.shuffle(tagObjList)
     objCnt = len(tagObjList)
     ricochetObjList = []
+    ricochetObjIDList = []
     for index in range(ricochetCnt):
         tagObj = tagObjList[index % objCnt]
         ricochetObjList.append(tagObj)
+        ricochetObjIDList.append(tagObj.GetID())
+        
+    GameWorld.DebugLog("弹射次数: %s, ricochetObjIDList=%s" % (ricochetCnt, ricochetObjIDList)) 
     return ricochetObjList
 
 def SkillModule_2(turnFight, curBatObj, useSkill):
     ## 治疗
     
+    calcCureResults = []
     relativeObj = GetRelativeObj(turnFight, curBatObj)
     for tagBatObj in useSkill.GetTagObjList():
-        __doCureObj(turnFight, curBatObj, tagBatObj, useSkill, relativeObj)
+        cureHP = CalcCureHP(turnFight, curBatObj, tagBatObj, useSkill, relativeObj=relativeObj)
+        poisonCureOwner = GetPoisonCureOwner(tagBatObj)
+        calcCureResults.append([tagBatObj, cureHP, poisonCureOwner])
         
+    DoSkillCureHP(turnFight, curBatObj, useSkill, calcCureResults)
     return
 
 def SkillModule_9(turnFight, curBatObj, useSkill):
     ## 弹射治疗(多次治疗,切换目标,单目标可多次)
     
     relativeObj = GetRelativeObj(turnFight, curBatObj)
-    ricochetObjList = GetRicochetObjList(turnFight, curBatObj, useSkill)
+    ricochetObjList = GetRicochetObjRankList(turnFight, curBatObj, useSkill)
+    
+    # 弹射技能需先从弹射目标里预选好曹仁要防护的对象,有毒奶的情况
+    poisonCureOwnerDict = {}
+    caorenProtectObj = None # 曹仁保护的目标,血量最低的,曹仁专用,不挡自己,不挡无敌,顺延直到一个可以挡的最低血量目标,如果没有就不挡
+    for tagObj in ricochetObjList:
+        tagID = tagObj.GetID()
+        if tagID not in poisonCureOwnerDict:
+            poisonCureOwner = GetPoisonCureOwner(tagObj)
+            poisonCureOwnerDict[tagID] = poisonCureOwner
+        poisonCureOwner = poisonCureOwnerDict[tagID]
+        if not poisonCureOwner:
+            continue
+        if tagID == ChConfig.HeroID_Caoren:
+            continue
+        if tagObj.CheckInState(ChConfig.BatObjState_Wudi):
+            continue
+        if not caorenProtectObj or tagObj.GetHP() < caorenProtectObj.GetHP():
+            caorenProtectObj = tagObj
+    if caorenProtectObj == None:
+        caorenProtectObj = 0 # 标记为已筛选过
+        
     for tagBatObj in ricochetObjList:
-        __doCureObj(turnFight, curBatObj, tagBatObj, useSkill, relativeObj)
+        cureHP = CalcCureHP(turnFight, curBatObj, tagBatObj, useSkill, relativeObj=relativeObj)
+        poisonCureOwner = poisonCureOwnerDict.get(tagBatObj.GetID())
+        calcCureResults = [[tagBatObj, cureHP, poisonCureOwner]]
+        
+        DoSkillCureHP(turnFight, curBatObj, useSkill, calcCureResults, "RicochetCure", caorenProtectObj, isManyTimes=True)
         
     return
 
-def __doCureObj(turnFight, curBatObj, tagBatObj, useSkill, relativeObj=None):
-    cureHP = CalcCureHP(turnFight, curBatObj, tagBatObj, useSkill, largeNum=True, relativeObj=relativeObj)
-    if cureHP <= 0:
-        return
+def DoSkillCureHP(turnFight, atkObj, curSkill, calcCureResults, lostType="", caorenProtectObj=None, isManyTimes=False):
+    '''结算技能治疗
+    @param calcCureResults: 技能公式计算后的伤害结果 [[tagBatObj, cureHP, poisonCureOwner], ...]
+    @param caorenProtectObj: 曹仁专用,要防护的对象
+    @param isManyTimes: 是否是后端多次计算的
+    '''
     
-    dID = tagBatObj.GetID()
-    hurtObj = useSkill.AddHurtObj(dID)
-    if DoPoisonCure(turnFight, tagBatObj, cureHP, hurtObj, useSkill):
-        return
-    
-    skillID = useSkill.GetSkillID()
-    dHP = tagBatObj.GetHP()
-    dMapHP = tagBatObj.GetMaxHP()
-    
-    remainHP = min(dHP + cureHP, dMapHP)
-    realCureHP = max(remainHP - dHP, 0)
-    tagBatObj.SetHP(remainHP)
-    
-    hurtObj.AddHurtType(ChConfig.HurtTYpe_Recovery)
-    hurtObj.SetHurtHP(cureHP)
-    hurtObj.SetLostHP(realCureHP)
-    hurtObj.SetCurHP(tagBatObj.GetHP())
-    GameWorld.DebugLog("    治疗: dID=%s,cureHP=%s,realCureHP=%s,%s/%s" % (dID, cureHP, realCureHP, tagBatObj.GetHP(), dMapHP))
-    
-    TurnAttack.AddTurnObjCureHP(tagBatObj, curBatObj, cureHP, realCureHP, skillID)
+    skillID = curSkill.GetSkillID()
+    calcHurtResults = []
+    for tagBatObj, cureHP, poisonCureOwner in calcCureResults:
+        # 汇总毒奶
+        if poisonCureOwner:
+            hurtTypes = pow(2, ChConfig.HurtTYpe_Cure)
+            hurtTypes |= pow(2, ChConfig.HurtType_PoisonCureHurt)
+            hurtValue = cureHP
+            hurtValue, hurtTypes, immuneHurt = CalcHurtWithBuff(turnFight, atkObj, tagBatObj, hurtValue, hurtTypes)
+            calcHurtResults.append([tagBatObj, hurtValue, hurtTypes, immuneHurt])
+            continue
+        
+        # 非毒奶的执行治疗
+        dID = tagBatObj.GetID()
+        dHP = tagBatObj.GetHP()
+        dMapHP = tagBatObj.GetMaxHP()
+        
+        remainHP = min(dHP + cureHP, dMapHP)
+        realCureHP = max(remainHP - dHP, 0)
+        tagBatObj.SetHP(remainHP)
+        
+        hurtObj = curSkill.AddHurtObj(dID)
+        hurtObj.AddHurtType(ChConfig.HurtTYpe_Cure)
+        hurtObj.SetHurtHP(cureHP)
+        hurtObj.SetLostHP(realCureHP)
+        hurtObj.SetCurHP(tagBatObj.GetHP())
+        GameWorld.DebugLog("    治疗: dID=%s,cureHP=%s,realCureHP=%s,%s/%s" % (dID, cureHP, realCureHP, tagBatObj.GetHP(), dMapHP))
+        
+        TurnAttack.AddTurnObjCureHP(tagBatObj, atkObj, cureHP, realCureHP, skillID)
+        
+    # 再结算毒奶伤害
+    DoSkillHurtHP(turnFight, atkObj, curSkill, calcHurtResults, "PoisonCure", caorenProtectObj, isManyTimes)
     return
+
+def GetPoisonCureOwner(defObj):
+    ## 获取玩家身上的毒奶buff归属者
+    # @return: buffOwner or None-无毒奶buff
+    buffMgr = defObj.GetBuffManager()
+    poisonCureBuff = buffMgr.FindBuffByState(ChConfig.BatObjState_PoisonCure) # 毒奶暂定唯一
+    if not poisonCureBuff:
+        return
+    ownerID = poisonCureBuff.GetOwnerID()
+    buffOwner = BattleObj.GetBatObjMgr().getBatObj(ownerID) # 攻击方
+    return buffOwner
 
 def DoCureOfTime(turnFight, batObj, curBuff, cureHP, **kwargs):
     ## 执行持续治疗单次逻辑
@@ -765,19 +860,16 @@
     if not buffOwner:
         return
     
-    atkObj = buffOwner
     defObj = batObj
+    poisonCureOwner = GetPoisonCureOwner(defObj)
+    atkObj = poisonCureOwner if poisonCureOwner else buffOwner
     
-    atkID = ownerID
+    atkID = atkObj.GetID()
     defID = defObj.GetID()
-    tagObjList = [defObj]
     
-    poolMgr = ObjPool.GetPoolMgr()
-    useSkill = poolMgr.acquire(BattleObj.PySkill, skillIpyData, atkID)
-    useSkill.SetTagObjList(tagObjList)
-    useSkill.ClearHurtObj()
-    hurtObj = useSkill.AddHurtObj(defID)
-    hurtObj.AddHurtType(ChConfig.HurtTYpe_Recovery)
+    # 结算需要同步标签
+    useTag = "Skill_%s_%s_Cot" % (atkID, skillID)
+    Sync_TurnFightTag(turnFight, useTag, 0)
     
     dHP = defObj.GetHP()
     dMaxHP = defObj.GetMaxHP()
@@ -792,15 +884,27 @@
     #    hurtValue *= (10000 + FinalDamPer) / 10000.0
     #    GameWorld.DebugLog("    增伤: hurtValue=%s,FinalDamPer=%s" % (hurtValue, FinalDamPer))
     
-    if DoPoisonCure(turnFight, defObj, cureHP, hurtObj, useSkill):
-        remainHP = defObj.GetHP()
-        diffType = 0
-        diffValue = hurtObj.GetHurtHP()
+    poolMgr = ObjPool.GetPoolMgr()
+    useSkill = poolMgr.acquire(BattleObj.PySkill, skillIpyData, atkID)
+    useSkill.SetTagObjList([defObj])
+    useSkill.SetBatType(ChConfig.TurnBattleType_Cot)
+    
+    poisonCureOwner = GetPoisonCureOwner(defObj)
+    if poisonCureOwner:
+        GameWorld.DebugLog("本次治疗为毒奶: cureHP=%s" % cureHP)
+        hurtValue = cureHP
+        hurtTypes = pow(2, ChConfig.HurtTYpe_Cure)
+        hurtTypes |= pow(2, ChConfig.HurtType_PoisonCureHurt)
+        hurtValue, hurtTypes, immuneHurt = CalcHurtWithBuff(turnFight, poisonCureOwner, defObj, hurtValue, hurtTypes)
+        calcHurtResults = [[defObj, hurtValue, hurtTypes, immuneHurt]]
+        DoSkillHurtHP(turnFight, poisonCureOwner, useSkill, calcHurtResults, "PoisonCOT")
     else:
         remainHP = min(dHP + cureHP, dMaxHP)
         realCureHP = max(remainHP - dHP, 0)
         defObj.SetHP(remainHP)
         
+        hurtObj = useSkill.AddHurtObj(defID)
+        hurtObj.AddHurtType(ChConfig.HurtTYpe_Cure)
         hurtObj.SetHurtHP(cureHP)
         hurtObj.SetLostHP(realCureHP)
         hurtObj.SetCurHP(defObj.GetHP())
@@ -808,13 +912,12 @@
         
         TurnAttack.AddTurnObjCureHP(defObj, atkObj, cureHP, realCureHP, skillID)
         
-        diffType = 1
-        diffValue = cureHP
-        
-    hurtTypes = hurtObj.GetHurtTypes()
-    Sync_PropertyRefreshView(turnFight, defObj, ChConfig.AttrID_HP, remainHP, diffValue, diffType=diffType, skillID=skillID, hurtTypes=hurtTypes)
+    Sync_UseSkill(turnFight, atkObj, useSkill)
     
     DoBeAttackResult(turnFight, atkObj, useSkill)
+    
+    # 通知结束标签
+    Sync_TurnFightTag(turnFight, useTag, 1)
     
     useSkill.ResetUseRec()
     poolMgr.release(useSkill)
@@ -891,7 +994,7 @@
     '''
     
     if curBatObj:
-        # 反弹、吸血 的原因,需要统一处理后结算,可能导致HP为负值,修正为0
+        # 反弹、吸血、平摊伤害自残 的原因,需要统一处理后结算,可能导致HP为负值,修正为0
         if curBatObj.GetHP() < 0:
             curBatObj.SetHP(0)
             
@@ -996,6 +1099,8 @@
         #GameWorld.DebugLog("同阵营不触发反击!") # 魅惑可能导致打自己人
         return
     
+    # 大回合单武将反击次数限制
+    
     canAtkbackDictTypeList = IpyGameDataPY.GetFuncEvalCfg("ParryCfg", 2)
     if atkObj.GetAtkDistType() not in canAtkbackDictTypeList:
         heroID = atkObj.GetHeroID()
@@ -1003,7 +1108,7 @@
         return
     
     canAtkBack = False
-    for hurtObj in useSkill.GetHurtObjList():
+    for hurtObj in useSkill.GetHurtObjListAll():
         if hurtObj.GetObjID() != tagID:
             continue
         if hurtObj.HaveHurtType(ChConfig.HurtType_Parry): # 格挡时可反击
@@ -1061,16 +1166,22 @@
             
     # 统计击杀
     killObjList = [] # 击杀其他阵营目标列表
-    dieObjList = [] # 死亡的单位列表
-    for tagObj in useSkill.GetTagObjList():
+    dieObjList = [] # 死亡的单位列表,包含友方单位或自己
+    tagObjList = useSkill.GetTagObjList() # 主要目标列表
+    tagObjListEx = useSkill.GetTagObjListEx() # 额外目标列表
+    tagObjListAll = tagObjList + tagObjListEx
+    for tagObj in tagObjListAll:
         tagID = tagObj.GetID()
         if tagObj.IsAlive() and tagObj.GetHP() <= 0:
             dieObjList.append(tagObj)
             if tagObj.GetFaction() != curObj.GetFaction():
                 killObjList.append(tagObj)
-                TurnAttack.SetObjKilled(turnFight, tagObj, curObj, useSkill)
+            TurnAttack.SetObjKilled(turnFight, tagObj, curObj, useSkill)
     useSkill.SetKillObjList(killObjList)
-    if curObj.IsAlive() and curObj.GetHP() <= 0:
+    # 判断自己,因为反弹、平摊伤害的原因,有可能自己干死自己
+    selfAlive = curObj.IsAlive()
+    if selfAlive and curObj.GetHP() <= 0:
+        selfAlive = False
         dieObjList.append(curObj)
         TurnAttack.SetObjKilled(turnFight, curObj)
         
@@ -1079,12 +1190,12 @@
     isSuperHit, isStun, isSuckHP = False, False, False
     missObjIDList, immuneObjIDList = [], [] # 闪避、免疫对象ID列表
     beHurtObjIDList = [] # 受伤的对象ID列表
-    for hurtObj in useSkill.GetHurtObjList():
+    for hurtObj in useSkill.GetHurtObjListAll():
         hurtObjID = hurtObj.GetObjID()
         tagObj = batObjMgr.getBatObj(hurtObjID)
         if not tagObj:
             continue
-        if not hurtObj.HaveHurtType(ChConfig.HurtTYpe_Recovery) and not hurtObj.HaveHurtType(ChConfig.HurtType_Immune) \
+        if not hurtObj.HaveHurtType(ChConfig.HurtTYpe_Cure) and not hurtObj.HaveHurtType(ChConfig.HurtType_Immune) \
             and (isTurnNormalSkill or isAngerSkill) and tagObj.IsAlive():
             __doSkillHurtAnger(tagObj, hurtObj.GetLostHP(), useSkill)
             
@@ -1116,7 +1227,7 @@
             isSuckHP = True
             
     # 记录最后一次总伤害,有伤害目标才记录
-    if useSkill.GetHurtObjList():
+    if totalHurtValue:
         curObj.SetLastHurtValue(totalHurtValue)
         
     # 群攻只触发一次特长
@@ -1135,7 +1246,8 @@
     effIgnoreObjIDList = missObjIDList + immuneObjIDList
     useSkill.SetEffIgnoreObjIDList(effIgnoreObjIDList)
     # 优先触发本技能额外效果,注:仅该技能释放后该技能的额外效果视为主技能的效果,优先级最高
-    __DoCurSkillEff(turnFight, curObj, useSkill, effIgnoreObjIDList, isUseSkill)
+    if selfAlive:
+        __DoCurSkillEff(turnFight, curObj, useSkill, effIgnoreObjIDList, isUseSkill)
     
     # ========== 以下触发被动 ==========
     
@@ -1171,44 +1283,47 @@
     triggerOne = False
     batType = useSkill.GetBatType()
     isAttackDirect = (isUseSkill and SkillCommon.isAttackDirectSkill(useSkill)) # 是否直接攻击
-    for tagObj in useSkill.GetTagObjList():
+    for tagObj in tagObjListAll:
         tagID = tagObj.GetID()
         if tagID in effIgnoreObjIDList:
             continue
+        
+        isExObj = tagObj in tagObjListEx # 是否额外目标
         
         # 掉血时
         if tagID in beHurtObjIDList:
             TurnPassive.OnTriggerPassiveEffect(turnFight, tagObj, ChConfig.TriggerWay_BeHurt, curObj, connSkill=useSkill)
             
         # 直接攻击
-        if isAttackDirect:
+        if isAttackDirect and not isExObj:
             if not triggerOne:
                 TurnPassive.OnTriggerPassiveEffect(turnFight, curObj, ChConfig.TriggerWay_AttackOverDirectOne, tagObj, connSkill=useSkill)
             TurnPassive.OnTriggerPassiveEffect(turnFight, curObj, ChConfig.TriggerWay_AttackOverDirect, tagObj, connSkill=useSkill)
             TurnPassive.OnTriggerPassiveEffect(turnFight, tagObj, ChConfig.TriggerWay_BeAttackedDirect, curObj, connSkill=useSkill)
-        else:
+        # 持续伤害
+        elif not isAttackDirect:
             TurnPassive.OnTriggerPassiveEffect(turnFight, tagObj, ChConfig.TriggerWay_BeAnyEffect, curObj, connSkill=useSkill)
             # 受到持续伤害
-            if tagID in beHurtObjIDList:
+            if tagID in beHurtObjIDList and not isExObj:
                 TurnPassive.OnTriggerPassiveEffect(turnFight, tagObj, ChConfig.TriggerWay_BeDOTHurt, curObj, connSkill=useSkill)
                 
         # 使用技能后
-        if isUseSkill:
+        if isUseSkill and not isExObj:
             if not triggerOne:
                 TurnPassive.OnTriggerPassiveEffect(turnFight, curObj, ChConfig.TriggerWay_UseSkillOverOne, tagObj, connSkill=useSkill)
             TurnPassive.OnTriggerPassiveEffect(turnFight, curObj, ChConfig.TriggerWay_UseSkillOver, tagObj, connSkill=useSkill)
             
         # 连击
-        if batType == ChConfig.TurnBattleType_Combo:
+        if batType == ChConfig.TurnBattleType_Combo and not isExObj:
             TurnPassive.OnTriggerPassiveEffect(turnFight, tagObj, ChConfig.TriggerWay_BeCombo, curObj, connSkill=useSkill)
         # 追击
-        elif batType == ChConfig.TurnBattleType_Pursue:
+        elif batType == ChConfig.TurnBattleType_Pursue and not isExObj:
             TurnPassive.OnTriggerPassiveEffect(turnFight, tagObj, ChConfig.TriggerWay_BePursue, curObj, connSkill=useSkill)
             
         triggerOne = True # 设置已经触发过一次
         
-    # 有击杀时验证是否结算,最后处理
-    if killObjList:
+    # 验证是否结算,最后处理
+    if dieObjList:
         turnFight.checkOverByKilled()
         
     return
@@ -1291,6 +1406,8 @@
         return
     specialtyAddXPDict = IpyGameDataPY.GetFuncEvalCfg("AngerXP", 5, {})
     if str(specialty) not in specialtyAddXPDict:
+        return
+    if not gameObj.IsAlive():
         return
     addXP = specialtyAddXPDict[str(specialty)]
     addXP = GetEnhanceXP(gameObj, addXP)
@@ -1465,55 +1582,8 @@
         
     return isOK
 
-def __doSkillHurtHP(turnFight, attacker, defObj, curSkill):
-    ## 执行技能伤血,只计算伤血,其他逻辑等技能同步后再处理
-    # @return: None - 没有执行成功,即忽略该目标
-    
-    atkObj = attacker
-    atkID = atkObj.GetID()
-    defID = defObj.GetID()
-    skillID = curSkill.GetSkillID()
-    hurtObj = curSkill.AddHurtObj(defID)
-    
-    atkSkillPer = curSkill.GetSkillPer()
-    atkSkillValue = curSkill.GetSkillValue()
-    
-    dHP = defObj.GetHP()                # 防守方当前血量
-    dMaxHP = defObj.GetMaxHP()          # 防守方最大血量
-    
-    zhansha = False # 斩杀特殊处理
-    # 斩杀
-    if zhansha:
-        pass
-    
-    else:
-        hurtValue, hurtTypes = CalcHurtHP(turnFight, atkObj, defObj, curSkill, atkSkillValue, atkSkillPer)
-        hurtValue, realHurtHP, hurtTypes = CalcHurtHPWithBuff(turnFight, atkObj, defObj, curSkill, hurtValue, hurtTypes)
-        
-        #伤害结构体
-        hurtObj.SetHurtTypes(hurtTypes)
-        hurtObj.SetHurtHP(hurtValue)
-        hurtObj.SetRealHurtHP(realHurtHP)
-        remainHP = min(dMaxHP, max(0, dHP - realHurtHP)) # 剩余血量
-        
-    remainHP = int(remainHP)    #防范
-    lostHP = dHP - remainHP # 实际掉血量
-    hurtObj.SetLostHP(lostHP)
-    hurtObj.SetCurHP(remainHP)
-    defObj.SetHP(remainHP)
-    GameWorld.DebugLog("    伤血: atkID=%s,defID=%s,hurtValue=%s,realHurtHP=%s,lostHP=%s,%s/%s" 
-                       % (atkID, defID, hurtValue, realHurtHP, lostHP, defObj.GetHP(), defObj.GetMaxHP()))
-    TurnAttack.AddTurnObjHurtValue(atkObj, defObj, hurtValue, lostHP, skillID)
-    
-    #反弹伤害
-    CalcBounceHP(turnFight, atkObj, defObj, hurtObj, curSkill)
-    
-    #吸血
-    CalcSuckBlood(turnFight, atkObj, defObj, hurtObj, curSkill)
-    return
-
-def CalcHurtHP(turnFight, atkObj, defObj, curSkill, atkSkillValue, atkSkillPer, **kwargs):
-    '''计算伤害,默认按攻击计算
+def CalcFormatHurt(turnFight, atkObj, defObj, curSkill, atkSkillValue, atkSkillPer, **kwargs):
+    '''按公式计算伤害,默认按攻击计算
     '''
     
     mapID = turnFight.mapID
@@ -1660,7 +1730,6 @@
     GameWorld.DebugLog("伤血计算: atkID=%s,defID=%s,skillID=%s,atkSkillPer=%s,calcType=%s,aAtk=%s,dDef=%s,dHP=%s/%s,hurtTypes=%s,aAddSkillPer=%s,aFinalDamPer=%s" 
                        % (atkID, defID, skillID, atkSkillPer, calcType, aAtk, dDef, dHP, dMaxHP, hurtTypes, aAddSkillPer, aFinalDamPer))
     
-    # 持续性伤害
     if isTurnNormalSkill:
         hurtValue = eval(IpyGameDataPY.GetFuncCompileCfg("HurtFormula", 1))
         GameWorld.DebugLog("    普攻技能伤害=%s,aNormalSkillPer=%s,dNormalSkillPerDef=%s" % (hurtValue, aNormalSkillPer, dNormalSkillPerDef))
@@ -1768,35 +1837,363 @@
         return True
     return False
 
-def CalcHurtHPWithBuff(turnFight, atkObj, defObj, curSkill, hurtValue, hurtTypes=0):
-    ## 计算伤害后,因各种buff和状态的影响处理
-    # @return: hurtValue, realHurtHP, hurtTypes
+def CalcHurtWithBuff(turnFight, atkObj, defObj, hurtValue, hurtTypes=0):
+    '''计算buff影响得到最终伤害: 如无敌、增减伤盾等
+    @param hurtValue: 公式计算的伤害值
+    @return: hurtValue, hurtTypes, immuneHurt
+    '''
     
+    immuneHurt = 0 # 免疫伤害值
     if hurtValue <= 0:
-        return 0, 0, hurtTypes
+        hurtValue = 0
+        return hurtValue, hurtTypes, immuneHurt
     
     hurtValue = int(hurtValue)
-    buffMgr = defObj.GetBuffManager()
-    wudiBuffList = buffMgr.FindBuffListByState(ChConfig.BatObjState_Wudi)
-    if wudiBuffList:
+    if defObj.CheckInState(ChConfig.BatObjState_Wudi):
         hurtTypes |= pow(2, ChConfig.HurtType_Immune) # 添加免疫
+        immuneHurt = hurtValue
+        hurtValue = 0
+        return hurtValue, hurtTypes, immuneHurt
+    
+    # 增减伤盾, 会改变 hurtValue,区别与计算公式中的增减伤,这里处理的是对公式计算结果的再次增减
+    
+    hurtValue = max(0, hurtValue)
+    return hurtValue, hurtTypes, immuneHurt
+
+def DoSkillHurtHP(turnFight, atkObj, curSkill, calcHurtResults, lostType="", caorenProtectObj=None, isManyTimes=False):
+    '''结算计算好的技能伤害
+    @param calcHurtResults: 技能公式计算后的伤害结果 [[tagBatObj, hurtValue, hurtTypes, immuneHurt], ...]
+    @param caorenProtectObj: 曹仁专用,要防护的对象
+    @param isManyTimes: 是否是后端多次计算伤害的
+    '''
+    if not calcHurtResults:
+        return
+    
+    isAttackDirect = SkillCommon.isAttackDirectSkill(curSkill) # 是否直接攻击
+    # 计算平摊
+    finalHurtResults = __calcAverageHurt(turnFight, atkObj, curSkill, calcHurtResults, caorenProtectObj, isAttackDirect)
+    
+    # 执行最终伤害结算
+    mainHurtObj = None
+    for hurtInfo in finalHurtResults:
+        defObj, hurtValue, hurtTypes, immuneHurt = hurtInfo[:4]
+        isEx = hurtInfo[4] if len(hurtInfo) > 4 else 0 # 是否是额外目标
+        
+        lostHP = DoLostHP(turnFight, atkObj, defObj, hurtValue, curSkill, lostType, hpCanNegative=True, immuneHurt=immuneHurt)
+        
+        #伤害结构体
+        defID = defObj.GetID()
+        if isEx:
+            if isManyTimes and mainHurtObj:
+                hurtObj = mainHurtObj.AddHurtObjEx(defID)
+            else:
+                hurtObj = curSkill.AddHurtObj(defID, isEx=True)
+            curSkill.AddTagObjEx(defObj)
+        else:
+            hurtObj = curSkill.AddHurtObj(defID)
+            if not mainHurtObj:
+                mainHurtObj = hurtObj
+                
+        hurtObj.SetHurtTypes(hurtTypes)
+        hurtObj.SetHurtHP(hurtValue)
+        hurtObj.SetLostHP(lostHP)
+        hurtObj.SetCurHP(max(defObj.GetHP(), 0))
+        
+        #反弹伤害:仅非dot的技能造成的伤害技能本身目标才反弹,附属目标不反弹(平摊、溅射  附属目标),反弹伤害不平摊
+        if isAttackDirect:
+            CalcBounceHP(turnFight, atkObj, defObj, hurtObj, curSkill)
+        #吸血:除反弹外的任意伤害都吸
+        CalcSuckBlood(turnFight, atkObj, defObj, hurtObj, curSkill)
+        
+    return mainHurtObj
+
+def __calcAverageHurt(turnFight, curBatObj, useSkill, calcHurtResults, caorenProtectObj=None, isAttackDirect=False):
+    '''计算伤害均摊承伤
+    @return: finalHurtResults  [[tagBatObj, hurtValue, hurtTypes, immuneHurt, <isEx>], ...]
+    '''
+    if not calcHurtResults:
+        return calcHurtResults
+    
+    # 是否在内层筛选曹仁防护目标,外层如果处理过了但是为None时会设置为0
+    checkCaoren = True if (caorenProtectObj == None and isAttackDirect) else False
+    if not isAttackDirect or caorenProtectObj == 0:
+        caorenProtectObj = None
+        
+    srcHurtTotal = 0 # 源总伤害
+    hurtValueDict = {} # 最终伤害 {objID:hurtValue, ...}
+    immuneHurtDict = {} # 最终免疫伤害 {objID:immuneHurt, ...} # 无敌目标可摊伤害,但免疫该伤害
+    tagFaction, tagLineupNum = 0, 0
+    for calcInfo in calcHurtResults:
+        tagObj, hurtValue, _, immuneHurt = calcInfo
+        srcHurtTotal = srcHurtTotal + hurtValue + immuneHurt
+        tagID = tagObj.GetID()
+        hurtValueDict[tagID] = hurtValue
+        if immuneHurt > 0:
+            immuneHurtDict[tagID] = immuneHurt
+            
+        if not tagFaction:
+            tagFaction = tagObj.GetFaction()
+            tagLineupNum = tagObj.GetLineupNum()
+            
+        # 不挡自己,不挡无敌,顺延直到一个可以挡的最低血量目标,如果没有就不挡
+        if checkCaoren and tagID != ChConfig.HeroID_Caoren and not immuneHurt and (not caorenProtectObj or tagObj.GetHP() < caorenProtectObj.GetHP()):
+            caorenProtectObj = tagObj
+            
+    isAve, caorenProtectID, otherFactionHurtDict = __calcFactionAverageHurt(turnFight, tagFaction, tagLineupNum, hurtValueDict, immuneHurtDict, caorenProtectObj)
+    if not isAve:
+        #GameWorld.DebugLog("没有分摊伤害原值返回")
+        return calcHurtResults
+    GameWorld.DebugLog("本阵营分摊伤害结果: %s,免疫伤害=%s,给敌阵营=%s,caorenProtectID=%s" 
+                       % (hurtValueDict, immuneHurtDict, otherFactionHurtDict, caorenProtectID))
+    
+    # 均摊给其他阵营的算非直接伤害,即曹仁无效
+    for key, otherHurtDict in otherFactionHurtDict.items():
+        aveFaction, aveLineupNum = key
+        isAveEx, _, otherFactionHurtDictEx = __calcFactionAverageHurt(turnFight, aveFaction, aveLineupNum, otherHurtDict, immuneHurtDict)
+        
+        # 其他阵营分摊后的最终伤害累加到实际最终伤害
+        for objID, aveHurt in otherHurtDict.items():
+            hurtValueDict[objID] = hurtValueDict.get(objID, 0) + aveHurt
+        GameWorld.DebugLog("其他阵营分摊后的最终伤害: isAveEx=%s,%s,免疫伤害=%s,给敌阵营=%s" 
+                           % (isAveEx, hurtValueDict, immuneHurtDict, otherFactionHurtDictEx))
+        
+        # 其他阵营再次分摊给其他阵营的,董白效果引起
+        # 董白暂时只能再次摊给源头一次伤害,在多V多场景下暂不做支持 A董白 摊 B董白 摊 C董白,目前只做 A->B->A
+        for keyEx, otherHurtDictEx in otherFactionHurtDictEx.items():
+            factionEx, lineupNumEx = keyEx
+            if factionEx != tagFaction or lineupNumEx != tagLineupNum:
+                continue
+            
+            for objID, aveHurt in otherHurtDictEx.items():
+                hurtValueDict[objID] = hurtValueDict.get(objID, 0) + aveHurt
+            GameWorld.DebugLog("其他阵营再分摊给源阵营的伤害: %s,最终伤害=%s,免疫伤害=%s" 
+                               % (otherHurtDictEx, hurtValueDict, immuneHurtDict))
+            
+    finalHurtTotal = 0
+    finalHurtResults = []
+    # 主要目标伤害更新
+    for calcInfo in calcHurtResults:
+        tagObj, _, hurtTypes, _ = calcInfo
+        tagID = tagObj.GetID()
+        finalHurt = int(hurtValueDict.pop(tagID, 0))
+        immuneHurt = max(int(immuneHurtDict.pop(tagID, 0)), 0) # 因为有标记-1值,所以加max
+        # 主要目标的免疫标记外层已经处理,仅额外处理标记曹仁防护
+        if caorenProtectID == tagID:
+            hurtTypes |= pow(2, ChConfig.HurtType_CaorenProtect) # 标记受到曹仁防护,前端表现需要
+        finalHurtResults.append([tagObj, finalHurt, hurtTypes, immuneHurt])
+        finalHurtTotal = finalHurtTotal + finalHurt + immuneHurt
+        GameWorld.DebugLog("得出主要目标分摊后的最终伤害: tagID=%s,finalHurt=%s,hurtTypes=%s,immuneHurt=%s,finalHurtTotal=%s" % (tagID, finalHurt, hurtTypes, immuneHurt, finalHurtTotal))
+        
+    # 剩下伤害则默认为额外目标伤害,放主要目标后面
+    isEx = 1
+    batObjMgr = BattleObj.GetBatObjMgr()
+    for tagID in set(hurtValueDict.keys() + immuneHurtDict.keys()):
+        tagObj = batObjMgr.getBatObj(tagID)
+        if not tagObj:
+            continue
+        hurtTypes = pow(2, ChConfig.HurtType_Normal)
+        finalHurt = int(hurtValueDict.pop(tagID, 0))
+        immuneHurt = max(int(immuneHurtDict.pop(tagID, 0)), 0)
+        if immuneHurt > 0:
+            hurtTypes |= pow(2, ChConfig.HurtType_Immune) # 添加免疫
+        finalHurtTotal = finalHurtTotal + finalHurt + immuneHurt
+        GameWorld.DebugLog("得出额外目标分摊后的最终伤害: tagID=%s,finalHurt=%s,hurtTypes=%s,immuneHurt=%s,finalHurtTotal=%s" % (tagID, finalHurt, hurtTypes, immuneHurt, finalHurtTotal))
+        if finalHurt > 0 or immuneHurt > 0: # 额外目标有受伤或免疫才算
+            finalHurtResults.append([tagObj, finalHurt, hurtTypes, immuneHurt, isEx])
+            
+    if finalHurtTotal != srcHurtTotal:
+        GameWorld.DebugLog("[分摊伤害后误差值]=%s,srcHurtTotal=%s,finalHurtTotal=%s" % (srcHurtTotal - finalHurtTotal, srcHurtTotal, finalHurtTotal))
+    return finalHurtResults
+
+def __calcFactionAverageHurt(turnFight, faction, lineupNum, hurtValueDict, immuneHurtDict, caorenProtectObj=None):
+    '''计算某个阵营的伤害平摊,每个阵营独立计算即可,减少复杂度
+        优先级: 甄宓 > 曹仁 > 董白;
+        减少复杂度,固定把董白最后算,这样每个阵营的伤害单独计算均摊即可
+        @param hurtValueDict: 目标伤害字典
+        @param immuneHurtDict: 目前最终免疫伤害字典
+        @param caorenProtectObj: 曹仁防护对象,可能为 None 或 0 或 某一个对象
+    '''
+    
+    # 甄宓    潜能1    织命    战斗开始,甄宓链接全体队友,使任意队友受到的伤害由全体均摊,持续2回合
+    # 曹仁的分担触发条件暂时程序内固定,有修改时程序同步修改
+    # 曹仁: 我方英雄受到直接攻击时(受到群攻时选择血量最低的友方进行防护),曹仁有30%概率为其分担一半所受伤害,每成功分担一次,曹仁获得1层坚韧,使我方全体减伤提升5%,最多6层,曹仁死亡效果消失
+    # 董白怒气    同命锁    对敌方攻击最高的1个目标造成攻击力450%物理伤害,并与其连接,使两人受到的任意伤害平摊(平摊伤害不会被再次平摊),持续2回合
+    caorenSkillID = 1015070
+    effHeroIDList = [ChConfig.HeroID_Zhenfu, ChConfig.HeroID_Caoren, ChConfig.HeroID_Dongbai] # 暂固定,有增加武将再同步修改
+    
+    isAve = False
+    caorenProtectID = 0 # 曹仁成功防护的对象ID,有值代表有防护
+    otherFactionHurtDict = {} # 分摊给其他阵营的伤害 {(faction, lineupNum):{objID:aveHurt, ...}, ...}
+    batFaction = turnFight.getBatFaction(faction)
+    batLineup = batFaction.getBatlineup(lineupNum)
+    batObjMgr = BattleObj.GetBatObjMgr()
+    GameWorld.DebugLog("----开始计算阵营分摊伤害: faction=%s,lineupNum=%s,hurtValueDict=%s,免疫伤害=%s" 
+                       % (faction, lineupNum, hurtValueDict, immuneHurtDict))
+    # 按优先级顺序处理拥有分摊效果的武将
+    for effHeroID in effHeroIDList:
+        if effHeroID not in batLineup.heroObjIDDict:
+            continue
+        objID = batLineup.heroObjIDDict[effHeroID]
+        batObj = batObjMgr.getBatObj(objID)
+        if not batObj or not batObj.IsAlive():
+            continue
+        buffMgr = batObj.GetBuffManager()
+        
+        inHurt = objID in hurtValueDict # 光环里的人员是否有受伤
+        aveBuff = None
+        aveObjList = [] # 可均摊伤害的对象列表
+        if effHeroID == ChConfig.HeroID_Caoren:
+            if not caorenProtectObj:
+                GameWorld.DebugLog("没有曹仁可防护的对象")
+                continue
+            hpLowestID = caorenProtectObj.GetID()
+            if hpLowestID == objID:
+                GameWorld.DebugLog("曹仁不防护自己")
+                continue
+            if not inHurt and hpLowestID not in hurtValueDict:
+                GameWorld.DebugLog("曹仁及防护对象都不在受伤列表里不处理! hpLowestID=%s" % hpLowestID)
+                continue
+            if batObj.IsInControlledHard():
+                GameWorld.DebugLog("曹仁被硬控中不防护")
+                continue
+            caoRenSkill = batObj.GetSkillManager().FindSkillByID(caorenSkillID)
+            if not caoRenSkill:
+                GameWorld.DebugLog("曹仁还未学习分担伤害技能! caorenSkillID=%s" % caorenSkillID)
+                continue
+            if not GameWorld.CanHappen(caoRenSkill.GetHappenRate()):
+                GameWorld.DebugLog("曹仁概率未触发防护")
+                continue
+            inHurt = True
+            aveObjList = [batObj, caorenProtectObj]
+            GameWorld.DebugLog("曹仁触发防护! hpLowestID=%s" % hpLowestID)
+            
+        elif effHeroID == ChConfig.HeroID_Zhenfu:
+            aveBuff = buffMgr.FindBuffByState(ChConfig.BatObjState_Zhiming)
+            
+        elif effHeroID == ChConfig.HeroID_Dongbai:
+            aveBuff = buffMgr.FindBuffByState(ChConfig.BatObjState_Link)
+            
+        else:
+            continue
+        
+        if not aveObjList: # 没有指定均摊伤害的,根据均摊光环buff找
+            if not aveBuff:
+                GameWorld.DebugLog("没有分摊伤害光环! effHeroID=%s" % (effHeroID))
+                continue
+            haloObjIDList = aveBuff.GetHaloObjIDList()
+            GameWorld.DebugLog("[分摊光环buff] effHeroID=%s,skillID=%s,objID=%s,haloObjIDList=%s" % (effHeroID, aveBuff.GetSkillID(), objID, haloObjIDList))
+            for haloObjID in haloObjIDList:
+                haloObj = batObjMgr.getBatObj(haloObjID)
+                if not haloObj or not haloObj.IsAlive():
+                    continue
+                aveObjList.append(haloObj)
+                if not inHurt and haloObjID in hurtValueDict:
+                    inHurt = True
+                    
+        if not inHurt:
+            GameWorld.DebugLog("相关人员没有受伤,不处理分摊伤害! effHeroID=%s" % effHeroID)
+            continue
+        
+        aveObjCnt = len(aveObjList)
+        if aveObjCnt < 2:
+            GameWorld.DebugLog("相关人员少于2个时自行承担所有伤害! effHeroID=%s,aveObjCnt=%s" % (effHeroID, aveObjCnt))
+            continue
+        
+        if not isAve:
+            isAve = True
+            
+        if effHeroID == ChConfig.HeroID_Caoren and caorenProtectObj:
+            caorenProtectID = caorenProtectObj.GetID()
+            GameWorld.DebugLog("[曹仁防护目标] caorenProtectID=%s,caorenObjID=%s" % (caorenProtectID, objID))
+            
+        aveHurtTotal = 0
+        for aveObj in aveObjList:
+            aveObjID = aveObj.GetID()
+            if effHeroID == ChConfig.HeroID_Caoren and aveObjID == objID:
+                GameWorld.DebugLog("    曹仁的效果不平摊曹仁自身的伤害,只是曹仁帮别人摊! aveObjID=%s" % aveObjID)
+                continue
+            if aveObjID not in hurtValueDict:
+                continue
+            hurtValue = hurtValueDict.pop(aveObjID, 0) # 被分摊的伤害要移除
+            if hurtValue <= 0:
+                GameWorld.DebugLog("    无伤害的不处理:可能是非主要目标或自身已经无敌免疫了伤害! aveObjID=%s" % aveObjID)
+                continue
+            aveHurtTotal += hurtValue
+            GameWorld.DebugLog("    累计待分摊伤害! effHeroID=%s,aveObjID=%s,hurtValue=%s,aveHurtTotal=%s" % (effHeroID, aveObjID, hurtValue, aveHurtTotal))
+            
+        # 大家分摊
+        aveHurtDict = {} # 本阵营该分摊效果分摊伤害记录
+        aveHurt = round(aveHurtTotal / float(aveObjCnt), 2)
+        GameWorld.DebugLog("计算分摊伤害! effHeroID=%s,aveHurtTotal=%s,aveObjCnt=%s,aveHurt=%s" % (effHeroID, aveHurtTotal, aveObjCnt, aveHurt))
+        for aveObj in aveObjList:
+            aveObjID = aveObj.GetID()
+            aveFaction = aveObj.GetFaction()
+            
+            immuneHurt = immuneHurtDict.get(aveObjID)
+            if immuneHurt == None:
+                if aveObj.CheckInState(ChConfig.BatObjState_Wudi):
+                    immuneHurt = 0
+                else:
+                    immuneHurt = -1 # 标记没有无敌
+                immuneHurtDict[aveObjID] = immuneHurt
+                
+            if immuneHurt >= 0:
+                immuneHurtDict[aveObjID] = immuneHurt + aveHurt
+                GameWorld.DebugLog("    累加免疫分摊伤害! aveObjID=%s,免疫伤害=%s" % (aveObjID, immuneHurtDict))
+                continue
+            
+            if aveFaction == faction:
+                aveHurtDict[aveObjID] = aveHurtDict.get(aveObjID, 0) + aveHurt
+                GameWorld.DebugLog("    累加本效果本阵营分摊伤害! aveObjID=%s,aveHurtDict=%s" % (aveObjID, aveHurtDict))
+            else:
+                aveLineupNum = aveObj.GetLineupNum()
+                key = (aveFaction, aveLineupNum)
+                if key not in otherFactionHurtDict:
+                    otherFactionHurtDict[key] = {}
+                otherHurtDict = otherFactionHurtDict[key]
+                otherHurtDict[aveObjID] = otherHurtDict.get(aveObjID, 0) + aveHurt
+                GameWorld.DebugLog("    累加本效果敌阵营分摊伤害! aveObjID=%s,factionKey=%s,otherHurtDict=%s" % (aveObjID, key, otherHurtDict))
+                
+        # 汇总最新伤害
+        for aveObjID, aveHurt in aveHurtDict.items():
+            hurtValueDict[aveObjID] = hurtValueDict.get(aveObjID, 0) + aveHurt
+        GameWorld.DebugLog("    更新本效果分摊后的伤害! %s,免疫伤害=%s,给敌阵营=%s" % (hurtValueDict, immuneHurtDict, otherFactionHurtDict))
+        
+    return isAve, caorenProtectID, otherFactionHurtDict
+
+def DoLostHP(turnFight, atkObj, defObj, hurtValue, curSkill, lostType="", hpCanNegative=False, immuneHurt=0):
+    '''仅执行掉血逻辑: 承伤盾抵扣  + 剩余扣血
+    @param hurtValue: 掉血伤害值,一般是各种计算后的最终伤害值
+    @param hpCanNegative: 扣除后的生命是否允许负值
+    @param immuneHurt: 免疫的伤害值
+    @param isSkillSelfTag: 是否技能自身的直接目标,如平摊伤害目标、溅射伤害目标这种就不算直接目标
+    @return: lostHP
+    '''
+    
+    if immuneHurt > 0:
+        buffMgr = defObj.GetBuffManager()
+        wudiBuffList = buffMgr.FindBuffListByState(ChConfig.BatObjState_Wudi)
         for buff in wudiBuffList:
-            skillData = buff.GetSkillData()
+            buffSkillID = buff.GetSkillID()
             # 记录免疫积攒的伤害
             buffValue = buff.GetValue1() + buff.GetValue2() * ChConfig.Def_PerPointValue
-            updBuffValue = buffValue + hurtValue
+            updBuffValue = buffValue + immuneHurt
             buff.SetValue1(updBuffValue % ChConfig.Def_PerPointValue)
             buff.SetValue2(updBuffValue / ChConfig.Def_PerPointValue)
-            GameWorld.DebugLog("    无敌盾免疫伤害: defID=%s,buffID=%s,skillID=%s,updBuffValue=%s" 
-                               % (defObj.GetID(), buff.GetBuffID(), skillData.GetSkillID(), updBuffValue))
-        return 0, 0, hurtTypes
+            GameWorld.DebugLog("    无敌盾累计免疫伤害: defID=%s,buffID=%s,buffSkillID=%s,buffValue=%s,immuneHurt=%s,updBuffValue=%s,lostType=%s" 
+                               % (defObj.GetID(), buff.GetBuffID(), buffSkillID, buffValue, immuneHurt, updBuffValue, lostType))
+            
+    if hurtValue <= 0:
+        return 0
     
-    # 减伤盾减伤, 会改变 hurtValue
-    hurtValue = max(0, hurtValue)
+    atkID = atkObj.GetID()
+    defID = defObj.GetID()
+    skillID = curSkill.GetSkillID()
     
-    # 承伤盾承伤,剩余时间短的优先承伤,承伤不影响输出,相当于额外的HP,仅用于抵扣掉血,仅 改变 realHurtHP
-    realHurtHP = hurtValue
+    # 承伤盾承伤,剩余时间短的优先承伤,承伤不影响输出,相当于额外的HP,仅用于抵扣掉血,仅 改变 lostHP
+    lostHP = hurtValue
     shieldBuffList = []
+    buffMgr = defObj.GetBuffManager()
     for buff in buffMgr.FindBuffListByState(ChConfig.BatObjState_DamShield):
         skillData = buff.GetSkillData()
         remainTime = buff.GetRemainTime() # 剩余回合
@@ -1834,9 +2231,20 @@
                 buff.SetValue2(updShieldValue / ChConfig.Def_PerPointValue)
                 curSkill.AddAfterLogic(ChConfig.AfterLogic_SyncBuff, [defObj, buff, atkObj, "ReduceShieldValue"])
                 
-        realHurtHP = int(shieldHurtValue / shieldHurtPer)
-        GameWorld.DebugLog("        承伤后剩余伤血: realHurtHP=%s" % (realHurtHP))
-    return hurtValue, max(0, realHurtHP), hurtTypes
+        lostHP = int(shieldHurtValue / shieldHurtPer)
+        GameWorld.DebugLog("        承伤后剩余伤血: lostHP=%s" % (lostHP))
+        
+    dHP = defObj.GetHP()
+    remainHP = dHP - lostHP # 剩余血量
+    if not hpCanNegative and remainHP < 0:
+        remainHP = 0
+    lostHP = dHP - remainHP # 实际掉血量
+    defObj.SetHP(remainHP)
+    
+    GameWorld.DebugLog("    扣血: atkID=%s,defID=%s,hurtValue=%s,lostType=%s,lostHP=%s,dHP=%s,updHP=%s/%s" 
+                       % (atkID, defID, hurtValue, lostType, lostHP, dHP, defObj.GetHP(), defObj.GetMaxHP()))
+    TurnAttack.AddTurnObjHurtValue(atkObj, defObj, hurtValue, lostHP, skillID, lostType)
+    return lostHP
 
 def CalcBounceHP(turnFight, atkObj, defObj, hurtObj, curSkill):
     '''计算反弹反弹伤害
@@ -1858,18 +2266,12 @@
         return
     
     GameWorld.DebugLog("    反弹伤害=%s,%s/%s, damBackPer=%s" % (bounceHP, atkObj.GetHP(), atkObj.GetMaxHP(), damBackPer))
-    bounceHP, realBounceHP, _ = CalcHurtHPWithBuff(turnFight, defObj, atkObj, curSkill, bounceHP)
+    bounceHP, _, immuneHurt = CalcHurtWithBuff(turnFight, atkObj, defObj, bounceHP)
     if bounceHP <= 0:
         GameWorld.DebugLog("        bounceHP=%s" % (bounceHP))
         return
     hurtObj.SetBounceHP(bounceHP)
-    
-    if realBounceHP > 0:
-        remainHP = atkObj.GetHP() - realBounceHP # 反弹允许弹到负值,如果最终吸血没有吸到正值则算死亡
-        atkObj.SetHP(remainHP)
-        GameWorld.DebugLog("        bounceHP=%s,realBounceHP=%s,%s/%s" % (bounceHP, realBounceHP, atkObj.GetHP(), atkObj.GetMaxHP()))
-        
-    TurnAttack.AddTurnObjHurtValue(defObj, atkObj, bounceHP, realBounceHP, isBounce=True)
+    DoLostHP(turnFight, atkObj, defObj, bounceHP, curSkill, "Bounce", hpCanNegative=True, immuneHurt=immuneHurt) # 反弹后生命允许负值
     return
 
 def CalcSuckBlood(turnFight, atkObj, defObj, hurtObj, curSkill):
@@ -1886,7 +2288,17 @@
     if suckHP <= 0:
         return
     
-    if DoPoisonCure(turnFight, atkObj, suckHP, hurtObj, curSkill, True):
+    # 吸血毒奶
+    poisonCureOwner = GetPoisonCureOwner(atkObj)
+    if poisonCureOwner:
+        atkObj, defObj = poisonCureOwner, atkObj
+        hurtValue = suckHP
+        hurtValue, _, immuneHurt = CalcHurtWithBuff(turnFight, atkObj, defObj, hurtValue)
+        GameWorld.DebugLog("    本次吸血为毒奶: suckHP=%s,hurtValue=%s" % (suckHP, hurtValue))
+        DoLostHP(turnFight, atkObj, defObj, hurtValue, curSkill, "PoisonSuckBlood", hpCanNegative=True, immuneHurt=immuneHurt) # 吸血毒奶生命允许负值
+        
+        hurtObj.AddHurtType(ChConfig.HurtType_PoisonCureSuck)
+        hurtObj.SetSuckHP(hurtValue) # 如果前端有需要表现免疫吸血毒奶,可以判断吸血毒奶下该值为0代表免疫
         return
     
     curHP = atkObj.GetHP()
@@ -1902,40 +2314,7 @@
     TurnAttack.AddTurnObjCureHP(atkObj, atkObj, suckHP, cureHP)
     return
 
-def DoPoisonCure(turnFight, curObj, cureHP, hurtObj, curSkill, isSuck=False):
-    ## 毒奶逻辑
-    buffMgr = curObj.GetBuffManager()
-    poisonCureBuff = buffMgr.FindBuffByState(ChConfig.BatObjState_PoisonCure) # 毒奶暂定唯一
-    if not poisonCureBuff:
-        return
-    ownerID = poisonCureBuff.GetOwnerID()
-    buffOwner = BattleObj.GetBatObjMgr().getBatObj(ownerID) # 攻击方
-    if not buffOwner:
-        return
-    atkObj, defObj = buffOwner, curObj
-    
-    hurtTypes = hurtObj.GetHurtTypes()
-    hurtValue, realHurtHP, hurtTypes = CalcHurtHPWithBuff(turnFight, atkObj, defObj, curSkill, cureHP, hurtTypes)
-    
-    dHP = defObj.GetHP()
-    remainHP = max(0, dHP - realHurtHP) # 剩余血量
-    lostHP = dHP - remainHP # 实际掉血量
-    defObj.SetHP(remainHP)
-    GameWorld.DebugLog("    毒奶: hurtValue=%s,realHurtHP=%s,lostHP=%s,%s/%s,isSuck=%s" % (hurtValue, realHurtHP, lostHP, defObj.GetHP(), defObj.GetMaxHP(), isSuck))
-    
-    if isSuck:
-        hurtTypes |= pow(2, ChConfig.HurtType_PoisonCureSuck)
-        hurtObj.SetSuckHP(hurtValue)
-    else:
-        hurtTypes |= pow(2, ChConfig.HurtType_PoisonCureHurt)
-        hurtObj.SetHurtHP(hurtValue)
-        hurtObj.SetLostHP(lostHP)
-    hurtObj.SetHurtTypes(hurtTypes)
-    
-    TurnAttack.AddTurnObjHurtValue(atkObj, defObj, hurtValue, lostHP, curSkill.GetSkillID())
-    return True
-
-def CalcCureHP(turnFight, userObj, tagObj, curSkill, largeNum=False, relativeObj=None):
+def CalcCureHP(turnFight, userObj, tagObj, curSkill, relativeObj=None):
     ''' 计算治疗值
     '''
     
@@ -1943,34 +2322,13 @@
     if not relativeObj:
         relativeObj = GetRelativeObj(turnFight, userObj)
         
+    skillID = curSkill.GetSkillID()
     cureType = curSkill.GetCalcType()
     skillPer = curSkill.GetSkillPer()
     #skillValue = curSkill.GetSkillValue()
     
     skillPer += GetAddSkillPer(turnFight, userObj, tagObj, curSkill)
     
-    cureHP = __calcCureHP(turnFight, userObj, tagObj, relativeObj, curSkill, cureType, skillPer, largeNum)
-    
-    cureWayExEff = curSkill.GetEffectByID(ChConfig.SkillEff_CureWayEx)
-    if cureWayExEff:
-        cureType = cureWayExEff.GetEffectValue(0)
-        skillPer = cureWayExEff.GetEffectValue(1)
-        GameWorld.DebugLog("额外计算治疗方式: cureType=%s,skillPer=%s" % (cureType, skillPer))
-        cureHPEx = __calcCureHP(turnFight, userObj, tagObj, relativeObj, curSkill, cureType, skillPer, largeNum)
-        cureHP += cureHPEx
-        
-    hurtShareEff = curSkill.GetEffectByID(ChConfig.SkillEff_HurtShare)
-    if hurtShareEff:
-        tagCnt = max(1, len(curSkill.GetTagObjList()))
-        cureHP = cureHP / tagCnt
-        GameWorld.DebugLog("    目标均摊治疗: cureHP=%s,tagCnt=%s" % (cureHP, tagCnt))
-        
-    return max(1, int(cureHP)) # 保底1点
-
-def __calcCureHP(turnFight, userObj, tagObj, relativeObj, curSkill, cureType, skillPer, largeNum=False):
-    cureBaseValue = GetCalcBaseValue(cureType, userObj, tagObj, curSkill)
-    
-    # 回合制
     curePer = userObj.GetBatAttrValue(ChConfig.AttrID_CurePer) # 治疗加成
     cureDefPer = relativeObj.GetBatAttrValue(ChConfig.AttrID_CurePerDef) # 对位的弱化治疗
     angerOverflow = 0 # 怒气溢出值
@@ -1981,16 +2339,31 @@
     curePer /= float(ChConfig.Def_MaxRateValue)
     cureDefPer /= float(ChConfig.Def_MaxRateValue)
     
-    baseValue = max(0, cureBaseValue) # 防止基值被弱化为负值,在恢复比例也是负值的情况下负负得正导致可以恢复血量
+    baseValue = GetCalcBaseValue(cureType, userObj, tagObj, curSkill)
     
-    #公式计算治疗值 
     cureHP = eval(IpyGameDataPY.GetFuncCompileCfg("CureFormula", 1))
-    if not largeNum:
-        cureHP = min(cureHP, ChConfig.Def_UpperLimit_DWord)
-    cureHP = max(1, int(cureHP)) # 保底1点
-    
     GameWorld.DebugLog("计算治疗值(%s):skillID=%s,cureType=%s,baseValue=%s,skillPer=%s,curePer=%s,cureDefPer=%s,angerOverflow=%s" 
-                       % (cureHP, curSkill.GetSkillID(), cureType, baseValue, skillPer, curePer, cureDefPer, angerOverflow))
+                       % (cureHP, skillID, cureType, baseValue, skillPer, curePer, cureDefPer, angerOverflow))
+    
+    # 额外治疗值
+    cureWayExEff = curSkill.GetEffectByID(ChConfig.SkillEff_CureWayEx)
+    if cureWayExEff:
+        cureType = cureWayExEff.GetEffectValue(0)
+        skillPer = cureWayExEff.GetEffectValue(1)
+        baseValue = GetCalcBaseValue(cureType, userObj, tagObj, curSkill)
+        cureHPEx = eval(IpyGameDataPY.GetFuncCompileCfg("CureFormula", 1))
+        cureHP += cureHPEx
+        GameWorld.DebugLog("    额外治疗值(%s): cureType=%s,skillPer=%s,cureHP=%s" % (cureHPEx, cureType, skillPer, cureHP))
+        
+    hurtShareEff = curSkill.GetEffectByID(ChConfig.SkillEff_HurtShare)
+    if hurtShareEff:
+        tagCnt = max(1, len(curSkill.GetTagObjList()))
+        cureHP = cureHP / tagCnt
+        GameWorld.DebugLog("    目标均摊治疗: cureHP=%s,tagCnt=%s" % (cureHP, tagCnt))
+        
+    if cureHP < 1:
+        cureHP = 1
+        GameWorld.DebugLog("    保底治疗值: cureHP=%s" % (cureHP))
     return cureHP
 
 def GetCalcBaseValue(calcType, curObj, tagObj, curSkill):
@@ -2020,12 +2393,12 @@
         if bySkill:
             bySkillID = bySkill.GetSkillID()
             curID = curObj.GetID()
-            for hurtObj in bySkill.GetHurtObjList():
+            for hurtObj in bySkill.GetHurtObjListAll():
                 if curID == hurtObj.GetObjID():
                     baseValue += hurtObj.GetHurtHP()
         GameWorld.DebugLog("根据最后受击技能伤害: %s,bySkillID=%s" % (baseValue, bySkillID))
         
-    return baseValue
+    return max(0, baseValue)
 
 def DoDOTAttack(turnFight, batObj, curBuff, hurtValue, hurtTypes, **kwargs):
     ## 执行单次dot逻辑
@@ -2044,17 +2417,15 @@
     
     atkID = ownerID
     defID = defObj.GetID()
-    tagObjList = [defObj]
     
-    poolMgr = ObjPool.GetPoolMgr()
-    useSkill = poolMgr.acquire(BattleObj.PySkill, skillIpyData, atkID)
-    useSkill.SetTagObjList(tagObjList)
-    useSkill.ClearHurtObj()
-    hurtObj = useSkill.AddHurtObj(defID)
+    # 结算需要同步标签
+    useTag = "Skill_%s_%s_Dot" % (atkID, skillID)
+    Sync_TurnFightTag(turnFight, useTag, 0)
     
     dHP = defObj.GetHP()
-    GameWorld.DebugLog("结算dot: atkID=%s,defID=%s,buffID=%s,skillID=%s,ownerID=%s,hurtValue=%s,hurtTypes=%s,dHP=%s" 
-                       % (atkID, defID, buffID, skillID, ownerID, hurtValue, hurtTypes, dHP))
+    dMaxHP = defObj.GetMaxHP()
+    GameWorld.DebugLog("结算dot: atkID=%s,defID=%s,buffID=%s,skillID=%s,ownerID=%s,hurtValue=%s,hurtTypes=%s,dHP=%s/%s" 
+                       % (atkID, defID, buffID, skillID, ownerID, hurtValue, hurtTypes, dHP, dMaxHP))
     layer = curBuff.GetLayer()
     if layer > 0:
         hurtValue *= layer
@@ -2063,27 +2434,34 @@
         FinalDamPer = kwargs["FinalDamPer"]
         hurtValue *= (10000 + FinalDamPer) / 10000.0
         GameWorld.DebugLog("    增伤: hurtValue=%s,FinalDamPer=%s" % (hurtValue, FinalDamPer))
-    hurtValue, realHurtHP, hurtTypes = CalcHurtHPWithBuff(turnFight, atkObj, defObj, useSkill, hurtValue, hurtTypes)
+        
+    poolMgr = ObjPool.GetPoolMgr()
+    useSkill = poolMgr.acquire(BattleObj.PySkill, skillIpyData, atkID)
+    useSkill.SetTagObjList([defObj])
+    useSkill.SetBatType(ChConfig.TurnBattleType_Dot)
     
-    remainHP = max(0, dHP - realHurtHP) # 剩余血量
-    lostHP = dHP - remainHP # 实际掉血量
-    defObj.SetHP(remainHP)
-    GameWorld.DebugLog("    hurtValue=%s,realHurtHP=%s,lostHP=%s,%s/%s" % (hurtValue, realHurtHP, lostHP, defObj.GetHP(), defObj.GetMaxHP()))
+    hurtValue, hurtTypes, immuneHurt = CalcHurtWithBuff(turnFight, atkObj, defObj, hurtValue, hurtTypes)
+    calcHurtResults = [[defObj, hurtValue, hurtTypes, immuneHurt]]
+    DoSkillHurtHP(turnFight, atkObj, useSkill, calcHurtResults, "DOT")
     
-    hurtObj.SetHurtTypes(hurtTypes)
-    hurtObj.SetHurtHP(hurtValue)
-    hurtObj.SetRealHurtHP(realHurtHP)
-    hurtObj.SetLostHP(lostHP)
-    hurtObj.SetCurHP(remainHP)
-    
-    TurnAttack.AddTurnObjHurtValue(atkObj, batObj, hurtValue, lostHP, skillID)
-    
-    Sync_PropertyRefreshView(turnFight, defObj, ChConfig.AttrID_HP, remainHP, hurtValue, diffType=0, skillID=skillID, hurtTypes=hurtTypes)
+    Sync_UseSkill(turnFight, atkObj, useSkill)
     
     DoBeAttackResult(turnFight, atkObj, useSkill)
     
+    # 通知结束标签
+    Sync_TurnFightTag(turnFight, useTag, 1)
+    
     useSkill.ResetUseRec()
     poolMgr.release(useSkill)
+    return
+
+def Sync_TurnFightTag(turnFight, useTag, sign):
+    ## 通知技能标签
+    clientPack = ObjPool.GetPoolMgr().acquire(ChPyNetSendPack.tagSCTurnFightTag)
+    clientPack.Tag = useTag
+    clientPack.Len = len(clientPack.Tag)
+    clientPack.Sign = sign
+    turnFight.addBatPack(clientPack)
     return
 
 def Sync_UseSkill(turnFight, curBatObj, useSkill):
@@ -2099,6 +2477,7 @@
     clientPack.CurHPEx = curBatObj.GetHP() / ChConfig.Def_PerPointValue
     clientPack.SkillID = useSkill.GetSkillID()
     clientPack.RelatedSkillID = relatedSkillID
+    # 技能的主要伤害
     clientPack.HurtList = []
     for hurtObj in useSkill.GetHurtObjList():
         hurt = poolMgr.acquire(ChPyNetSendPack.tagSCUseSkillHurt)
@@ -2110,7 +2489,36 @@
         hurt.CurHPEx = hurtObj.GetCurHP() / ChConfig.Def_PerPointValue
         hurt.SuckHP = min(hurtObj.GetSuckHP(), ChConfig.Def_UpperLimit_DWord)
         hurt.BounceHP = min(hurtObj.GetBounceHP(), ChConfig.Def_UpperLimit_DWord)
+        hurt.HurtListEx = []
+        # 单个伤害的额外伤害,如弹射+平摊伤害
+        for hurtObjEx in hurtObj.GetHurtObjListEx():
+            hurtEx = poolMgr.acquire(ChPyNetSendPack.tagSCUseSkillHurtEx)
+            hurtEx.ObjID = hurtObjEx.GetObjID()
+            hurtEx.AttackTypes = hurtObjEx.GetHurtTypes()
+            hurtEx.HurtHP = hurtObjEx.GetHurtHP() % ChConfig.Def_PerPointValue
+            hurtEx.HurtHPEx = hurtObjEx.GetHurtHP() / ChConfig.Def_PerPointValue
+            hurtEx.CurHP = hurtObjEx.GetCurHP() % ChConfig.Def_PerPointValue
+            hurtEx.CurHPEx = hurtObjEx.GetCurHP() / ChConfig.Def_PerPointValue
+            hurtEx.SuckHP = min(hurtObjEx.GetSuckHP(), ChConfig.Def_UpperLimit_DWord)
+            hurt.HurtListEx.append(hurtEx)
+        hurt.HurtCountEx = len(hurt.HurtListEx)
         clientPack.HurtList.append(hurt)
+    
+    # 技能的额外伤害,如常规技能的平摊伤害
+    clientPack.HurtListEx = []
+    for hurtObjEx in useSkill.GetHurtObjListEx():
+        hurtEx = poolMgr.acquire(ChPyNetSendPack.tagSCUseSkillHurtEx)
+        hurtEx.ObjID = hurtObjEx.GetObjID()
+        hurtEx.AttackTypes = hurtObjEx.GetHurtTypes()
+        hurtEx.HurtHP = hurtObjEx.GetHurtHP() % ChConfig.Def_PerPointValue
+        hurtEx.HurtHPEx = hurtObjEx.GetHurtHP() / ChConfig.Def_PerPointValue
+        hurtEx.CurHP = hurtObjEx.GetCurHP() % ChConfig.Def_PerPointValue
+        hurtEx.CurHPEx = hurtObjEx.GetCurHP() / ChConfig.Def_PerPointValue
+        hurtEx.SuckHP = min(hurtObjEx.GetSuckHP(), ChConfig.Def_UpperLimit_DWord)
+        clientPack.HurtListEx.append(hurtEx)
+    clientPack.HurtCountEx = len(clientPack.HurtListEx)
+    
+    # 没有主要伤害时视为没有伤害,如纯buff的技能,需要通知目标给前端
     if not clientPack.HurtList:
         for tagObj in useSkill.GetTagObjList():
             tagID = tagObj.GetID()

--
Gitblit v1.8.0