yyl
2025-11-26 5ae116b703d6a87042b9632844c082c3328b5799
125 战斗 血量问题 修复初版 (hurtex 未处理)
9个文件已修改
1169 ■■■■ 已修改文件
Main/Component/UI/Common/BossLifeBar.cs 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Core/NetworkPackage/DTCFile/ServerPack/HB4_FightDefine/DTCB430_tagSCTurnFightReport.cs 96 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/BattleField/BattleField.cs 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/BattleObject/BattleObject.cs 106 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/BattleUtility.cs 305 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/Define/BattleDmgInfo.cs 215 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/Skill/SkillBase.cs 326 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/StoryBossBattleWin.cs 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/UIComp/BattleHeroInfoBar.cs 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Component/UI/Common/BossLifeBar.cs
@@ -83,6 +83,8 @@
        // 立刻显示基准百分比(使用 percentage)
        m_SurplusPercent.text = (percentage * 100f).ToString("F2") + "%";
        // Debug.LogError("BossLifeBar SetBaseInfo: totalSegments=" + totalSegments + ", hp=" + _hp + ", maxHp=" + _maxHp + ", percentage=" + percentage);
    }
    public void Show(ulong _hp, ulong _maxHp)
@@ -99,6 +101,7 @@
        // 立即更新百分比显示(直接使用 percentage)
        m_SurplusPercent.text = (percentage * 100f).ToString("F2") + "%";
        // Debug.LogError("BossLifeBar Show: totalSegments=" + totalSegments + ", hp=" + _hp + ", maxHp=" + _maxHp + ", percentage=" + percentage);
    }
    private void LateUpdate()
Main/Core/NetworkPackage/DTCFile/ServerPack/HB4_FightDefine/DTCB430_tagSCTurnFightReport.cs
@@ -86,6 +86,38 @@
            vPackList = AnalysisPackQueueAndDistribute(guid, vPackList);
#if UNITY_EDITOR
if (Launch.Instance.isOpenSkillLogFile)
{
string packDetail = "AnalysisPackQueueAndDistribute 处理后的包列表:\n";
packDetail += PrintPackListDetail(vPackList, 0);
// 分段打印,避免消息被截断
int chunkSize = 15000; // Unity日志单条消息最大长度约16000字符
if (packDetail.Length > chunkSize)
{
    int totalChunks = (packDetail.Length + chunkSize - 1) / chunkSize;
    for (int i = 0; i < totalChunks; i++)
    {
        int startIndex = i * chunkSize;
        int length = Mathf.Min(chunkSize, packDetail.Length - startIndex);
        string chunk = packDetail.Substring(startIndex, length);
        Debug.LogError($"[Part {i + 1}/{totalChunks}]\n{chunk}");
    }
}
else
{
    Debug.LogError(packDetail);
}
// 或者写入文件
string filePath = Application.dataPath + "/../BattleReport/PackageAnalysis_" + DateTime.Now.ToString("yyyyMMdd_HHmmss") + ".txt";
System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(filePath));
System.IO.File.WriteAllText(filePath, packDetail);
Debug.LogError("包分析结果已保存到: " + filePath);
}
#endif
            for (int i = 0; i < vPackList.Count; i++)
            {
                BattleManager.Instance.PushPackage(guid, vPackList[i]);
@@ -99,6 +131,70 @@
        }
    }
    private string PrintPackListDetail(List<GameNetPackBasic> packList, int indent)
    {
        string result = string.Empty;
        string indentStr = new string(' ', indent * 2);
        for (int i = 0; i < packList.Count; i++)
        {
            var pack = packList[i];
            if (pack is HB427_tagSCUseSkill skill)
            {
                result += $"{indentStr}[{i}] HB427_tagSCUseSkill - ObjID:{skill.ObjID} SkillID:{skill.SkillID} RelatedSkillID:{skill.RelatedSkillID} BattleType:{skill.BattleType} UID:{skill.packUID}\n";
                // 打印子技能列表
                if (skill.subSkillList != null && skill.subSkillList.Count > 0)
                {
                    result += $"{indentStr}  SubSkills ({skill.subSkillList.Count}):\n";
                    int j = 0;
                    foreach (var subSkill in skill.subSkillList)
                    {
                        result += $"{indentStr}    [{j}] SubSkill - SkillID:{subSkill.SkillID} RelatedSkillID:{subSkill.RelatedSkillID}\n";
                        j++;
                    }
                }
                // 打印子技能CombinePack列表
                if (skill.subSkillCombinePackList != null && skill.subSkillCombinePackList.Count > 0)
                {
                    result += $"{indentStr}  SubSkillCombinePacks ({skill.subSkillCombinePackList.Count}):\n";
                    int j = 0;
                    foreach (var subCombinePack in skill.subSkillCombinePackList)
                    {
                        var subMainSkill = subCombinePack.GetMainHB427SkillPack();
                        result += $"{indentStr}    [{j}] SubCombinePack - Tag:{subCombinePack.startTag?.Tag} MainSkill:{subMainSkill?.SkillID} PackCount:{subCombinePack.packList.Count}\n";
                        // 递归打印子CombinePack内部
                        if (subCombinePack.packList.Count > 0)
                        {
                            result += PrintPackListDetail(subCombinePack.packList, indent + 3);
                        }
                        j++;
                    }
                }
            }
            else if (pack is CustomHB426CombinePack combinePack)
            {
                var mainSkill = combinePack.GetMainHB427SkillPack();
                result += $"{indentStr}[{i}] CustomHB426CombinePack - Tag:{combinePack.startTag?.Tag} MainSkill:{mainSkill?.SkillID} PackCount:{combinePack.packList.Count}\n";
                // 递归打印内部包
                if (combinePack.packList.Count > 0)
                {
                    result += PrintPackListDetail(combinePack.packList, indent + 1);
                }
            }
            else
            {
                result += $"{indentStr}[{i}] {pack.GetType().Name} UID:{pack.packUID}\n";
            }
        }
        return result;
    }
    //约定第一个包是B424,先发过来的过滤报错通知
    bool FilterBeforeB424(GameNetPackBasic npk)
    {
Main/System/Battle/BattleField/BattleField.cs
@@ -3,6 +3,7 @@
using System;
using LitJson;
using DG.Tweening;
using System.IO;
public class BattleField
@@ -66,6 +67,10 @@
    // 记录正在处理死亡的角色ID,防止重复处理
    private HashSet<uint> processingDeathObjIds = new HashSet<uint>();
#if UNITY_EDITOR
    public static Dictionary<string, string> battleHpRecorder = new Dictionary<string, string>();
#endif
    public BattleField(string _guid)
    {
@@ -132,6 +137,43 @@
        SetRootNodePosition();
        rejectNewPackage = false;
        OnRoundChange?.Invoke(round, turnMax);
#if UNITY_EDITOR
        if (Launch.Instance.isOpenSkillLogFile)
        {
            string battleHpRecord = $"初始化战场: MapID={MapID}, FuncLineID={FuncLineID}, TurnMax={turnMax}\n";
            for (int i = 0; i < redTeamList.Count; i++)
            {
                battleHpRecord += $"红方队伍 {i}:\n";
                if (redTeamList[i] == null)
                    continue;
                foreach (var hero in redTeamList[i].serverHeroes)
                {
                    if (hero == null)
                        continue;
                    battleHpRecord += $"  角色ID: {hero.ObjID}, 初始血量: {hero.curHp}/{hero.maxHp}\n";
                }
            }
            for (int i = 0; blueTeamList != null && i < blueTeamList.Count; i++)
            {
                battleHpRecord += $"蓝方队伍 {i}:\n";
                if (blueTeamList[i] == null)
                    continue;
                foreach (var hero in blueTeamList[i].serverHeroes)
                {
                    if (hero == null)
                        continue;
                    battleHpRecord += $"  角色ID: {hero.ObjID}, 初始血量: {hero.curHp}/{hero.maxHp}\n";
                }
            }
            battleHpRecord += "------------------ END ------------------\n";
            battleHpRecorder.Add(guid, battleHpRecord);
        }
#endif
    }
    
    protected virtual void LoadMap(int mapID)
@@ -535,7 +577,20 @@
    //内部结算需要处理的逻辑,不含UI
    protected virtual void OnSettlement(JsonData turnFightStateData)
    {
#if UNITY_EDITOR
        if (Launch.Instance.isOpenSkillLogFile)
        {
            string finalReport = battleHpRecorder[guid];
            SkillBase.changeListDict.TryGetValue(guid, out string skillChanges);
            finalReport += "技能变更记录:\n";
            finalReport += skillChanges ?? "无技能变更记录\n";
            File.WriteAllText(Application.dataPath + "/../BattleReport/HpReport" + guid.Replace("-", "_") + ".txt",
                finalReport);
        }
#endif
    }
    //UI结算后回调需要处理的逻辑
Main/System/Battle/BattleObject/BattleObject.cs
@@ -310,8 +310,15 @@
    {
        bool isLastHit = battleHurtParam.hitIndex >= battleHurtParam.skillConfig.DamageDivide.Length - 1;
        bool firstHit = battleHurtParam.hitIndex == 0;
        // 添加调试日志
        bool isHealing = BattleUtility.IsHealing(battleHurtParam.hurt);
        BattleDmgInfo dmgInfo = PopDamage(battleHurtParam);
        // ============ 应用目标的血量和护盾变化 ============
        ApplyHurtToTarget(battleHurtParam, isLastHit);
        //  这里
        if (dmgInfo.IsType(DamageType.Dodge) /*&& !buffMgr.isControled[BattleConst.HardControlGroup]*/)//如果被控制了还闪避了 要看看服务器怎么处理了
@@ -327,8 +334,6 @@
                OnDodgeBegin();
            }
        }
        bool isFatalAttack = (null != battleHurtParam.deadPack) && isLastHit;
