hch
17 小时以前 6113c5c3ad2162ba35204fc309fcfe83f0b62d70
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using DG.Tweening;
using DG.Tweening.Core;
using System.Linq;
 
public static class BattleUtility
{
    // 其他通用的战斗工具方法可以放在这里
 
 
    public static void MarkStartAndEnd(RectTransform startNode, RectTransform endNode)
    {
        // 运行时才执行
        if (!Application.isPlaying)
        {
            Debug.LogWarning("请在运行时使用该功能!");
            return;
        }
 
        var battleField = BattleManager.Instance.storyBattleField;
        if (battleField == null)
        {
            Debug.LogError("BattleManager.storyBattleField 未初始化!");
            return;
        }
 
 
        BattleWin battleWin = UIManager.Instance.GetUI<BattleWin>();
 
        RectTransform canvasRect = battleWin.transform as RectTransform;
 
        CreateMarker(canvasRect, startNode, "StartMarker");
        CreateMarker(canvasRect, endNode, "EndMarker");
    }
 
    private static void CreateMarker(RectTransform canvasRect, RectTransform targetNode, string markerName)
    {
        // 获取目标节点的世界坐标(中心点)
        Vector3 worldPos = targetNode.TransformPoint(targetNode.rect.center);
 
        // 转换到Canvas本地坐标
        Vector2 localPoint;
        RectTransformUtility.ScreenPointToLocalPointInRectangle(
            canvasRect,
            RectTransformUtility.WorldToScreenPoint(null, worldPos),
            null,
            out localPoint);
 
        // 创建RawImage
        GameObject marker = new GameObject(markerName, typeof(RawImage));
        GameObject.Destroy(marker, 5f);
        marker.transform.SetParent(canvasRect, false);
        var rawImage = marker.GetComponent<RawImage>();
        rawImage.color = Color.white;
        rawImage.rectTransform.sizeDelta = new Vector2(100, 100);
        rawImage.rectTransform.anchoredPosition = localPoint;
    }
 
 
    public static TweenerCore<Vector2, Vector2, DG.Tweening.Plugins.Options.VectorOptions> MoveToTarget(
        RectTransform transform, RectTransform target, Vector2 offset, Action onComplete = null, float speed = 500f)
    {
        // 获取目标节点的世界坐标(锚点位置)
        Vector3 worldPos = target.position;
 
        // 如果需要加 offset,需考虑 scale
        Vector3 offsetWorld = target.TransformVector(offset);
        worldPos += offsetWorld;
 
        RectTransform canvasRect = transform.parent as RectTransform;
 
        // 转换到Canvas本地坐标
        Vector2 localPoint;
        RectTransformUtility.ScreenPointToLocalPointInRectangle(
            canvasRect,
            RectTransformUtility.WorldToScreenPoint(null, worldPos),
            null,
            out localPoint);
 
        float distance = Vector2.Distance(transform.anchoredPosition, localPoint);
        float duration = distance / speed;
 
        var tween = transform.DOAnchorPos(localPoint, duration).SetEase(Ease.Linear);
        tween.onComplete += () =>
        {
            onComplete?.Invoke();
        };
 
        return tween;
    }
 
    public static string DisplayDamageNum(long num, int attackType)
    {
        var config = DamageNumConfig.Get(attackType);
        var basePowerStr = UIHelper.ReplaceLargeArtNum(num);
        var result = string.Empty;
        for (int i = 0; i < basePowerStr.Length; i++)
        {
            var numChar = (char)GetDamageNumKey(config, basePowerStr[i]);
            if (numChar > 0)
            {
                result += numChar;
            }
        }
        return result;
    }
 
    public static string DisplayDamageNum(BattleDmg damage)
    {
        var config = DamageNumConfig.Get(damage.attackType);
 
        string result = string.Empty;
 
        //  如果是闪避或免疫 则只显示对应文字,不显示数字
        if (damage.IsType(DamageType.Dodge) || damage.IsType(DamageType.Immune))
        {
            result += (char)config.prefix;
        }
        else
        {
            result = ConvertToArtFont(config, damage.damage);
        }
 
        return result;
    }
 
