yyl
2026-04-22 b8f554f03a6114db353736741eda63bdd6c63854
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
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
 
// SkillBase(Finish 部分):完成判定与强制结束。
public partial class SkillBase
{
    public virtual bool IsActionCompleted()
    {
        if (!isPlay) return false;
 
        if (skillEffect != null)
        {
            if (!skillEffect.IsFinished()) return false;
        }
 
        if (moveFinished)
        {
            //  如果技能有动画(SkillMotionName不为空),需要等待动画播放完成
            if (skillSkinConfig != null && !string.IsNullOrEmpty(skillSkinConfig.SkillMotionName))
            {
                if (!isMotionCompleted)
                {
                    BattleDebug.LogError($"SkillBase.IsActionCompleted: 技能 {skillConfig.SkillID} 等待动画播放完成");
                    return false;
                }
            }
 
            return true;
        }
 
        return false;
    }
 
    // 检查技能是否完成:综合检查所有完成条件
    public virtual bool IsFinished()
    {
        if (!isPlay)
        {
            ReportStuckIfNeeded("isPlay=false(OnSkillStart 未被调用或提前退出)");
            return false;
        }
 
        bool tempRetValue = true;
 
        // 检查技能效果是否完成
        if (skillEffect != null)
        {
            if (!skillEffect.IsFinished())
            {
                ReportStuckIfNeeded("skillEffect 未完成");
                return false;
            }
            skillEffect = null;
            OnSkillFinished();
            tempRetValue = false;
        }
 
        // 检查其他技能动作是否完成
        if (currentWaitingSkill.Count > 0)
        {
            if (currentWaitingSkill.Any(s => s.IsFinished()))
            {
                currentWaitingSkill.RemoveAll(s => s.IsFinished());
                OnSkillFinished();
            }
            else
            {
                tempRetValue = false;
            }
        }
 
        if (!tempRetValue)
        {
            if (currentWaitingSkill.Count > 0)
            {
                ReportStuckIfNeeded($"currentWaitingSkill 仍有 {currentWaitingSkill.Count} 个子动作");
            }
            return false;
        }
 
 
        // 检查最终完成状态
        if (isFinished && moveFinished)
        {
            if (packList.Count > 0)
            {
                OnSkillFinished();
                ReportStuckIfNeeded($"packList 仍剩 {packList.Count} 个包,ResolvePackList 未消费完");
                return false;
            }
 
            //  如果自己内部的recora action的 inner record player还有没执行完的包 也是返回false
            if (ownRecordAction != null && ownRecordAction.GetInnerRecordPlayer().IsPlaying())
            {
                ReportStuckIfNeeded("ownRecordAction.innerRecordPlayer.IsPlaying() == true(子 RecordAction 未播完)");
                return false;
            }
 
            // 技能完全结束,移除技能注册并触发延迟的死亡判定
            //  useInnerPlayer=true:DeathRecordAction 投到 ownRecordAction.innerRecordPlayer,等待当前技能完成
            //  clearEvenIfNotDispatched=false:保持原有行为,OnObjsDead 返回 null 时不清空缓存
            if (FlushPendingDeathActions(useInnerPlayer: true, clearEvenIfNotDispatched: false))
            {
                ReportStuckIfNeeded("FlushPendingDeathActions 投递了 DeathRecordAction,等待其播放完成");
                return false;
            }
 
            bool done = !ownRecordAction.GetInnerRecordPlayer().IsPlaying();
            if (!done) ReportStuckIfNeeded("末尾 innerRecordPlayer.IsPlaying() == true");
            else       ResetStuckCounter();
            return done;
        }
 
        ReportStuckIfNeeded($"isFinished={isFinished} moveFinished={moveFinished}(还没到末段)");
        return false;
    }
 
#if UNITY_EDITOR
    //  卡死侦测计数器:IsFinished 连续多少次返回 false。
    //  第 StuckThreshold 次首次 dump,之后每 StuckRepeatInterval 次再 dump,避免刷屏。
    private int _stuckCheckCount = 0;
    private string _lastStuckReason = null;
    private const int StuckThreshold = 120;        // ~2 秒(60fps)
    private const int StuckRepeatInterval = 180;   // ~3 秒
#endif
 