@@ -361,6 +366,27 @@
        }
    }
    /// <summary>
    /// 应用目标的血量和护盾变化
    /// </summary>
    private void ApplyHurtToTarget(BattleHurtParam battleHurtParam, bool isLastHit)
    {
        BattleHurtObj hurter = battleHurtParam.hurter;
        // 应用血量变化
        teamHero.curHp = hurter.toHp;
        // 护盾值由buff系统自动管理,不需要手动设置
        // buffMgr会根据服务器的HB428_tagSCBuffRefresh包更新护盾值
#if UNITY_EDITOR
        // 最后一击时验证血量是否与服务器一致
        if (isLastHit)
        {
            BattleUtility.ValidateHpConsistency(battleHurtParam, "目标受伤");
        }
#endif
    }
    const float pingpongTime = 0.4f;
    //  闪避开始
@@ -437,7 +463,8 @@
        BattleDmgInfo battleDmgInfo = new BattleDmgInfo(battleField.guid, battleHurtParam);
        // 天子的挑战拦截血条逻辑
        BattleObject boss = battleField.FindBoss();
        if (boss != null && battleField.MapID == 30020 && boss.ObjID == battleHurtParam.hurtObj.ObjID)
        // 修复:battleHurtParam.hurtObj.ObjID -> battleHurtParam.hurter.hurtObj.ObjID
        if (boss != null && battleField.MapID == 30020 && boss.ObjID == battleHurtParam.hurter.hurtObj.ObjID)
        {
            EventBroadcast.Instance.Broadcast(EventName.BATTLE_DAMAGE_TAKEN, battleDmgInfo);
            return battleDmgInfo;
@@ -451,6 +478,28 @@
            return battleDmgInfo;
        }
    }
    /// <summary>
    /// 为施法者创建伤害信息(吸血/反伤)
    /// </summary>
    protected virtual BattleDmgInfo PopDamageForCaster(BattleHurtParam battleHurtParam)
    {
        // 传入 isCasterView=true 表示这是施法者视角
        BattleDmgInfo battleDmgInfo = new BattleDmgInfo(battleField.guid, battleHurtParam, isCasterView: true);
        BattleObject boss = battleField.FindBoss();
        if (boss != null && battleField.MapID == 30020 && boss.ObjID == this.ObjID)
        {
            EventBroadcast.Instance.Broadcast(EventName.BATTLE_DAMAGE_TAKEN, battleDmgInfo);
            return battleDmgInfo;
        }
        else
        {
            heroInfoBar.UpdateDamage(battleDmgInfo);
            EventBroadcast.Instance.Broadcast(EventName.BATTLE_DAMAGE_TAKEN, battleDmgInfo);
            return battleDmgInfo;
        }
    }
    public RectTransform GetAliasTeamNode()
@@ -602,4 +651,55 @@
    }