    public static string ConvertToArtFont(DamageNumConfig config, float _num)
    {
        var stringBuild = new System.Text.StringBuilder();
 
        if (0 != config.plus)
            stringBuild.Append((char)config.plus);
        if (0 != config.prefix)
            stringBuild.Append((char)config.prefix);
 
        var chars = UIHelper.ReplaceLargeArtNum(_num);
        for (var i = 0; i < chars.Length; i++)
        {
            int numChar = GetDamageNumKey(config, (int)chars[i]);
 
            if (numChar > 0)
            {
                stringBuild.Append((char)numChar);
            }
        }
 
        return stringBuild.ToString();
    }
 
    public static int GetMainTargetPositionNum(SkillBase skillBase, BattleObject caster, List<HB427_tagSCUseSkill.tagSCUseSkillHurt> targetList, SkillConfig skillConfig)
    {
        int returnIndex = 0;
        //  根据敌方血量阵营 存活人数来选择
        BattleCamp battleCamp = skillConfig.TagFriendly != 0 ? caster.Camp : caster.GetEnemyCamp();
        List<BattleObject> targetObjList = caster.battleField.battleObjMgr.GetBattleObjList(battleCamp);
 
 
        // 瞄准的目标范围,如果目标个数为0则为范围内全部
        // 0    全部范围
        // 1    对位,默认只选1个
        // 2    前排
        // 3    后排
        // 4    纵排,按对位规则选择纵排
        // 5    自己,默认只选自己
 
        switch (skillConfig.TagAim)
        {
            case 0:
                // 0   全部范围:
                // 若TagCount目标个数为0或6,根据TagFriendly敌我配置,代表作用于敌方全体或我方全体,此时主目标为敌我站位中的2号位置
                // 若TagCount目标个数为1~5个,根据TagFriendly敌我+TagAffect细分目标配置,代表随机作用于敌方或我方x个武将,第一个为主目标
                if (skillConfig.TagCount == 0 || skillConfig.TagCount == 6)
                {
                    returnIndex = 1;
                }
                else
                {
                    uint objId = targetList[0].ObjID;
                    BattleObject target = caster.battleField.battleObjMgr.GetBattleObject((int)objId);
                    return target.teamHero.positionNum;
                }
                break;
            case 1:
                // 1    对位:
                // 默认只选1个,对位规则为A1优先打B1,A2优先打B2,A3优先打B3,对位目标死亡时,优先前排,比如B2已经死亡,那么A2将优先打B
                if (targetList.Count > 0)
                {
                    BattleObject battleObject = caster.battleField.battleObjMgr.GetBattleObject((int)targetList[0].ObjID);
                    if (battleObject != null)
                    {
                        returnIndex = battleObject.teamHero.positionNum;
                    }
                    else
                    {
                        Debug.LogError("GetMainTargetPositionNum 找不到目标 ObjId : " + targetList[0].ObjID);
                        returnIndex = 0;
                    }
                }
                else
                {
                    Debug.LogError("targetList 目标列表为空");
                    returnIndex = 0;
                }
                break;
            case 2:
                //  1、2、3号位为前排,默认2号位置为主目标,当1、2、3号位置角色全部死亡,前排将替换成后排,5号位置变更为主目标,
                //  若配置TagAffect细分目标,且人数小于3,则所有被选择目标均为主目标(施法位置会用客户端配置)
                // (即前排默认2号位或5号位规则无效,实际作用多少人就是多少个主目标) (YL : TagAffect>0 && TagAffect != 3就是全体都是主目标 后排一样 )
                if (skillConfig.TagAffect != 0 || skillConfig.TagCount < 3)
                {
                    uint objId = targetList[0].ObjID;
                    BattleObject target = caster.battleField.battleObjMgr.GetBattleObject((int)objId);
                    returnIndex = target.teamHero.positionNum;
                }
                else
                {
                    //  看看对面前排是否都活着
                    List<BattleObject> front = new List<BattleObject>(from bo in targetObjList where !bo.IsDead() && bo.teamHero.positionNum < 3 select bo);
                    if (front.Count > 0)
                    {
                        returnIndex = 1;
                    }
                    else
                    {
                        returnIndex = 4;
                    }
                }
                break;
            case 3:
                if (skillConfig.TagAffect != 0 || skillConfig.TagCount < 3)
                {
                    uint objId = targetList[0].ObjID;
                    BattleObject target = caster.battleField.battleObjMgr.GetBattleObject((int)objId);
                    returnIndex = target.teamHero.positionNum;
                }
                else
                {
                    //  看看对面后排是否都活着
                    List<BattleObject> back = new List<BattleObject>(from bo in targetObjList where !bo.IsDead() && bo.teamHero.positionNum >= 3 select bo);
                    if (back.Count > 0)
                    {
                        returnIndex = 4;
                    }
                    else
                    {
                        returnIndex = 1;
                    }
                }
                break;
            // 4    纵排,按对位规则选择纵排
            case 4:
                returnIndex = int.MaxValue;
                for (int i = 0; i < targetList.Count; i++)
                {
                    var hurt = targetList[i];
                    BattleObject target = caster.battleField.battleObjMgr.GetBattleObject((int)hurt.ObjID);
                    if (target == null)
                    {
                        Debug.LogError("GetMainTargetPositionNum 找不到目标 ObjId : " + hurt.ObjID);
                        continue;
                    }
                    else
                    {
                        returnIndex = Mathf.Min(returnIndex, target.teamHero.positionNum);
                    }
                }
                break;
            // 5    自己,默认只选自己
            case 5:
                returnIndex = caster.teamHero.positionNum;
                break;
            case 6:
                //  跟随主技能的目标
                var fromSkill = skillBase.fromSkill;
                if (fromSkill != null)
                {
                    returnIndex = GetMainTargetPositionNum(fromSkill, fromSkill.caster, fromSkill.tagUseSkillAttack.HurtList.ToList(), fromSkill.skillConfig);
                }
                else
                {
                    Debug.LogError("GetMainTargetPositionNum 找不到跟随主技能的目标");
                    returnIndex = 0;
                }
                break;
            default:
                Debug.LogError("暂时不支持其他的方式选择主目标 有需求请联系策划 技能id:" + skillConfig.SkillID + " TagAim " + skillConfig.TagAim);
                returnIndex = 0;
                break;
        }
 
        return returnIndex;
    }
 