    /// <summary>
    /// 当 IsFinished 持续返回 false 时打一次详细诊断。reason 描述阻塞原因。
    /// 只在 UNITY_EDITOR + BattleDebug 开关下输出。
    /// </summary>
    private void ReportStuckIfNeeded(string reason)
    {
#if UNITY_EDITOR
        _stuckCheckCount++;
 
        //  阻塞原因切换了 → 立刻打一次,并重置计数
        bool reasonChanged = _lastStuckReason != reason;
        if (reasonChanged)
        {
            _lastStuckReason = reason;
            _stuckCheckCount = 1;
        }
 
        bool firstHit = _stuckCheckCount == StuckThreshold;
        bool repeat   = _stuckCheckCount > StuckThreshold
                     && (_stuckCheckCount - StuckThreshold) % StuckRepeatInterval == 0;
 
        if (!firstHit && !repeat) return;
 
        int skillId = skillConfig != null ? skillConfig.SkillID : 0;
        ulong casterId = tagUseSkillAttack != null ? tagUseSkillAttack.ObjID : 0UL;
 
        string subSkillDump = "  (无)";
        if (currentWaitingSkill.Count > 0)
        {
            var lines = new List<string>();
            for (int i = 0; i < currentWaitingSkill.Count; i++)
            {
                var s = currentWaitingSkill[i];
                lines.Add($"    [{i}] type={s.GetType().Name} IsFinished={s.IsFinished()}");
            }
            subSkillDump = string.Join("\n", lines);
        }
 
        string packListDump = "  (无)";
        if (packList != null && packList.Count > 0)
        {
            var lines = new List<string>();
            for (int i = 0; i < packList.Count && i < 8; i++)
            {
                var p = packList[i];
                string tag = p is CustomHB426CombinePack cb ? $" tag={cb.startTag?.Tag}" : "";
                lines.Add($"    [{i}] {p.GetType().Name}{tag}");
            }
            if (packList.Count > 8) lines.Add($"    ... 还有 {packList.Count - 8} 个");
            packListDump = string.Join("\n", lines);
        }
 
        bool innerPlaying = ownRecordAction != null && ownRecordAction.GetInnerRecordPlayer().IsPlaying();
 
        string innerPlayerDump = "  (innerRecordPlayer: 无)";
        if (ownRecordAction != null)
        {
            innerPlayerDump = "  innerRecordPlayer: " + ownRecordAction.GetInnerRecordPlayer().DumpPlayingState();
        }
 
        //  额外诊断:caster 动画状态 + SkillEffect 内部标志位 + 技能动作名
        string casterAnim = "  (caster 信息不可用)";
        if (caster is HeroBattleObject hbo && hbo.motionBase != null)
        {
            casterAnim = $"  caster.motionBase: playingSkillWithAnim={hbo.motionBase.PlayingSkillWithAnimForDebug}";
        }
        string skinInfo = $"  skillSkinConfig.SkillMotionName={(skillSkinConfig == null ? "null" : (string.IsNullOrEmpty(skillSkinConfig.SkillMotionName) ? "(空)" : skillSkinConfig.SkillMotionName))}";
        string skillEffectDump = skillEffect == null ? "  skillEffect=null" : $"  skillEffect: {skillEffect.DumpState()}";
 
        BattleDebug.LogError(
            "SkillBase.IsFinished 疑似卡死 (持续 " + _stuckCheckCount + " 次未完成)\n" +
            $"  skillId={skillId} caster={casterId} 原因: {reason}\n" +
            $"  StateFlags={_stateFlags}\n" +
            $"{skillEffectDump}\n" +
            $"{skinInfo}\n" +
            $"{casterAnim}\n" +
            $"  currentWaitingSkill.Count={currentWaitingSkill.Count}\n{subSkillDump}\n" +
            $"  packList.Count={(packList?.Count ?? 0)}\n{packListDump}\n" +
            $"  innerRecordPlayer.IsPlaying={innerPlaying}\n" +
            $"{innerPlayerDump}\n" +
            $"  tempDeadPackList.Count={tempDeadPackList.Count}\n" +
            $"  buffPackCollections.Count={buffPackCollections.Count}");
#endif
    }
 