#endif
    // BattleObject.cs
    public virtual void OnHurtTarget(BattleHurtParam battleHurtParam)
    {
        // 检查是否有吸血或反伤
        bool hasSuckHp = battleHurtParam.caster.suckHpList != null && battleHurtParam.caster.suckHpList.Count > 0;
        bool hasReflectHp = battleHurtParam.caster.reflectHpList != null && battleHurtParam.caster.reflectHpList.Count > 0;
        if (!hasSuckHp && !hasReflectHp)
        {
            return;
        }
        // ============ 应用施法者的血量和护盾变化 ============
        bool isLastHit = battleHurtParam.hitIndex >= battleHurtParam.skillConfig.DamageDivide.Length - 1;
        ApplyHurtToCaster(battleHurtParam, isLastHit);
        // 和Hurt一样,调用PopDamage处理吸血/反伤的显示
        BattleDmgInfo casterDmgInfo = PopDamageForCaster(battleHurtParam);
        // 如果有反伤,施法者播放受击动画
        if (hasReflectHp && casterDmgInfo.casterDamageList != null && casterDmgInfo.casterDamageList.Count > 0)
        {
            long totalReflect = casterDmgInfo.casterDamageList.Sum(d => d.damage);
            if (totalReflect > 0 && !buffMgr.isControled[BattleConst.HardControlGroup])
            {
                motionBase.PlayAnimation(MotionName.hit, false);
            }
        }
    }
    /// <summary>
    /// 应用施法者的血量和护盾变化(吸血和反伤)
    /// </summary>
    private void ApplyHurtToCaster(BattleHurtParam battleHurtParam, bool isLastHit)
    {
        BattleCastObj caster = battleHurtParam.caster;
        // 应用血量变化
        teamHero.curHp = caster.toHp;
        // 护盾值由buff系统自动管理,不需要手动设置
#if UNITY_EDITOR
        // 最后一击时验证血量是否与服务器一致
        if (isLastHit)
        {
            BattleUtility.ValidateHpConsistencyForCaster(battleHurtParam, "施法者吸血/反伤");
        }
#endif
    }
}
Main/System/Battle/BattleUtility.cs
@@ -311,6 +311,169 @@
                         (hurt.AttackTypes & (int)ServerDamageType.SuckHpReverse) == 0 &&
                         (hurt.AttackTypes & (int)ServerDamageType.SelfHarm) == 0;
    }
    public static long GetSuckHp(HB427_tagSCUseSkill hB427_TagSCUseSkill)
    {
        long totalSuckHp = 0;
        for (int i = 0; i < hB427_TagSCUseSkill.HurtList.Length; i++)
        {
            var hurt = hB427_TagSCUseSkill.HurtList[i];
            totalSuckHp += hurt.SuckHP;
        }
        return totalSuckHp;
    }
    public static BattleHurtParam CalcBattleHurtParam(SkillBase skillBase, int hitIndex, BattleObject target,
        HB427_tagSCUseSkill.tagSCUseSkillHurt hurt, BattleDrops battleDrops, HB422_tagMCTurnFightObjDead deadPack)
    {
        long suckHp = GetSuckHp(skillBase.tagUseSkillAttack);
        SkillConfig skillConfig = skillBase.skillConfig;
        long totalDamage = GeneralDefine.GetFactValue(hurt.HurtHP, hurt.HurtHPEx);
        long totalReflectHp = hurt.BounceHP;
        // 计算伤害分段
        long currentHitDamage = 0;
        List<long> damageList = DivideDamageToList(skillConfig.DamageDivide, hitIndex, totalDamage, ref currentHitDamage);
        // 计算吸血分段
        long currentHitSuckHp = 0;
        List<long> suckHpList = DivideDamageToList(skillConfig.DamageDivide, hitIndex, suckHp, ref currentHitSuckHp);
        // 计算反伤分段
        long currentHitReflectHp = 0;
        List<long> reflectHpList = DivideDamageToList(skillConfig.DamageDivide, hitIndex, totalReflectHp, ref currentHitReflectHp);
        // 创建目标受伤对象
        BattleHurtObj hurter = CreateHurter(target, damageList, hurt, hitIndex, skillConfig, currentHitDamage);
        // 创建施法者对象
        BattleCastObj caster = CreateCaster(skillBase, suckHpList, reflectHpList, currentHitSuckHp, currentHitReflectHp);
        // 组装BattleHurtParam
        BattleHurtParam battleHurtParam = new BattleHurtParam();
        battleHurtParam.caster = caster;
        battleHurtParam.hurter = hurter;
        battleHurtParam.battleDrops = battleDrops;
        battleHurtParam.hurt = hurt;
        battleHurtParam.hitIndex = hitIndex;
        battleHurtParam.deadPack = deadPack;
        battleHurtParam.skillConfig = skillConfig;
        return battleHurtParam;
    }
    public static BattleHurtObj CreateHurter(BattleObject target, List<long> damageList, HB427_tagSCUseSkill.tagSCUseSkillHurt hurt, int hitIndex, SkillConfig skillConfig, long currentHitDamage)
    {
        BattleHurtObj hurter = new BattleHurtObj();
        hurter.hurtObj = target;
        hurter.damageList = damageList;
        hurter.fromHp = target.teamHero.curHp;
        hurter.fromShieldValue = target.buffMgr.GetShieldValue();
        // 判断是否是最后一击
        bool isLastHit = hitIndex >= skillConfig.DamageDivide.Length - 1;
        // 判断是治疗还是伤害
        bool isHealing = IsHealing(hurt);
        // 计算目标血量变化
        if (isLastHit)
        {
            // 最后一击:使用服务器下发的最终血量
            hurter.toHp = GeneralDefine.GetFactValue(hurt.CurHP, hurt.CurHPEx);
        }
        else
        {
            // 非最后一击:客户端计算中间血量
            long maxHp = target.teamHero.maxHp;
            if (isHealing)
            {
                // 治疗逻辑:直接加血
                hurter.toHp = Math.Min(maxHp, hurter.fromHp + currentHitDamage);
            }
            else
            {
                // 伤害逻辑:先扣护盾,护盾不足再扣血
                if (hurter.fromShieldValue >= currentHitDamage)
                {
                    hurter.toHp = hurter.fromHp;
                }
                else
                {
                    long remainingDamage = currentHitDamage - hurter.fromShieldValue;
                    hurter.toHp = Math.Max(0, hurter.fromHp - remainingDamage);
                }
            }
        }
        // 计算护盾变化
        if (isHealing)
        {
            hurter.toShieldValue = hurter.fromShieldValue;
        }
        else
        {
            if (hurter.fromShieldValue >= currentHitDamage)
            {
                hurter.toShieldValue = hurter.fromShieldValue - currentHitDamage;
            }
            else
            {
                hurter.toShieldValue = 0;
            }
        }
        return hurter;
    }
    public static BattleCastObj CreateCaster(SkillBase skillBase, List<long> suckHpList, List<long> reflectHpList, long currentHitSuckHp, long currentHitReflectHp)
    {
        BattleCastObj caster = new BattleCastObj();
        caster.casterObj = skillBase.caster;
        caster.suckHpList = suckHpList;
        caster.reflectHpList = reflectHpList;
        // 获取施法者当前状态
        long casterFromHp = skillBase.caster.teamHero.curHp;
        long casterMaxHp = skillBase.caster.teamHero.maxHp;
        long casterFromShield = skillBase.caster.buffMgr.GetShieldValue();
        caster.fromHp = casterFromHp;
        caster.fromShieldValue = casterFromShield;
        // 计算施法者血量变化(吸血和反伤)
        long casterToHp = casterFromHp;
        long casterToShield = casterFromShield;
        // 处理吸血
        if (currentHitSuckHp > 0)
        {
            casterToHp = Math.Min(casterMaxHp, casterToHp + currentHitSuckHp);
        }
        // 处理反伤(施法者受到伤害)
        if (currentHitReflectHp > 0)
        {
            if (casterToShield >= currentHitReflectHp)
            {
                // 施法者护盾足够,只扣护盾
                casterToShield -= currentHitReflectHp;
            }
            else
            {
                // 施法者护盾不足,先扣完护盾,剩余扣血
                long remainingReflect = currentHitReflectHp - casterToShield;
                casterToShield = 0;
                casterToHp = Math.Max(0, casterToHp - remainingReflect);
            }
        }
        caster.toHp = casterToHp;
        caster.toShieldValue = casterToShield;
        return caster;
    }
    
    /// <summary>
    /// 将整个技能的总伤害按命中次数和分段配置分配