    public static int GetDamageNumKey(DamageNumConfig config, int _num)
    {
        if (_num == 46) return config.nums[10]; // '.'
        else if (_num == 107) return config.nums[11]; // 'k'
        else if (_num == 109) return config.nums[12]; // 'm'
        else if (_num == 98) return config.nums[13]; // 'b'
        else if (_num == 116) return config.nums[14]; // 't'
        int targetNum = _num - 48;
        if (targetNum >= config.nums.Length || targetNum < 0)
        {
            Debug.LogError("damage config " + config.TypeID + " _num is " + _num + " out of range");
            return _num;
        }
        return config.nums[_num - 48];
    }
 
    public static bool IsHealing(HB427_tagSCUseSkill.tagSCUseSkillHurt hurt)
    {
        return ((hurt.AttackTypes & (int)ServerDamageType.Recovery) != 0 || 
                          (hurt.AttackTypes & (int)ServerDamageType.DamageReverse) != 0) &&
                         (hurt.AttackTypes & (int)ServerDamageType.Damage) == 0 &&
                         (hurt.AttackTypes & (int)ServerDamageType.Realdamage) == 0 &&
                         (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;
            for (int k = 0; k < hurt.HurtListEx.Length; k++)
            {
                var hurtEx = hurt.HurtListEx[k];
                totalSuckHp += hurtEx.SuckHP;
            }
        }
        for (int i = 0; i < hB427_TagSCUseSkill.HurtListEx.Length; i++)
        {
            var hurt = hB427_TagSCUseSkill.HurtListEx[i];
            totalSuckHp += hurt.SuckHP;
        }
        return totalSuckHp;
    }
 