    private void ResetStuckCounter()
    {
#if UNITY_EDITOR
        _stuckCheckCount = 0;
        _lastStuckReason = null;
#endif
    }
 
 
    // 强制结束技能:立即结束所有技能相关的处理
    public virtual void ForceFinished()
    {
        if (isFinished)
            return;
 
        //  强制结束路径:移除注册 + 投递死亡动作(不等技能完成,直接播放)+ 始终清空缓存。
        //  useInnerPlayer=false 保持原 ForceFinished 的行为(不指定 _playSkillRecordAction)。
        //  clearEvenIfNotDispatched=true 保持 ForceFinished 原有的"无论是否投递都 Clear"语义。
        FlushPendingDeathActions(useInnerPlayer: false, clearEvenIfNotDispatched: true);
 
        // 1. 强制结束技能效果
        skillEffect?.ForceFinished();
        skillEffect = null;
 
        // 2. 强制结束所有子技能动作
        if (currentWaitingSkill.Count > 0)
        {
            foreach (var skill in currentWaitingSkill)
            {
                skill.ForceFinish();
            }
            currentWaitingSkill.Clear();
        }
 
        // 3. 清理 DOTween 动画(防止移动回调在战斗结束后执行)
        if (caster != null)
        {
            caster.StopMoveAnimation();
        }
 
        // 4. 重置施法者状态
        if (caster != null)
        {
            // 重置位置到原点
            caster.ResetPosition();
 
            // 重置朝向
            caster.ResetFacing();
 
            // 取消幻影效果
            caster.ShowIllusionShadow(false);
        }
 
        // 5. 恢复 UI 状态
        if (battleField != null)
        {
            // 恢复所有角色的显示层级和血条
            var allList = battleField.battleObjMgr?.allBattleObjDict?.Values;
            if (allList != null)
            {
                foreach (BattleObject bo in allList)
                {
                    bo.layerMgr?.SetFront();
                    bo.GetHeroInfoBar()?.SetActive(true);
                }
            }
 
            // 关闭技能遮罩
            if (battleField.battleRootNode != null && battleField.battleRootNode.skillMaskNode != null)
            {
                battleField.battleRootNode.skillMaskNode.SetActive(false);
            }
        }
 
        isFinished = true;
        moveFinished = true;
        isPlay = true;
 
        //  强制结束时,无论是否有动画,都标记动画完成
        isMotionCompleted = true;
 
        // 6. 处理所有剩余包(包括 buff 包)
        // 先处理 buffPackCollections
        DistributeBuffPacks(buffPackCollections);
        buffPackCollections.Clear();
 
        // 处理剩余的 packList
        while (packList.Count > 0)
        {
            var pack = packList[0];
            packList.RemoveAt(0);
 
            if (pack is CustomHB426CombinePack combinePack && combinePack.startTag.Tag.StartsWith("Skill_"))
            {
                var otherSkillAction = combinePack.CreateSkillAction();
                otherSkillAction.fromSkill = this;
                otherSkillAction.ForceFinish();
            }
            else
            {
                // 【使用 parentRecordAction.innerRecordPlayer】
                // 原因:ForceFinished时剩余的包也是技能内部产生的,应该由innerRecordPlayer管理
                // 这样可以确保即使强制结束,包的处理也在正确的上下文中
                PackageRegedit.Distribute(pack);
            }
        }
    }
 