@@ -319,7 +482,7 @@
    /// <param name="hitIndex">当前是第几击(从0开始)</param>
    /// <param name="totalDamage">整个技能的总伤害</param>
    /// <returns>这一击内每一段的伤害值列表</returns>
    public static List<long> DivideDamageToList(int[][] damageDivideList, int hitIndex, long totalDamage)
    public static List<long> DivideDamageToList(int[][] damageDivideList, int hitIndex, long totalDamage, ref long currentHitDamage)
    {
        if (totalDamage <= 0)
        {
@@ -400,6 +563,8 @@
            // 非最后一击: 按权重计算
            currentHitTotalDamage = (long)((float)totalDamage * (float)currentHitWeight / (float)totalWeight);
        }
        currentHitDamage = currentHitTotalDamage;
        // ============ 第二步: 将当前这一击的伤害分配到各分段 ============
        List<long> fixedDamageList = new List<long>();
@@ -519,4 +684,142 @@
        }
        return rebornPackList;
    }
    // ============================================================
    // 文件 2: BattleUtility.cs
    // 添加血量一致性验证函数
    // ============================================================
    /// <summary>
    /// 验证目标血量是否与服务器包一致(仅在最后一击时调用)
    /// </summary>
    public static void ValidateHpConsistency(BattleHurtParam hurtParam, string context)
    {
#if UNITY_EDITOR
        BattleHurtObj hurter = hurtParam.hurter;
        HB427_tagSCUseSkill.tagSCUseSkillHurt hurt = hurtParam.hurt;
        // 获取服务器下发的最终血量
        long serverFinalHp = GeneralDefine.GetFactValue(hurt.CurHP, hurt.CurHPEx);
        // 获取客户端计算的最终血量
        long clientFinalHp = hurter.toHp;
        // 验证是否一致
        bool isConsistent = (serverFinalHp == clientFinalHp);
        string logColor = isConsistent ? "<color=green>" : "<color=red>";
        string resultStr = isConsistent ? "✓ 一致" : "✗ 不一致";
        BattleDebug.LogError(
            $"{logColor}========== 血量一致性验证 [{context}] {resultStr} ==========</color>\n" +
            $"目标: {hurter.hurtObj.teamHero.name} (ObjID:{hurter.hurtObj.ObjID})\n" +
            $"击数: 第{hurtParam.hitIndex + 1}击 (最后一击)\n" +
            $"服务器最终血量: {serverFinalHp}\n" +
            $"客户端计算血量: {clientFinalHp}\n" +
            $"血量差异: {clientFinalHp - serverFinalHp}\n" +
            $"\n" +
            $"---- 血量变化过程 ----\n" +
            $"初始血量: {hurter.fromHp}\n" +
            $"本次伤害/治疗: {(hurter.damageList != null ? hurter.damageList.Sum() : 0)}\n" +
            $"最终血量: {clientFinalHp}\n" +
            $"最大血量: {hurter.hurtObj.teamHero.maxHp}\n" +
            $"\n" +
            $"---- 护盾变化过程 ----\n" +
            $"初始护盾: {hurter.fromShieldValue}\n" +
            $"最终护盾: {hurter.toShieldValue}\n" +
            $"护盾变化: {hurter.toShieldValue - hurter.fromShieldValue}\n"
        );
        // 如果不一致,额外输出警告
        if (!isConsistent)
        {
            Debug.LogWarning(
                $"[血量验证失败] 目标:{hurter.hurtObj.teamHero.name} " +
                $"服务器:{serverFinalHp} vs 客户端:{clientFinalHp} " +
                $"差异:{clientFinalHp - serverFinalHp}"
            );
        }
#endif
    }
    /// <summary>
    /// 验证施法者血量是否正确(仅在最后一击时调用)
    /// </summary>
    public static void ValidateHpConsistencyForCaster(BattleHurtParam hurtParam, string context)
    {
#if UNITY_EDITOR
        BattleCastObj caster = hurtParam.caster;
        // 施法者没有服务器下发的最终血量,只能验证计算逻辑是否正确
        long clientFinalHp = caster.toHp;
        long calculatedHp = caster.fromHp;
        // 计算预期的血量变化
        long totalSuckHp = caster.suckHpList != null ? caster.suckHpList.Sum() : 0;
        long totalReflectHp = caster.reflectHpList != null ? caster.reflectHpList.Sum() : 0;
        // 模拟计算过程
        long expectedHp = calculatedHp;
        long expectedShield = caster.fromShieldValue;
        // 应用吸血
        if (totalSuckHp > 0)
        {
            expectedHp = Math.Min(caster.casterObj.teamHero.maxHp, expectedHp + totalSuckHp);
        }
        // 应用反伤
        if (totalReflectHp > 0)
        {
            if (expectedShield >= totalReflectHp)
            {
                expectedShield -= totalReflectHp;
            }
            else
            {
                long remainingReflect = totalReflectHp - expectedShield;
                expectedShield = 0;
                expectedHp = Math.Max(0, expectedHp - remainingReflect);
            }
        }
        bool isConsistent = (expectedHp == clientFinalHp);
        string logColor = isConsistent ? "<color=cyan>" : "<color=red>";
        string resultStr = isConsistent ? "✓ 计算正确" : "✗ 计算错误";
        BattleDebug.LogError(
            $"{logColor}========== 施法者血量验证 [{context}] {resultStr} ==========</color>\n" +
            $"施法者: {caster.casterObj.teamHero.name} (ObjID:{caster.casterObj.ObjID})\n" +
            $"击数: 第{hurtParam.hitIndex + 1}击 (最后一击)\n" +
            $"预期最终血量: {expectedHp}\n" +
            $"实际最终血量: {clientFinalHp}\n" +
            $"血量差异: {clientFinalHp - expectedHp}\n" +
            $"\n" +
            $"---- 血量变化过程 ----\n" +
            $"初始血量: {caster.fromHp}\n" +
            $"吸血总量: {totalSuckHp}\n" +
            $"反伤总量: {totalReflectHp}\n" +
            $"最终血量: {clientFinalHp}\n" +
            $"最大血量: {caster.casterObj.teamHero.maxHp}\n" +
            $"\n" +
            $"---- 护盾变化过程 ----\n" +
            $"初始护盾: {caster.fromShieldValue}\n" +
            $"预期护盾: {expectedShield}\n" +
            $"实际护盾: {caster.toShieldValue}\n" +
            $"护盾差异: {caster.toShieldValue - expectedShield}\n"
        );
        // 如果不一致,额外输出警告
        if (!isConsistent)
        {
            Debug.LogWarning(
                $"[施法者血量计算错误] {caster.casterObj.teamHero.name} " +
                $"预期:{expectedHp} vs 实际:{clientFinalHp} " +
                $"差异:{clientFinalHp - expectedHp}"
            );
        }
#endif
    }
}
Main/System/Battle/Define/BattleDmgInfo.cs
@@ -17,9 +17,50 @@
    public string battleFieldGuid { get; private set; }
    public BattleHurtParam battleHurtParam { get; private set; }
    
    public List<long> damageList { get { return battleHurtParam.damageList; } }
    public BattleObject hurtObj { get { return battleHurtParam.hurtObj; } }
    public BattleObject casterObj { get { return battleHurtParam.casterObj; } }
    private bool isCasterView = false; // 标记是否是施法者视角
    // 新增属性:自动判断是处理目标还是施法者
    public List<long> damageList
    {
        get
        {
            // 施法者视角:使用吸血和反伤列表的总和
            if (isCasterView)
            {
                List<long> casterDamages = new List<long>();
                if (battleHurtParam.caster?.suckHpList != null)
                    casterDamages.AddRange(battleHurtParam.caster.suckHpList);
                if (battleHurtParam.caster?.reflectHpList != null)
                    casterDamages.AddRange(battleHurtParam.caster.reflectHpList);
                return casterDamages;
            }
            // 目标视角:使用目标的伤害列表
            if (battleHurtParam.hurter != null && battleHurtParam.hurter.damageList != null)
                return battleHurtParam.hurter.damageList;
            return new List<long>();
        }
    }
    public BattleObject hurtObj
    {
        get
        {
            // 如果是施法者视角,返回施法者作为受伤对象
            if (isCasterView)
                return battleHurtParam.caster?.casterObj;
            // 否则返回目标
            return battleHurtParam.hurter?.hurtObj;
        }
    }
    public BattleObject casterObj
    {
        get
        {
            return battleHurtParam.caster?.casterObj;
        }
    }
    public HB427_tagSCUseSkill.tagSCUseSkillHurt hurt { get { return battleHurtParam.hurt; } }
    public SkillConfig skillConfig { get { return battleHurtParam.skillConfig; } }