    public static BattleHurtParam CalcBattleHurtParam(SkillBase skillBase, int hitIndex, BattleObject target, 
        HB427_tagSCUseSkill.tagSCUseSkillHurt hurt, BattleDrops battleDrops, BattleDeadPack 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.hB427_TagSCUseSkill = skillBase.tagUseSkillAttack;
        battleHurtParam.hitIndex = hitIndex;
        battleHurtParam.deadPack = deadPack;
        battleHurtParam.skillConfig = skillConfig;
        battleHurtParam.packUID = skillBase.tagUseSkillAttack.packUID;
        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;
                    target.teamHero.curHp = hurter.toHp; // 保持一致性
                }
                else
                {
                    long remainingDamage = currentHitDamage - hurter.fromShieldValue;
                    hurter.toHp = Math.Max(0, hurter.fromHp - remainingDamage);
                    target.teamHero.curHp = hurter.toHp; // 保持一致性
                }
            }
        }
 
        // 计算护盾变化
        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;
        skillBase.caster.teamHero.curHp = caster.toHp; // 保持一致性
        
        return caster;
    }
    
    /// <summary>
    /// 将整个技能的总伤害按命中次数和分段配置分配
    /// </summary>
    /// <param name="damageDivideList">整个技能的所有命中分段配置</param>
    /// <param name="hitIndex">当前是第几击(从0开始)</param>
    /// <param name="totalDamage">整个技能的总伤害</param>
    /// <returns>这一击内每一段的伤害值列表</returns>
    public static List<long> DivideDamageToList(int[][] damageDivideList, int hitIndex, long totalDamage, ref long currentHitDamage)
    {
        if (totalDamage <= 0)
        {
            return new List<long>{};
        }
 
        if (damageDivideList == null || damageDivideList.Length == 0)
        {
            Debug.LogError("damageDivideList 为空或长度为0");
            return new List<long> { totalDamage };
        }
 
        if (hitIndex < 0 || hitIndex >= damageDivideList.Length)
        {
            Debug.LogError($"hitIndex={hitIndex} 超出范围, damageDivideList.Length={damageDivideList.Length}");
            return new List<long> { totalDamage };
        }
 
        int[] currentHitDivide = damageDivideList[hitIndex];
        if (currentHitDivide == null || currentHitDivide.Length == 0)
        {
            Debug.LogError($"damageDivide[{hitIndex}] 为空或长度为0");
            return new List<long> { totalDamage };
        }
 
        // ============ 第一步: 计算每一击应该造成的伤害 ============
        // 先计算所有击的总权重
        int totalWeight = 0;
        for (int i = 0; i < damageDivideList.Length; i++)
        {
            if (damageDivideList[i] != null && damageDivideList[i].Length > 0)
            {
                // 每一击的权重是其所有分段之和
                for (int j = 0; j < damageDivideList[i].Length; j++)
                {
                    totalWeight += damageDivideList[i][j];
                }
            }
        }
 
        if (totalWeight == 0)
        {
            Debug.LogError("totalWeight 为 0");
            return new List<long> { totalDamage };
        }
 
        // 计算当前这一击的权重
        int currentHitWeight = 0;
        for (int i = 0; i < currentHitDivide.Length; i++)
        {
            currentHitWeight += currentHitDivide[i];
        }
 
        // 计算当前这一击应该造成的总伤害
        long currentHitTotalDamage;
        bool isLastHit = hitIndex >= damageDivideList.Length - 1;
        
        if (isLastHit)
        {
            // 最后一击: 计算前面所有击已经造成的伤害,剩余的全部给最后一击
            long previousHitsDamage = 0;
            for (int i = 0; i < hitIndex; i++)
            {
                if (damageDivideList[i] != null)
                {
                    int hitWeight = 0;
                    for (int j = 0; j < damageDivideList[i].Length; j++)
                    {
                        hitWeight += damageDivideList[i][j];
                    }
                    previousHitsDamage += (long)((float)totalDamage * (float)hitWeight / (float)totalWeight);
                }
            }
            currentHitTotalDamage = totalDamage - previousHitsDamage;
        }
        else
        {
            // 非最后一击: 按权重计算
            currentHitTotalDamage = (long)((float)totalDamage * (float)currentHitWeight / (float)totalWeight);
        }
 
        currentHitDamage = currentHitTotalDamage;
 
        // ============ 第二步: 将当前这一击的伤害分配到各分段 ============
        List<long> fixedDamageList = new List<long>();
        long accumulatedDamage = 0;
 
        for (int i = 0; i < currentHitDivide.Length; i++)
        {
            long damage;
            
            // 当前击的最后一段进行误差补偿
            if (i == currentHitDivide.Length - 1)
            {
                damage = currentHitTotalDamage - accumulatedDamage;
            }
            else
            {
                // 按当前击的权重分配
                damage = (long)((float)currentHitTotalDamage * (float)currentHitDivide[i] / (float)currentHitWeight);
                accumulatedDamage += damage;
            }
            
            fixedDamageList.Add(damage);
        }
 
        return fixedDamageList;
    }
 
    /// <summary>
    /// 保证所有分配项加起来等于totalDamage,避免因整除导致的误差
    /// </summary>
    public static List<long> DivideDamageToList(int[] damageDivide, long totalDamage)
    {
        if (damageDivide == null || damageDivide.Length == 0)
        {
            Debug.LogError("damageDivide 为空或长度为0");
            return new List<long> { totalDamage };
        }
 
        List<long> fixedDamageList = new List<long>();
        long accumulatedDamage = 0; // 累计已分配的伤害
 
        for (int i = 0; i < damageDivide.Length; i++)
        {
            long damage;
            
            // 最后一次分配:用总伤害减去已分配的伤害,确保总和精确
            if (i == damageDivide.Length - 1)
            {
                damage = totalDamage - accumulatedDamage;
            }
            else
            {
                // 计算当前分段伤害(向下取整)
                damage = (long)((float)totalDamage * (float)damageDivide[i] / 10000f);
                accumulatedDamage += damage;
            }
            
            fixedDamageList.Add(damage);
        }
 
        return fixedDamageList;
    }
    
    public static HB419_tagSCObjHPRefresh FindObjHPRefreshPack(List<GameNetPackBasic> packList)
    {
        for (int i = 0; i < packList.Count; i++)
        {
            var pack = packList[i];
            if (pack is HB419_tagSCObjHPRefresh hpRefreshPack)
            {
                return hpRefreshPack;
            }
            else if (pack is CustomHB426CombinePack)
            {
                break;
            }
        }
        return null;
    }
 
    public static List<BattleDeadPack> FindDeadPack(List<GameNetPackBasic> packList)
    {
        List<BattleDeadPack> deadPacks = new List<BattleDeadPack>();
        for (int i = 0; i < packList.Count; i++)
        {
            var pack = packList[i];
            //    寻找死亡包 找到死亡包之后要找掉落包 不能超过技能包
            if (pack is HB422_tagMCTurnFightObjDead deadPack)
            {
                deadPacks.Add(new BattleDeadPack { deadPack = deadPack });
            }
            else if (pack is CustomHB426CombinePack combinePack)
            {
                if (deadPacks.Count > 0)
                {
                    HB427_tagSCUseSkill hB427_TagSCUseSkill = combinePack.GetMainHB427SkillPack();
 
                    if (null == hB427_TagSCUseSkill)
                    {
                        break;
                    }
                    else
                    {
                        var skillID = hB427_TagSCUseSkill.SkillID;
                        var skillConfig = SkillConfig.Get((int)skillID);
 
                        if ((SkillTriggerTiming)skillConfig.ClientTriggerTiming == SkillTriggerTiming.Death)
                        {
                            for (int j = 0 ; j < deadPacks.Count; j ++)
                            {
                                var parentPack = deadPacks[j];
 
                                if (parentPack.IsOwnSkill(hB427_TagSCUseSkill))
                                {
                                    parentPack.SetDeathSkill(combinePack);
                                    break;
                                }
                            }
                        }
                        else
                        {
                            break;
                        }
                    }
                }
                else
                {
                    break;
                }
            }
        }
        // Debug.LogError("find dead pack " + deadPacks.Count);
        return deadPacks;
    }
 
 
    public static List<HB423_tagMCTurnFightObjReborn> FindRebornPack(List<GameNetPackBasic> packList)
    {
        List<HB423_tagMCTurnFightObjReborn> rebornPackList = new List<HB423_tagMCTurnFightObjReborn>();
        for (int i = 0; i < packList.Count; i++)
        {
            var pack = packList[i];
            //    寻找死亡包 找到死亡包之后要找掉落包 不能超过技能包
            if (pack is HB423_tagMCTurnFightObjReborn rebornPack)
            {
                rebornPackList.Add(rebornPack);
            }
            else if (pack is CustomHB426CombinePack)
            {
                break;
            }
        }
        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
    }
}