    // 技能完成处理:正常完成时的清理工作
    public void OnSkillFinished()
    {
        // 修复:使用循环代替递归,避免栈溢出风险
        while (true)
        {
            // 验证技能效果是否完成
            if (skillEffect != null && !skillEffect.IsFinished())
                return;
 
            if (skillEffect != null)
            {
                skillEffect = null;
                continue; // 使用continue代替递归调用
            }
 
            // 验证其他技能动作是否完成
            if (currentWaitingSkill.Count > 0)
            {
                bool hasFinishedAction = currentWaitingSkill.All(s => s.IsFinished());
 
                if (hasFinishedAction)
                {
                    // 修复死循环:完成后需要清空 currentWaitingSkill
                    currentWaitingSkill.Clear();
                    continue; // 使用continue代替递归调用
                }
                return;
            }
 
            break; // 没有更多需要处理的,退出循环
        }
 
        // 处理剩余包
        if (!ResolvePackList())
        {
            return;
        }
 
        isFinished = true;
    }
 
    protected virtual bool ResolvePackList()
    {
        if (currentWaitingSkill.Count > 0)
        {
            return false;
        }
 
        while (packList.Count > 0)
        {
            var pack = packList[0];
            packList.RemoveAt(0);
 
 
            if (pack is CustomHB426CombinePack combinePack && combinePack.startTag.Tag.StartsWith("Skill_"))
            {
                var skillRecordAction = combinePack.CreateSkillAction();
                skillRecordAction.fromSkill = this;
                currentWaitingSkill.Add(skillRecordAction);
 
                //  需要给真正parent播的
                if (skillRecordAction.useParentRecordPlayer && skillRecordAction.parentSkillAction != null)
                {
                    skillRecordAction.parentSkillAction.GetInnerRecordPlayer().PlayRecord(skillRecordAction);
                }
                else
                {
                    ownRecordAction.GetInnerRecordPlayer().PlayRecord(skillRecordAction);
                }
 
                return false;
            }
            else if (IsBuffPack(pack))
            {
                // 从找到第一个 Buff 包开始,收集连续的 HB428/HB429 包
                buffPackCollections.Add(pack);
                while (packList.Count > 0)
                {
                    var nextPack = packList[0];
                    if (IsBuffPack(nextPack))
                    {
                        buffPackCollections.Add(nextPack);
                        packList.RemoveAt(0);
                    }
                    else
                    {
                        break;
                    }
                }
 
                // 处理所有收集到的 buff 包
                ProcessBuffPacks(buffPackCollections);
 
                // 清空已处理的 buff 集合
                buffPackCollections.Clear();
                continue;
            }
            else
            {
                // 【使用 parentRecordAction.innerRecordPlayer】
                // 原因:技能执行过程中的包(Buff、属性刷新等)是技能效果的一部分
                // 应该由SkillRecordAction的innerRecordPlayer管理,确保与技能生命周期一致
                PackageRegeditEx.DistributeToRecordAction(pack, ownRecordAction);
            }
        }
 
        return true;
    }
 
    // 添加清理方法:防止内存泄漏
    public virtual void Cleanup()
    {
        tempDropList?.Clear();
        tempDeadPackList?.Clear();
        currentWaitingSkill?.Clear();
        dropPackList?.Clear();
        expPackList?.Clear();
        buffPackCollections?.Clear();
 
        skillEffect = null;
        packList = null;
    }
 
    public virtual bool CanStartExecution()
    {
        if (null == caster)
        {
            return false;
        }
 
        if (null == skillConfig)
        {
            return false;
        }
 
        if (string.IsNullOrEmpty(skillSkinConfig.SkillMotionName))
        {
            return true;
        }
 
        return !battleField.IsCastingSkill(caster.ObjID);
    }
}