@@ -34,10 +75,11 @@
    #region Initialization
    public BattleDmgInfo(string battleFieldGuid, BattleHurtParam battleHurtParam)
    public BattleDmgInfo(string battleFieldGuid, BattleHurtParam battleHurtParam, bool isCasterView = false)
    {
        this.battleFieldGuid = battleFieldGuid;
        this.battleHurtParam = battleHurtParam;
        this.isCasterView = isCasterView;
        this.isLastHit = battleHurtParam.hitIndex >= battleHurtParam.skillConfig.DamageDivide.Length - 1;
        
        m_rawAttackType = hurt == null ? 0 : hurt.AttackTypes;
@@ -134,20 +176,19 @@
        }
        hurt.AttackTypes = (uint)convertedAttackTypes;
        Debug.Log($"[BattleDmgInfo] 伤害类型转换: {originalAttackTypes} -> {hurt.AttackTypes}");
    }
    #endregion
    #region Damage List Generation
    // HandleAttackTypeAndDamage 中只生成对应视角的飘字列表
    private void HandleAttackTypeAndDamage()
    {
        isBlocked = HaveBlockDamage();
        isImmune = IsImmune();
        int rawAttackType = hurt == null ? 0 : (int)hurt.AttackTypes;
        
        // 如果是免疫,直接添加免疫显示,不处理其他伤害
        if (isImmune)
        {
            targetDamageList.Add(new BattleDmg 
@@ -162,9 +203,19 @@
        for (int i = 0; i < maxCount; i++)
        {
            ProcessReflectDamage(i);
            ProcessSuckHpDamage(i);
            ProcessMainDamage(i, rawAttackType);
            if (isCasterView)
            {
                // 施法者视角:只处理吸血和反伤,添加到targetDamageList用于飘字
                ProcessSuckHpDamage(i);
                ProcessReflectDamage(i);
            }
            else
            {
                // 目标视角:处理反伤和吸血添加到casterDamageList,主伤害添加到targetDamageList
                ProcessReflectDamage(i);
                ProcessSuckHpDamage(i);
                ProcessMainDamage(i, rawAttackType);
            }
        }
    }
@@ -173,9 +224,21 @@
    /// </summary>
    private int CalculateMaxDamageSegmentCount()
    {
        int maxCount = damageList != null ? damageList.Count : 0;
        maxCount = Mathf.Max(maxCount, battleHurtParam.suckHpList != null ? battleHurtParam.suckHpList.Count : 0);
        maxCount = Mathf.Max(maxCount, battleHurtParam.reflectHpList != null ? battleHurtParam.reflectHpList.Count : 0);
        int maxCount = 0;
        // 目标受伤的伤害段数
        if (battleHurtParam.hurter != null && battleHurtParam.hurter.damageList != null)
            maxCount = Mathf.Max(maxCount, battleHurtParam.hurter.damageList.Count);
        // 施法者的吸血和反伤段数
        if (battleHurtParam.caster != null)
        {
            if (battleHurtParam.caster.suckHpList != null)
                maxCount = Mathf.Max(maxCount, battleHurtParam.caster.suckHpList.Count);
            if (battleHurtParam.caster.reflectHpList != null)
                maxCount = Mathf.Max(maxCount, battleHurtParam.caster.reflectHpList.Count);
        }
        return maxCount;
    }
@@ -184,17 +247,33 @@
    /// </summary>
    private void ProcessReflectDamage(int segmentIndex)
    {
        if (battleHurtParam.reflectHpList == null || segmentIndex >= battleHurtParam.reflectHpList.Count)
        if (battleHurtParam.caster == null || battleHurtParam.caster.reflectHpList == null)
            return;
        if (segmentIndex >= battleHurtParam.caster.reflectHpList.Count)
            return;
        long reflectHp = battleHurtParam.reflectHpList[segmentIndex];
        long reflectHp = battleHurtParam.caster.reflectHpList[segmentIndex];
        if (reflectHp > 0)
        {
            casterDamageList.Add(new BattleDmg
            if (isCasterView)
            {
                damage = reflectHp,
                attackType = (int)DamageType.Reflect
            });
                // 施法者视角:反伤显示在自己身上(targetDamageList)
                targetDamageList.Add(new BattleDmg
                {
                    damage = reflectHp,
                    attackType = (int)DamageType.Reflect
                });
            }
            else
            {
                // 目标视角:反伤显示在施法者身上(casterDamageList)
                casterDamageList.Add(new BattleDmg
                {
                    damage = reflectHp,
                    attackType = (int)DamageType.Reflect
                });
            }
        }
    }
@@ -203,17 +282,33 @@
    /// </summary>
    private void ProcessSuckHpDamage(int segmentIndex)
    {
        if (battleHurtParam.suckHpList == null || segmentIndex >= battleHurtParam.suckHpList.Count)
        if (battleHurtParam.caster == null || battleHurtParam.caster.suckHpList == null)
            return;
        if (segmentIndex >= battleHurtParam.caster.suckHpList.Count)
            return;
        long suckHp = battleHurtParam.suckHpList[segmentIndex];
        long suckHp = battleHurtParam.caster.suckHpList[segmentIndex];
        if (suckHp > 0)
        {
            casterDamageList.Add(new BattleDmg
            if (isCasterView)
            {
                damage = suckHp,
                attackType = (int)DamageType.SuckHP
            });
                // 施法者视角:吸血显示在自己身上(targetDamageList)
                targetDamageList.Add(new BattleDmg
                {
                    damage = suckHp,
                    attackType = (int)DamageType.SuckHP
                });
            }
            else
            {
                // 目标视角:吸血显示在施法者身上(casterDamageList)
                casterDamageList.Add(new BattleDmg
                {
                    damage = suckHp,
                    attackType = (int)DamageType.SuckHP
                });
            }
        }
    }
@@ -222,10 +317,14 @@
    /// </summary>
    private void ProcessMainDamage(int segmentIndex, int rawAttackType)
    {
        if (damageList == null || segmentIndex >= damageList.Count)
        // 只处理目标受伤的主要伤害
        if (battleHurtParam.hurter == null || battleHurtParam.hurter.damageList == null)
            return;
        if (segmentIndex >= battleHurtParam.hurter.damageList.Count)
            return;
        long actualDamage = damageList[segmentIndex];
        long actualDamage = battleHurtParam.hurter.damageList[segmentIndex];
        if (isBlocked)
        {
@@ -351,28 +450,27 @@
    #endregion
}
public class BattleHurtParam
public class BattleHurtObj
{
    public BattleObject casterObj;
    public BattleObject hurtObj;
    public List<long> damageList;
    public List<long> suckHpList;
    public List<long> reflectHpList;
    public BattleDrops battleDrops;
    public HB427_tagSCUseSkill.tagSCUseSkillHurt hurt;
    public int hitIndex;
    public HB422_tagMCTurnFightObjDead deadPack;
    public SkillConfig skillConfig;
    public long maxHp;
    public long fromShieldValue;
    public long toShieldValue;
    public long fromHp;
    public long toHp;
    //  未被护盾抵消前的伤害
    public long maxHp
    {
        get
        {
            return hurtObj != null ? hurtObj.teamHero.maxHp : 0;
        }
    }
    public long fromShieldValue;
    public long toShieldValue;
    public long totalDamage
    {
        get
@@ -389,4 +487,41 @@
            return total;
        }
    }
}
public class BattleCastObj
{
    public BattleObject casterObj;
    public List<long> suckHpList;
    public List<long> reflectHpList;
    public long fromHp;
    public long toHp;
    public long maxHp
    {
        get
        {
            return casterObj != null ? casterObj.teamHero.maxHp : 0;
        }
    }
    public long fromShieldValue;
    public long toShieldValue;
}
public class BattleHurtParam
{
    public BattleCastObj caster;
    public BattleHurtObj hurter;
    public BattleDrops battleDrops;
    public HB427_tagSCUseSkill.tagSCUseSkillHurt hurt;
    public int hitIndex;
    public HB422_tagMCTurnFightObjDead deadPack;
    public SkillConfig skillConfig;
}
Main/System/Battle/Skill/SkillBase.cs
@@ -53,6 +53,87 @@
        packList = _packList;
        SafetyCheck();
#if UNITY_EDITOR
        if (Launch.Instance.isOpenSkillLogFile)
        {
            PinrtHB427Hp();
        }
#endif
    }
#if UNITY_EDITOR
    public static Dictionary<string, string> changeListDict = new Dictionary<string, string>();
#endif
    private void PinrtHB427Hp()
    {
#if UNITY_EDITOR
        string skillDetail = "SkillCaster : " + tagUseSkillAttack.ObjID + " -> cast SkillID: " + skillConfig.SkillID + "\n";
        skillDetail += "------------------ HurtList ------------------\n";
        for (int i = 0; i < tagUseSkillAttack.HurtCount; i++)
        {
            var Hurt = tagUseSkillAttack.HurtList[i];
            BattleObject battleObject = caster.battleField.battleObjMgr.GetBattleObject((int)Hurt.ObjID);
            string targetName = battleObject != null ? battleObject.teamHero.name : "Unknown";
            long hurtHp = GeneralDefine.GetFactValue(Hurt.HurtHP, Hurt.HurtHPEx);
            long curHp = GeneralDefine.GetFactValue(Hurt.CurHP, Hurt.CurHPEx);
            skillDetail += $"  [{i}] Target: {targetName} (ObjID:{Hurt.ObjID})\n";
            skillDetail += $"      HurtHP: {hurtHp}\n";
            skillDetail += $"      CurHP: {curHp}\n";
            skillDetail += $"      SuckHP: {Hurt.SuckHP}\n";
            skillDetail += $"      BounceHP: {Hurt.BounceHP}\n";
            skillDetail += $"      AttackTypes: {Hurt.AttackTypes}\n";
            if (Hurt.HurtListEx != null && Hurt.HurtListEx.Length > 0)
            {
                skillDetail += $"      HurtListEx ({Hurt.HurtListEx.Length}):\n";
                for (int j = 0; j < Hurt.HurtListEx.Length; j++)
                {
                    var hurtEx = Hurt.HurtListEx[j];
                    long hurtExHp = GeneralDefine.GetFactValue(hurtEx.HurtHP, hurtEx.HurtHPEx);
                    long curExHp = GeneralDefine.GetFactValue(hurtEx.CurHP, hurtEx.CurHPEx);
                    skillDetail += $"        [{j}] ObjID:{hurtEx.ObjID} HurtHP:{hurtExHp} CurHP:{curExHp} SuckHP:{hurtEx.SuckHP} AttackTypes:{hurtEx.AttackTypes}\n";
                }
            }
        }
        skillDetail += "------------------ HurtListEx ------------------\n";
        if (tagUseSkillAttack.HurtListEx != null)
        {
            for (int i = 0; i < tagUseSkillAttack.HurtListEx.Length; i++)
            {
                var HurtEx = tagUseSkillAttack.HurtListEx[i];
                BattleObject battleObject = caster.battleField.battleObjMgr.GetBattleObject((int)HurtEx.ObjID);
                string targetName = battleObject != null ? battleObject.teamHero.name : "Unknown";
                long hurtHp = GeneralDefine.GetFactValue(HurtEx.HurtHP, HurtEx.HurtHPEx);
                long curHp = GeneralDefine.GetFactValue(HurtEx.CurHP, HurtEx.CurHPEx);
                skillDetail += $"  [{i}] Target: {targetName} (ObjID:{HurtEx.ObjID})\n";
                skillDetail += $"      HurtHP: {hurtHp}\n";
                skillDetail += $"      CurHP: {curHp}\n";
                skillDetail += $"      SuckHP: {HurtEx.SuckHP}\n";
                skillDetail += $"      AttackTypes: {HurtEx.AttackTypes}\n";
            }
        }
        skillDetail += "------------------ END ------------------\n";
        if (changeListDict.ContainsKey(caster.battleField.guid))
        {
            string origin = changeListDict[caster.battleField.guid];
            origin += skillDetail;
            changeListDict[caster.battleField.guid] = origin;
        }
        else
            changeListDict.Add(caster.battleField.guid, skillDetail);
        Debug.LogError("skillDetail : " + skillDetail);
#endif
    }
    private void SafetyCheck()
@@ -358,8 +439,14 @@
    // 技能开始回调:处理死亡、子技能、技能效果初始化
    public void OnSkillStart()
    {
        if (isPlay)
        {
            Debug.LogError(" play twice OnSkillStart skillId :" + skillConfig.SkillID);
            return;
        }
        HandleDead();
        skillEffect = SkillEffectFactory.CreateSkillEffect(this, caster, skillConfig, tagUseSkillAttack);
        skillEffect.Play(OnHitTargets);
        foreach (var subSkillPack in tagUseSkillAttack.subSkillList)
@@ -369,6 +456,7 @@
            otherSkillActionList.Add(recordAction);
            battleField.recordPlayer.ImmediatelyPlay(recordAction);
        }
        tagUseSkillAttack.subSkillList.Clear();
        foreach (var subCombinePack in tagUseSkillAttack.subSkillCombinePackList)
        {
            SkillRecordAction recordAction = CustomHB426CombinePack.CreateSkillAction(battleField.guid, subCombinePack.packList);
@@ -376,6 +464,7 @@
            otherSkillActionList.Add(recordAction);
            battleField.recordPlayer.ImmediatelyPlay(recordAction);
        }
        tagUseSkillAttack.subSkillCombinePackList.Clear();
        isPlay = true;
    }
@@ -475,189 +564,65 @@
        }
    }
    // protected void RecoveryHp(long suckHp, int _hitIndex)
    // {
    //     // long suckHp = hurt.SuckHP;
    //     if (suckHp <= 0)
    //     {
    //         return;
    //     }
    //     List<long> suckHpList = BattleUtility.DivideDamageToList(skillConfig.DamageDivide, _hitIndex, suckHp);
    //     long currentHitSuckHp = 0;
    //     foreach (long suck in suckHpList)
    //     {
    //         currentHitSuckHp += suck;
    //     }
    //     long fromHp = caster.teamHero.curHp;
    //     long toHp = caster.teamHero.curHp + currentHitSuckHp;
    //             //  参数打包
    //     BattleHurtParam hurtParam = new BattleHurtParam()
    //     {
    //         casterObj = caster,
    //         hurtObj = null,
    //         damageList = new List<long>(),
    //         suckHpList = suckHpList,
    //         reflectHpList = new List<long>(),
    //         fromHp = fromHp,
    //         toHp = toHp,
    //         battleDrops = null,
    //         hurt = null,
    //         hitIndex = _hitIndex,
    //         deadPack = null,
    //         skillConfig = skillConfig
    //     };
    //     caster.Hurt(hurtParam);
    // }
    // 处理单个目标被命中:应用伤害和施法者效果
    protected virtual void OnHitEachTarget(int _hitIndex, BattleObject target, HB427_tagSCUseSkill.tagSCUseSkillHurt hurt)
    {
        // 计算总伤害和分段伤害列表
        long totalDamage = GeneralDefine.GetFactValue(hurt.HurtHP, hurt.HurtHPEx);
        List<long> damageList = BattleUtility.DivideDamageToList(skillConfig.DamageDivide, _hitIndex, totalDamage);
        long totalSuckHp = suckHp;
        List<long> suckHpList = BattleUtility.DivideDamageToList(skillConfig.DamageDivide, _hitIndex, totalSuckHp);
        long totalReflectHp = hurt.BounceHP;
        List<long> reflectHpList = BattleUtility.DivideDamageToList(skillConfig.DamageDivide, _hitIndex, totalReflectHp);
        // 计算当前这一击的各项数值
        long currentHitDamage = 0;
        foreach (long dmg in damageList)
        {
            currentHitDamage += dmg;
        }
        long currentHitSuckHp = 0;
        foreach (long suck in suckHpList)
        {
            currentHitSuckHp += suck;
        }
        long currentHitReflectHp = 0;
        foreach (long reflect in reflectHpList)
        {
            currentHitReflectHp += reflect;
        }
        // ============ 第二步:获取目标当前状态 ============
        long fromHp = target.teamHero.curHp;
        long maxHp = target.teamHero.maxHp;
        long fromShieldValue = target.buffMgr.GetShieldValue();
        // 判断是治疗还是伤害
        bool isHealing = BattleUtility.IsHealing(hurt);
        // ============ 第三步:计算目标血量和护盾变化 ============
        long toHp;
        long toShieldValue;
        if (isHealing)
        {
            // 治疗逻辑:直接加血,护盾不变
            toHp = Math.Min(maxHp, fromHp + currentHitDamage);
            toShieldValue = fromShieldValue;
        }
        else
        {
            // 伤害逻辑:先扣护盾,护盾不足再扣血
            if (fromShieldValue >= currentHitDamage)
            {
                // 护盾足够承受所有伤害
                toShieldValue = fromShieldValue - currentHitDamage;
                toHp = fromHp;
            }
            else
            {
                // 护盾不足,先扣完护盾,剩余伤害扣血
                long remainingDamage = currentHitDamage - fromShieldValue;
                toShieldValue = 0;
                toHp = Math.Max(0, fromHp - remainingDamage);
            }
        }
        // ============ 第四步:更新目标实际血量 ============
        target.teamHero.curHp = toHp;
        // ============ 第五步:计算并更新施法者血量变化 ============
        long casterFromHp = caster.teamHero.curHp;
        long casterMaxHp = caster.teamHero.maxHp;
        long casterToHp = casterFromHp;
        // 处理吸血
        if (currentHitSuckHp > 0)
        {
            casterToHp = Math.Min(casterMaxHp, casterToHp + currentHitSuckHp);
        }
        // 处理反伤(施法者受到伤害)
        if (currentHitReflectHp > 0)
        {
            long casterShieldValue = caster.buffMgr.GetShieldValue();
            if (casterShieldValue >= currentHitReflectHp)
            {
                // 施法者护盾足够,血量不变
            }
            else
            {
                // 施法者护盾不足,扣血
                long remainingReflect = currentHitReflectHp - casterShieldValue;
                casterToHp = Math.Max(0, casterToHp - remainingReflect);
            }
        }
        // 更新施法者血量
        caster.teamHero.curHp = casterToHp;
#if UNITY_EDITOR
        BattleDebug.LogError(
            (caster.Camp == BattleCamp.Red ? "【红方行动】" : "【蓝方行动】 ") +
            $"攻击者: {caster.teamHero.name}\n" +
            $"目标: {target.teamHero.name}\n" +
            $"技能: {skillConfig.SkillName} (第{_hitIndex}击)\n" +
            $"伤害: {currentHitDamage} (总伤害: {totalDamage})\n" +
            $"吸血: {currentHitSuckHp}\n" +
            $"反伤: {currentHitReflectHp}\n" +
            $"目标护盾变化: {fromShieldValue} -> {toShieldValue}\n" +
            $"目标血量变化: {fromHp} -> {toHp}\n" +
            $"施法者血量变化: {casterFromHp} -> {casterToHp}\n"
        );
#endif
        // ============ 第六步:获取临时数据(掉落、死亡等) ============
        // ============ 获取临时数据(掉落、死亡等) ============
        int objID = (int)target.ObjID;
        tempDropList.TryGetValue(objID, out BattleDrops battleDrops);
        tempDeadPackList.TryGetValue(objID, out HB422_tagMCTurnFightObjDead deadPack);
        // ============ 第七步:参数打包并调用目标Hurt ============
        BattleHurtParam hurtParam = new BattleHurtParam()
        {
            casterObj = caster,
            hurtObj = target,
            damageList = damageList,
            suckHpList = suckHpList,      // 用于casterDamageList飘字
            reflectHpList = reflectHpList, // 用于casterDamageList飘字
            fromHp = fromHp,
            toHp = toHp,
            maxHp = maxHp,
            fromShieldValue = fromShieldValue,
            toShieldValue = toShieldValue,
            battleDrops = battleDrops,
            hurt = hurt,
            hitIndex = _hitIndex,
            deadPack = deadPack,
            skillConfig = skillConfig
        };
        // ============ 参数打包 ============
        BattleHurtParam hurtParam = BattleUtility.CalcBattleHurtParam(this, _hitIndex, target, hurt, battleDrops, deadPack);
#if UNITY_EDITOR
        PrintHurtParamDebugInfo(hurtParam);
#endif
        // 先调用目标受伤
        target.Hurt(hurtParam);
        // 再调用施法者吸血/反伤
        caster.OnHurtTarget(hurtParam);
    }
#if UNITY_EDITOR
    private void PrintHurtParamDebugInfo(BattleHurtParam hurtParam)
    {
        bool isLastHit = hurtParam.hitIndex >= hurtParam.skillConfig.DamageDivide.Length - 1;
        long currentHitDamage = hurtParam.hurter.damageList != null ? hurtParam.hurter.damageList.Sum() : 0;
        long currentHitSuckHp = hurtParam.caster.suckHpList != null ? hurtParam.caster.suckHpList.Sum() : 0;
        long currentHitReflectHp = hurtParam.caster.reflectHpList != null ? hurtParam.caster.reflectHpList.Sum() : 0;
        long totalDamage = GeneralDefine.GetFactValue(hurtParam.hurt.HurtHP, hurtParam.hurt.HurtHPEx);
        long totalSuckHp = BattleUtility.GetSuckHp(tagUseSkillAttack);
        long totalReflectHp = hurtParam.hurt.BounceHP;
        BattleDebug.LogError(
            (hurtParam.caster.casterObj.Camp == BattleCamp.Red ? "【红方行动】" : "【蓝方行动】 ") +
            $"攻击者: {hurtParam.caster.casterObj.teamHero.name} (ObjID:{hurtParam.caster.casterObj.ObjID})\n" +
            $"目标: {hurtParam.hurter.hurtObj.teamHero.name} (ObjID:{hurtParam.hurter.hurtObj.ObjID})\n" +
            $"技能: {hurtParam.skillConfig.SkillName} (ID:{hurtParam.skillConfig.SkillID})\n" +
            $"击数: 第{hurtParam.hitIndex + 1}击 / 共{hurtParam.skillConfig.DamageDivide.Length}击" + (isLastHit ? " [最后一击]" : " [中间击]") + "\n" +
            $"\n" +
            $"========== 目标受伤数据 ==========\n" +
            $"伤害: {currentHitDamage} / 总伤害: {totalDamage}\n" +
            $"伤害分段: [{string.Join(", ", hurtParam.hurter.damageList ?? new List<long>())}]\n" +
            $"目标血量: {hurtParam.hurter.fromHp} -> {hurtParam.hurter.toHp} (最大:{hurtParam.hurter.maxHp})\n" +
            $"目标护盾: {hurtParam.hurter.fromShieldValue} -> {hurtParam.hurter.toShieldValue}\n" +
            $"攻击类型: {hurtParam.hurt.AttackTypes}\n" +
            $"\n" +
            $"========== 施法者数据 ==========\n" +
            $"吸血: {currentHitSuckHp} / 总吸血: {totalSuckHp}\n" +
            $"吸血分段: [{string.Join(", ", hurtParam.caster.suckHpList ?? new List<long>())}]\n" +
            $"反伤: {currentHitReflectHp} / 总反伤: {totalReflectHp}\n" +
            $"反伤分段: [{string.Join(", ", hurtParam.caster.reflectHpList ?? new List<long>())}]\n" +
            $"施法者血量: {hurtParam.caster.fromHp} -> {hurtParam.caster.toHp} (最大:{hurtParam.caster.maxHp})\n" +
            $"施法者护盾: {hurtParam.caster.fromShieldValue} -> {hurtParam.caster.toShieldValue}\n"
        );
    }
#endif
    // 处理HP刷新包(简化逻辑)
    private void HandleRefreshHP()
@@ -841,6 +806,7 @@
    {
        if (!isPlay) return false;
        
        bool tempRetValue = true;
        // 检查技能效果是否完成
        if (skillEffect != null)
@@ -848,7 +814,7 @@
            if (!skillEffect.IsFinished()) return false;
            skillEffect = null;
            OnSkillFinished();
            return false;
            tempRetValue = false;
        }
        // 检查其他技能动作是否完成
@@ -863,7 +829,15 @@
                    OnSkillFinished();
                }
            }
            if (otherSkillActionList.Count > 0) return false;
            if (otherSkillActionList.Count > 0)
            {
                tempRetValue = false;
            }
        }
        if (!tempRetValue)
        {
            return false;
        }
        // 检查最终完成状态
Main/System/Battle/StoryBossBattleWin.cs
@@ -116,12 +116,21 @@
    protected override void OnDamageTaken(BattleDmgInfo info)
    {
        // Debug.LogError("OnDamageTaken 被调用 调用者是 " + info.battleHurtParam.caster.casterObj?.teamHero.name + " 对象 " + info.battleHurtParam.hurter.hurtObj?.teamHero.name);
        base.OnDamageTaken(info);
        if (battleField == null || info.battleFieldGuid != battleField.guid)
            return;
        if (bossBattleObject != null && info.hurtObj != null && info.hurtObj.ObjID == bossBattleObject.ObjID)
        if (null == bossBattleObject)
            return;
        if (bossBattleObject.teamHero.ObjID == info.battleHurtParam.hurter.hurtObj.teamHero.ObjID)
        {
            RefreshHP();
            bossLifeBar.Show((ulong)info.battleHurtParam.hurter.toHp, (ulong)bossBattleObject.teamHero.maxHp);
        }
        else if (bossBattleObject.teamHero.ObjID == info.battleHurtParam.caster.casterObj.teamHero.ObjID)
        {
            bossLifeBar.Show((ulong)info.battleHurtParam.caster.toHp, (ulong)bossBattleObject.teamHero.maxHp);
        }
    }
Main/System/Battle/UIComp/BattleHeroInfoBar.cs
@@ -259,13 +259,37 @@
    }
    /// <summary>
    /// 实际执行伤害更新
    /// 实际执行伤害更新(统一处理目标和施法者)
    /// </summary>
    private void ExecuteDamageUpdate(BattleDmgInfo dmgInfo)
    {
        KillTween(ref damageSequence);
        long maxHp = dmgInfo.battleHurtParam.maxHp;
        // 判断是目标视角还是施法者视角,使用对应的数据
        bool isCasterView = dmgInfo.hurtObj == dmgInfo.casterObj;
        long maxHp, fromHp, toHp, fromShield, toShield;
        if (isCasterView)
        {
            // 施法者视角:使用 caster 数据
            BattleCastObj caster = dmgInfo.battleHurtParam.caster;
            maxHp = caster.maxHp;
            fromHp = caster.fromHp;
            toHp = caster.toHp;
            fromShield = caster.fromShieldValue;
            toShield = caster.toShieldValue;
        }
        else
        {
            // 目标视角:使用 hurter 数据
            BattleHurtObj hurter = dmgInfo.battleHurtParam.hurter;
            maxHp = hurter.maxHp;
            fromHp = hurter.fromHp;
            toHp = hurter.toHp;
            fromShield = hurter.fromShieldValue;
            toShield = hurter.toShieldValue;
        }
        if (maxHp <= 0)
        {
@@ -274,32 +298,21 @@
            return;
        }
        long fromHp = dmgInfo.battleHurtParam.fromHp;
        long toHp = dmgInfo.battleHurtParam.toHp;
        long fromShield = dmgInfo.battleHurtParam.fromShieldValue;
        long toShield = dmgInfo.battleHurtParam.toShieldValue;
        damageSequence = DOTween.Sequence();
        // 设置初始值
        // 护盾动画
        if (fromShield > 0)
        {
            // 护盾1的值 = min(当前血量 + 当前护盾值, maxHp) / maxHp
            float fromShield1Value = Mathf.Min((float)(fromHp + fromShield), (float)maxHp) / (float)maxHp;
            // 护盾2的值 = max(当前血量 + 当前护盾值 - maxHp, 0) / maxHp
            float fromShield2Value = Mathf.Max((float)(fromHp + fromShield - maxHp), 0f) / (float)maxHp;
            
            sliderShield1.value = fromShield1Value;
            sliderShield2.value = fromShield2Value;
            // 如果护盾2有值,先播放护盾2的动画
            // 护盾2动画
            if (fromShield2Value > 0)
            {
                // 计算护盾2的初始值和目标值
                long beginShield2Value = fromHp + fromShield - maxHp;
                long endShield2Value = Mathf.Max(0, (int)(toHp + toShield - maxHp));
                float toShield2Value = (float)endShield2Value / (float)maxHp;
                float toShield2Value = Mathf.Max((float)(toHp + toShield - maxHp), 0f) / (float)maxHp;
                if (Mathf.Abs(fromShield2Value - toShield2Value) > 0.001f)
                {
@@ -307,10 +320,9 @@
                }
            }
            // 播放护盾1的动画
            // 护盾1动画
            if (fromShield1Value > 0)
            {
                // 计算护盾1的目标值
                float toShield1Value = Mathf.Min((float)(toHp + toShield), (float)maxHp) / (float)maxHp;
                if (Mathf.Abs(fromShield1Value - toShield1Value) > 0.001f)
@@ -321,7 +333,6 @@
        }
        else
        {
            // 没有护盾,直接设置为0
            sliderShield1.value = 0f;
            sliderShield2.value = 0f;
        }
@@ -338,7 +349,6 @@
        }
        damageSequence.Play();
        battleObject.battleField.battleTweenMgr.OnPlayTween(damageSequence);
    }