@cc-component/cc-fly 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/.cc-ex-component.md +12 -0
  2. package/assets/fly/Interface.ts +32 -0
  3. package/assets/fly/Interface.ts.meta +9 -0
  4. package/assets/fly/base/BuffUI.ts +68 -0
  5. package/assets/fly/base/BuffUI.ts.meta +9 -0
  6. package/assets/fly/base/FlyArrowLauncher.ts +166 -0
  7. package/assets/fly/base/FlyArrowLauncher.ts.meta +9 -0
  8. package/assets/fly/base/FlyBaseBullet.ts +9 -0
  9. package/assets/fly/base/FlyBaseBullet.ts.meta +9 -0
  10. package/assets/fly/base/FlyBaseHit.ts +10 -0
  11. package/assets/fly/base/FlyBaseHit.ts.meta +9 -0
  12. package/assets/fly/base/FlyBaseHp.ts +96 -0
  13. package/assets/fly/base/FlyBaseHp.ts.meta +9 -0
  14. package/assets/fly/base/FlyBasePlayer.ts +359 -0
  15. package/assets/fly/base/FlyBasePlayer.ts.meta +9 -0
  16. package/assets/fly/base/FlyDamageLabel.ts +198 -0
  17. package/assets/fly/base/FlyDamageLabel.ts.meta +9 -0
  18. package/assets/fly/base/FlyDamageLabelManager.ts +111 -0
  19. package/assets/fly/base/FlyDamageLabelManager.ts.meta +9 -0
  20. package/assets/fly/base/FlyModule.ts +145 -0
  21. package/assets/fly/base/FlyModule.ts.meta +9 -0
  22. package/assets/fly/base/FlyNodePoolUtil.ts +56 -0
  23. package/assets/fly/base/FlyNodePoolUtil.ts.meta +9 -0
  24. package/assets/fly/base/FlyPlayerBuffSystem.ts +673 -0
  25. package/assets/fly/base/FlyPlayerBuffSystem.ts.meta +9 -0
  26. package/assets/fly/base/FlyPooledParticleEffect.ts +58 -0
  27. package/assets/fly/base/FlyPooledParticleEffect.ts.meta +9 -0
  28. package/assets/fly/base/FlySkiiManager.ts +19 -0
  29. package/assets/fly/base/FlySkiiManager.ts.meta +9 -0
  30. package/assets/fly/base.meta +9 -0
  31. package/assets/fly.meta +9 -0
  32. package/assets/pathfinding.meta +9 -0
  33. package/assets.meta +9 -0
  34. package/index.ts +19 -0
  35. package/index.ts.meta +9 -0
  36. package/package.json +18 -5
  37. package/package.json.meta +11 -0
  38. package/README.md +0 -3
@@ -0,0 +1,673 @@
1
+ import { _decorator, Component } from 'cc';
2
+
3
+ const { ccclass } = _decorator;
4
+
5
+ export enum PlayerBuffType {
6
+ /** value 为伤害倍率,例如 2 表示双倍伤害;chance 为触发概率。 */
7
+ CriticalDamage,
8
+ /** value 为固定增加的攻击伤害。 */
9
+ FlatDamage,
10
+ /** value 为吸血比例,范围 0 到 1。 */
11
+ Lifesteal,
12
+ /** value 为伤害加成比例,范围 0 到 1。 */
13
+ PercentDamage,
14
+ /** value 为固定增加的最大生命值。 */
15
+ MaxHealth,
16
+ /** value 为攻速加成比例,例如 0.5 表示提高 50%。 */
17
+ AttackSpeed,
18
+ /** value 为额外暴击倍率,例如 0.5 会让 2 倍暴击提高到 2.5 倍。 */
19
+ CriticalDamageBonus,
20
+ /** value 为物理防御数值。 */
21
+ PhysicalDefense,
22
+ /** value 为魔法防御数值。 */
23
+ MagicDefense,
24
+ /** value 为目标最大生命值伤害比例,例如 0.1 表示额外造成 10%。 */
25
+ MaxHealthDamage,
26
+ /** value 为固定物理防御穿透点数。 */
27
+ PhysicalPenetration,
28
+ /** value 为固定魔法防御穿透点数。 */
29
+ MagicPenetration,
30
+ /** 兼容用通用百分比穿透,物理和魔法攻击都会生效。 */
31
+ PercentPenetration,
32
+ /** value 为物理百分比穿透比例,例如 0.2 表示穿透 20% 物防。 */
33
+ PhysicalPercentPenetration,
34
+ /** value 为魔法百分比穿透比例,例如 0.2 表示穿透 20% 魔防。 */
35
+ MagicPercentPenetration,
36
+ /** value 为伤害减免比例,例如 0.2 表示减少 20% 伤害。 */
37
+ DamageReduction,
38
+ /** value 为物理伤害减免比例。 */
39
+ PhysicalDamageReduction,
40
+ /** value 为魔法伤害减免比例。 */
41
+ MagicDamageReduction,
42
+ /** value 为闪避概率,例如 0.2 表示有 20% 概率完全闪避。 */
43
+ Dodge,
44
+ /** value 为物理攻击闪避概率。 */
45
+ PhysicalDodge,
46
+ /** value 为魔法攻击闪避概率。 */
47
+ MagicDodge,
48
+ /** value 为格挡减伤比例;chance 为格挡触发概率。 */
49
+ Block,
50
+ /** value 为生命恢复量;有 duration 时按每秒恢复,否则立即恢复。 */
51
+ CurrentHealth,
52
+ /** value 为每秒伤害,duration 为持续秒数,chance 为施加概率。 */
53
+ DamageOverTime,
54
+ /** 目标身上的限时 DoT 层。 */
55
+ DamageOverTimeEffect,
56
+ /** value 为移动速度加成比例,例如 0.2 表示提高 20%。 */
57
+ MovementSpeed,
58
+ /** value 为移动速度降低比例,范围 0 到 1。 */
59
+ Slow,
60
+ /** value 为单次攻击命中的目标总数,包含主目标。 */
61
+ AttackTargetCount,
62
+ /** value 为以主目标为圆心的范围攻击半径,单位为 Cocos 世界坐标。 */
63
+ AreaAttackRadius,
64
+ /** value 为攻击距离,单位为 Cocos 世界坐标。 */
65
+ AttackDistance,
66
+ }
67
+
68
+ export enum PlayerDamageType {
69
+ Physical,
70
+ Magic,
71
+ }
72
+
73
+ /** Player Buff 配置;只需填写对应 Buff 使用的属性。 */
74
+ export interface PlayerBuffOptions {
75
+ type: PlayerBuffType | any;
76
+ value: number;
77
+ chance?: number;
78
+ duration?: number;
79
+ damageType?: PlayerDamageType;
80
+ /** CurrentHealth 每次实际恢复生命后调用,参数为实际恢复量。 */
81
+ onApplied?: (healedAmount: number) => void;
82
+ }
83
+
84
+ /** 供 UI 展示的单层 Buff 信息。 */
85
+ export interface PlayerBuffDescription {
86
+ type: PlayerBuffType;
87
+ description: string;
88
+ value: number;
89
+ chance?: number;
90
+ duration?: number;
91
+ damageType?: PlayerDamageType;
92
+ }
93
+
94
+ /** 一次攻击结算后交给命中逻辑处理的结果。 */
95
+ export interface PlayerBuffAttackResult {
96
+ damage: number;
97
+ isCritical: boolean;
98
+ lifestealAmount: number;
99
+ }
100
+
101
+ /** 一次攻击的多目标与范围配置;未配置的字段为 undefined。 */
102
+ export interface PlayerAttackTargetConfig {
103
+ targetCount?: number;
104
+ areaAttackRadius?: number;
105
+ }
106
+
107
+ interface PlayerBuffData {
108
+ value: number;
109
+ chance: number;
110
+ duration?: number;
111
+ expireCallback?: () => void;
112
+ onExpire?: () => void;
113
+ onTick?: () => void;
114
+ }
115
+
116
+ interface PlayerDamageOverTimeBuff {
117
+ damagePerSecond: number;
118
+ chance: number;
119
+ duration: number;
120
+ damageType: PlayerDamageType;
121
+ }
122
+
123
+ @ccclass('FlyPlayerBuffSystem')
124
+ export class FlyPlayerBuffSystem extends Component {
125
+ private buffs = new Map<PlayerBuffType, PlayerBuffData[]>();
126
+ private isAttackCoolingDown = false;
127
+ /** 该角色攻击时触发的 DoT 配置,不代表其正在承受 DoT。 */
128
+ private damageOverTimeBuffs: PlayerDamageOverTimeBuff[] = [];
129
+
130
+ /** 添加一层攻击触发型 DoT 配置;同类配置可以并存。 */
131
+ public addDamageOverTimeBuff(
132
+ damagePerSecond: number,
133
+ chance: number,
134
+ duration: number | undefined,
135
+ damageType: PlayerDamageType,
136
+ ): void {
137
+ const buff = this.normalizeDamageOverTimeBuff(damagePerSecond, chance, duration, damageType);
138
+ if (buff) this.damageOverTimeBuffs.push(buff);
139
+ }
140
+
141
+ /** 替换所有攻击触发型 DoT 配置,只保留新配置。 */
142
+ public resetDamageOverTimeBuff(
143
+ damagePerSecond: number,
144
+ chance: number,
145
+ duration: number | undefined,
146
+ damageType: PlayerDamageType,
147
+ ): void {
148
+ const buff = this.normalizeDamageOverTimeBuff(damagePerSecond, chance, duration, damageType);
149
+ if (!buff) return;
150
+ this.damageOverTimeBuffs = [buff];
151
+ }
152
+
153
+ /** 校验并规范化 DoT 配置;参数无效时返回 null。 */
154
+ private normalizeDamageOverTimeBuff(
155
+ damagePerSecond: number,
156
+ chance: number,
157
+ duration: number | undefined,
158
+ damageType: PlayerDamageType,
159
+ ): PlayerDamageOverTimeBuff | null {
160
+ if (!Number.isFinite(damagePerSecond) || !Number.isFinite(chance)
161
+ || duration === undefined || !Number.isFinite(duration) || duration < 0) {
162
+ return null;
163
+ }
164
+
165
+ return {
166
+ damagePerSecond: Math.max(0, damagePerSecond),
167
+ chance: Math.max(0, Math.min(chance, 1)),
168
+ duration,
169
+ damageType,
170
+ };
171
+ }
172
+
173
+ /** 命中时逐层检定触发概率,并将成功的 DoT 效果重设到目标身上。 */
174
+ public applyDamageOverTimeOnHit(
175
+ target: FlyPlayerBuffSystem,
176
+ createTick: (damagePerSecond: number, damageType: PlayerDamageType) => () => void,
177
+ ): void {
178
+ for (const buff of this.damageOverTimeBuffs) {
179
+ if (Math.random() < buff.chance) {
180
+ target.resetBuff(
181
+ PlayerBuffType.DamageOverTimeEffect,
182
+ buff.damagePerSecond,
183
+ 1,
184
+ buff.duration,
185
+ undefined,
186
+ createTick(buff.damagePerSecond, buff.damageType),
187
+ );
188
+ }
189
+ }
190
+ }
191
+
192
+ /** 按概率立即恢复生命,或在指定持续时间内按秒恢复。 */
193
+ public applyCurrentHealthBuff(
194
+ value: number,
195
+ chance: number,
196
+ onApply: (amount: number) => void,
197
+ duration?: number,
198
+ reset = false,
199
+ ): void {
200
+ if (!Number.isFinite(value) || !Number.isFinite(chance)
201
+ || (duration !== undefined && (!Number.isFinite(duration) || duration < 0))) return;
202
+ if (Math.random() >= Math.max(0, Math.min(chance, 1))) return;
203
+
204
+ const amount = Math.max(0, value);
205
+ if (duration === undefined || duration === 0) {
206
+ onApply(amount);
207
+ return;
208
+ }
209
+
210
+ if (reset) {
211
+ this.resetBuff(
212
+ PlayerBuffType.CurrentHealth,
213
+ amount,
214
+ 1,
215
+ duration,
216
+ undefined,
217
+ () => onApply(amount),
218
+ );
219
+ } else {
220
+ this.addBuff(
221
+ PlayerBuffType.CurrentHealth,
222
+ amount,
223
+ 1,
224
+ duration,
225
+ undefined,
226
+ () => onApply(amount),
227
+ );
228
+ }
229
+ }
230
+
231
+ /** 获取所有持续生效的 Buff;同类型的不同层分别返回。 */
232
+ public getBuffDescriptions(): PlayerBuffDescription[] {
233
+ const descriptions: PlayerBuffDescription[] = [];
234
+
235
+ for (const [type, layers] of this.buffs) {
236
+ if (type === PlayerBuffType.DamageOverTimeEffect) continue;
237
+
238
+ for (const layer of layers) {
239
+ if (type === PlayerBuffType.CurrentHealth && !layer.onTick) continue;
240
+ descriptions.push({
241
+ type,
242
+ description: type === PlayerBuffType.CurrentHealth
243
+ ? `持续恢复生命(${layer.value}点/秒)`
244
+ : this.describeBuff(type, layer.value, layer.chance),
245
+ value: layer.value,
246
+ ...(
247
+ type === PlayerBuffType.CriticalDamage || type === PlayerBuffType.Block
248
+ ? { chance: layer.chance }
249
+ : {}
250
+ ),
251
+ ...(layer.duration !== undefined ? { duration: layer.duration } : {}),
252
+ });
253
+ }
254
+ }
255
+
256
+ for (const buff of this.damageOverTimeBuffs) {
257
+ descriptions.push({
258
+ type: PlayerBuffType.DamageOverTime,
259
+ description: `命中施加中毒(${buff.damagePerSecond}点/秒),触发率${Math.round(buff.chance * 100)}%`,
260
+ value: buff.damagePerSecond,
261
+ chance: buff.chance,
262
+ duration: buff.duration,
263
+ damageType: buff.damageType,
264
+ });
265
+ }
266
+
267
+ return descriptions;
268
+ }
269
+
270
+ private describeBuff(type: PlayerBuffType, value: number, chance: number): string {
271
+ const percent = (amount: number): string => `${amount * 100}%`;
272
+ switch (type) {
273
+ case PlayerBuffType.CriticalDamage: return `暴击伤害倍率(${value}倍),触发率${Math.round(chance * 100)}%`;
274
+ case PlayerBuffType.FlatDamage: return `固定攻击伤害(+${value})`;
275
+ case PlayerBuffType.Lifesteal: return `吸血(${percent(value)})`;
276
+ case PlayerBuffType.PercentDamage: return `伤害加成(${percent(value)})`;
277
+ case PlayerBuffType.MaxHealth: return `最大生命值(+${value})`;
278
+ case PlayerBuffType.AttackSpeed: return `攻速加成(${percent(value)})`;
279
+ case PlayerBuffType.CriticalDamageBonus: return `额外暴击倍率(+${value})`;
280
+ case PlayerBuffType.PhysicalDefense: return `物理防御(${value})`;
281
+ case PlayerBuffType.MagicDefense: return `魔法防御(${value})`;
282
+ case PlayerBuffType.MaxHealthDamage: return `最大生命伤害(${percent(value)})`;
283
+ case PlayerBuffType.PhysicalPenetration: return `物理固定穿透(${value})`;
284
+ case PlayerBuffType.MagicPenetration: return `魔法固定穿透(${value})`;
285
+ case PlayerBuffType.PercentPenetration: return `通用百分比穿透(${percent(value)})`;
286
+ case PlayerBuffType.PhysicalPercentPenetration: return `物理百分比穿透(${percent(value)})`;
287
+ case PlayerBuffType.MagicPercentPenetration: return `魔法百分比穿透(${percent(value)})`;
288
+ case PlayerBuffType.DamageReduction: return `伤害减免(${percent(value)})`;
289
+ case PlayerBuffType.PhysicalDamageReduction: return `物理伤害减免(${percent(value)})`;
290
+ case PlayerBuffType.MagicDamageReduction: return `魔法伤害减免(${percent(value)})`;
291
+ case PlayerBuffType.Dodge: return `闪避率(${percent(value)})`;
292
+ case PlayerBuffType.PhysicalDodge: return `物理闪避率(${percent(value)})`;
293
+ case PlayerBuffType.MagicDodge: return `魔法闪避率(${percent(value)})`;
294
+ case PlayerBuffType.Block: return `格挡减伤(${percent(value)}),触发率${Math.round(chance * 100)}%`;
295
+ case PlayerBuffType.DamageOverTimeEffect: return `中毒伤害(${value}点/秒)`;
296
+ case PlayerBuffType.DamageOverTime: return `命中施加中毒(${value}点/秒)`;
297
+ case PlayerBuffType.MovementSpeed: return `移动速度加成(${percent(value)})`;
298
+ case PlayerBuffType.Slow: return `移动速度降低(${percent(value)})`;
299
+ case PlayerBuffType.AttackTargetCount: return `攻击目标数量(${value}个,包含主目标)`;
300
+ case PlayerBuffType.AreaAttackRadius: return `范围攻击半径(${value})`;
301
+ case PlayerBuffType.CurrentHealth: return `即时生命恢复(${value})`;
302
+ case PlayerBuffType.AttackDistance: return `攻击距离(${value})`;
303
+
304
+
305
+
306
+
307
+ default: return `Buff(${value})`;
308
+ }
309
+ }
310
+
311
+ /** 移动速度加成与减速相抵后得到的移动倍率,最低为 0。 */
312
+ public getMovementSpeedMultiplier(): number {
313
+ return Math.max(
314
+ 0,
315
+ 1 + this.getBuffValue(PlayerBuffType.MovementSpeed) - this.getBuffValue(PlayerBuffType.Slow),
316
+ );
317
+ }
318
+
319
+ /** 添加 Buff;同类型数值累加,duration 以秒为单位且到期只移除本层。
320
+ * type: Buff 类型 any是自定义类型
321
+ * value: Buff 数值
322
+ * chance: Buff 触发概率
323
+ * duration: Buff 持续时间(秒)
324
+ * onExpire: Buff 到期回调
325
+ * onTick: Buff 每秒触发回调
326
+ */
327
+ public addBuff(
328
+ type: PlayerBuffType | any,
329
+ value: number,
330
+ chance = 1,
331
+ duration?: number,
332
+ onExpire?: () => void,
333
+ onTick?: () => void,
334
+ ): void {
335
+ const data = this.normalizeBuffData(type, value, chance);
336
+ if (!data) return;
337
+
338
+ if (duration !== undefined && (!Number.isFinite(duration) || duration < 0)) return;
339
+ const layers = this.buffs.get(type) ?? [];
340
+ layers.push(data);
341
+ this.buffs.set(type, layers);
342
+
343
+ if (duration !== undefined) {
344
+ data.duration = duration;
345
+ data.onExpire = onExpire;
346
+ data.onTick = onTick;
347
+ this.scheduleBuffLayer(type, data, duration);
348
+ }
349
+ }
350
+
351
+ /** 替换指定类型的所有 Buff 层,并可设置新层的持续时间。 */
352
+ public resetBuff(
353
+ type: PlayerBuffType,
354
+ value: number,
355
+ chance = 1,
356
+ duration?: number,
357
+ onExpire?: () => void,
358
+ onTick?: () => void,
359
+ ): void {
360
+ const data = this.normalizeBuffData(type, value, chance);
361
+ if (!data) return;
362
+ if (duration !== undefined && (!Number.isFinite(duration) || duration < 0)) return;
363
+
364
+ this.removeBuff(type);
365
+ const layers = [data];
366
+ this.buffs.set(type, layers);
367
+ if (duration !== undefined) {
368
+ data.duration = duration;
369
+ data.onExpire = onExpire;
370
+ data.onTick = onTick;
371
+ this.scheduleBuffLayer(type, data, duration);
372
+ }
373
+ }
374
+
375
+ private scheduleBuffLayer(type: PlayerBuffType, layer: PlayerBuffData, duration: number): void {
376
+ let remainingDuration = duration;
377
+ if (layer.onTick) {
378
+ layer.onTick();
379
+ if (!this.buffs.get(type)?.includes(layer)) return;
380
+ }
381
+
382
+ if (remainingDuration <= 0) {
383
+ this.removeBuffLayer(type, layer);
384
+ layer.onExpire?.();
385
+ return;
386
+ }
387
+
388
+ const scheduleNext = (): void => {
389
+ const delay = layer.onTick ? Math.min(1, remainingDuration) : remainingDuration;
390
+ layer.expireCallback = () => {
391
+ if (!this.buffs.get(type)?.includes(layer)) return;
392
+
393
+ remainingDuration -= delay;
394
+
395
+ if (remainingDuration <= 0) {
396
+ this.removeBuffLayer(type, layer);
397
+ layer.onExpire?.();
398
+ return;
399
+ }
400
+ layer.onTick?.();
401
+ if (!this.buffs.get(type)?.includes(layer)) return;
402
+ scheduleNext();
403
+ };
404
+ this.scheduleOnce(layer.expireCallback, delay);
405
+ };
406
+
407
+ scheduleNext();
408
+ }
409
+
410
+ private normalizeBuffData(type: PlayerBuffType, value: number, chance: number): PlayerBuffData | null {
411
+ if (!Number.isFinite(value) || !Number.isFinite(chance)) return null;
412
+
413
+ let normalizedValue: number;
414
+ switch (type) {
415
+ case PlayerBuffType.CriticalDamage:
416
+ normalizedValue = Math.max(1, value);
417
+ break;
418
+ case PlayerBuffType.FlatDamage:
419
+ case PlayerBuffType.MaxHealth:
420
+ case PlayerBuffType.CriticalDamageBonus:
421
+ case PlayerBuffType.PhysicalPenetration:
422
+ case PlayerBuffType.MagicPenetration:
423
+ case PlayerBuffType.CurrentHealth:
424
+ case PlayerBuffType.DamageOverTime:
425
+ case PlayerBuffType.DamageOverTimeEffect:
426
+ normalizedValue = Math.max(0, value);
427
+ break;
428
+ case PlayerBuffType.MovementSpeed:
429
+ normalizedValue = Math.max(0, value);
430
+ break;
431
+ case PlayerBuffType.PhysicalDefense:
432
+ case PlayerBuffType.MagicDefense:
433
+ normalizedValue = value;
434
+ break;
435
+ case PlayerBuffType.AttackSpeed:
436
+ case PlayerBuffType.AreaAttackRadius:
437
+ normalizedValue = Math.max(0, value);
438
+ break;
439
+ case PlayerBuffType.AttackTargetCount:
440
+ normalizedValue = Math.max(1, Math.floor(value));
441
+ break;
442
+ case PlayerBuffType.PercentPenetration:
443
+ case PlayerBuffType.PhysicalPercentPenetration:
444
+ case PlayerBuffType.MagicPercentPenetration:
445
+ case PlayerBuffType.DamageReduction:
446
+ case PlayerBuffType.PhysicalDamageReduction:
447
+ case PlayerBuffType.MagicDamageReduction:
448
+ case PlayerBuffType.Dodge:
449
+ case PlayerBuffType.PhysicalDodge:
450
+ case PlayerBuffType.MagicDodge:
451
+ case PlayerBuffType.Block:
452
+ case PlayerBuffType.Slow:
453
+ case PlayerBuffType.AttackDistance:
454
+ normalizedValue = Math.max(0, Math.min(value, 1));
455
+ break;
456
+ default:
457
+ normalizedValue = Math.max(0, Math.min(value, 1));
458
+ break;
459
+ }
460
+
461
+ return {
462
+ value: normalizedValue,
463
+ chance: Math.max(0, Math.min(chance, 1)),
464
+ };
465
+ }
466
+
467
+ /** 移除指定类型的 Buff。 */
468
+ public removeBuff(type: PlayerBuffType): void {
469
+ if (type === PlayerBuffType.DamageOverTime) {
470
+ this.damageOverTimeBuffs = [];
471
+ }
472
+ const layers = this.buffs.get(type);
473
+ if (layers) {
474
+ for (const layer of layers) {
475
+ if (layer.expireCallback) this.unschedule(layer.expireCallback);
476
+ }
477
+ }
478
+ this.buffs.delete(type);
479
+ }
480
+
481
+ private removeBuffLayer(type: PlayerBuffType, layer: PlayerBuffData): void {
482
+ const layers = this.buffs.get(type);
483
+ if (!layers) return;
484
+
485
+ const layerIndex = layers.indexOf(layer);
486
+ if (layerIndex < 0) return;
487
+ layers.splice(layerIndex, 1);
488
+ if (layer.expireCallback) this.unschedule(layer.expireCallback);
489
+ if (layers.length === 0) this.buffs.delete(type);
490
+ console.log(`${PlayerBuffType[type]} Buff 已到期移除,剩余 ${layers.length} 层`);
491
+ }
492
+
493
+ /** 清除当前角色的所有 Buff。 */
494
+ public clearBuffs(): void {
495
+ this.resetBuffs();
496
+ }
497
+
498
+ /** 重置 Buff 状态,移除当前拥有的所有 Buff。 */
499
+ public resetBuffs(): void {
500
+ this.damageOverTimeBuffs = [];
501
+ for (const type of this.buffs.keys()) {
502
+ this.removeBuff(type);
503
+ }
504
+ }
505
+
506
+ /** 查询角色是否拥有指定 Buff。 */
507
+ public hasBuff(type: PlayerBuffType): boolean {
508
+ if (type === PlayerBuffType.DamageOverTime && this.damageOverTimeBuffs.length > 0) {
509
+ return true;
510
+ }
511
+ return (this.buffs.get(type)?.length ?? 0) > 0;
512
+ }
513
+
514
+ /** 读取 Buff 数值;未持有该 Buff 时返回 0。 */
515
+ public getBuffValue(type: PlayerBuffType | any): number {
516
+ return this.buffs.get(type)?.reduce((total, layer) => total + layer.value, 0) ?? 0;
517
+ }
518
+
519
+ /** 获取当前攻击所需的多目标与范围配置。 */
520
+ public getAttackTargetConfig(): PlayerAttackTargetConfig {
521
+ const targetCount = this.hasBuff(PlayerBuffType.AttackTargetCount)
522
+ ? Math.max(1, Math.floor(this.getBuffValue(PlayerBuffType.AttackTargetCount)))
523
+ : undefined;
524
+ const areaAttackRadius = this.hasBuff(PlayerBuffType.AreaAttackRadius)
525
+ ? this.getBuffValue(PlayerBuffType.AreaAttackRadius)
526
+ : undefined;
527
+
528
+ return { targetCount, areaAttackRadius };
529
+ }
530
+
531
+ private getBuffData(type: PlayerBuffType): PlayerBuffData | null {
532
+ const layers = this.buffs.get(type);
533
+ if (!layers?.length) return null;
534
+
535
+ return layers.reduce((combined, layer) => ({
536
+ value: combined.value + layer.value,
537
+ chance: 1 - (1 - combined.chance) * (1 - layer.chance),
538
+ }), { value: 0, chance: 0 });
539
+ }
540
+
541
+ /** 正抗性使用递减公式,负抗性会提高受到的伤害。 */
542
+ public calculateIncomingDamage(
543
+ amount: number,
544
+ defenseType: PlayerBuffType,
545
+ attackerBuffs?: FlyPlayerBuffSystem,
546
+ ): number {
547
+ const safeDamage = Number.isFinite(amount) ? Math.max(0, amount) : 0;
548
+ const isMagicDamage = defenseType === PlayerBuffType.MagicDefense;
549
+ const dodgeType = isMagicDamage ? PlayerBuffType.MagicDodge : PlayerBuffType.PhysicalDodge;
550
+ const dodgeChance = Math.min(1,
551
+ this.getBuffValue(PlayerBuffType.Dodge) + this.getBuffValue(dodgeType),
552
+ );
553
+ if (Math.random() < dodgeChance) {
554
+ console.log(`成功闪避${isMagicDamage ? '魔法' : '物理'}攻击,本次未受到伤害`);
555
+ return 0;
556
+ }
557
+
558
+ /** 根据本次伤害类型选择对应的固定穿透属性。 */
559
+ const flatPenetrationType = isMagicDamage
560
+ ? PlayerBuffType.MagicPenetration
561
+ : PlayerBuffType.PhysicalPenetration;
562
+ const defense = this.getBuffValue(defenseType);
563
+ /** 通用穿透与对应类型穿透相加,最多穿透 100% 防御。 */
564
+ const percentPenetrationType = isMagicDamage
565
+ ? PlayerBuffType.MagicPercentPenetration
566
+ : PlayerBuffType.PhysicalPercentPenetration;
567
+ const percentPenetration = Math.min(1,
568
+ (attackerBuffs?.getBuffValue(PlayerBuffType.PercentPenetration) ?? 0)
569
+ + (attackerBuffs?.getBuffValue(percentPenetrationType) ?? 0),
570
+ );
571
+ /** 先削减百分比防御,再减去固定穿透;结果可低于零。 */
572
+ const effectiveDefense = defense * (1 - percentPenetration)
573
+ - (attackerBuffs?.getBuffValue(flatPenetrationType) ?? 0);
574
+ /** 正防御使用递减减伤;负防御按负抗性规则放大伤害。 */
575
+ const damageMultiplier = effectiveDefense >= 0
576
+ ? 100 / (100 + effectiveDefense)
577
+ : 2 - 100 / (100 - effectiveDefense);
578
+ /** 应用伤害倍率并取整,然后记录防御实际改变的伤害值。 */
579
+ const damageAfterDefense = Math.round(safeDamage * damageMultiplier);
580
+ this.logDefenseChange(defenseType, safeDamage - damageAfterDefense);
581
+
582
+ let damageAfterBlock = damageAfterDefense;
583
+ const blockBuff = this.getBuffData(PlayerBuffType.Block);
584
+ const blockRatio = Math.min(1, this.getBuffValue(PlayerBuffType.Block));
585
+ if (blockBuff && Math.random() < blockBuff.chance) {
586
+ const blockedDamage = Math.round(damageAfterBlock * blockRatio);
587
+ damageAfterBlock = Math.max(0, damageAfterBlock - blockedDamage);
588
+ if (blockedDamage > 0) {
589
+ console.log(`格挡减少了 ${blockedDamage} 点伤害`);
590
+ }
591
+ }
592
+
593
+ const damageReductionType = isMagicDamage
594
+ ? PlayerBuffType.MagicDamageReduction
595
+ : PlayerBuffType.PhysicalDamageReduction;
596
+ const damageReduction = Math.min(1,
597
+ this.getBuffValue(PlayerBuffType.DamageReduction)
598
+ + this.getBuffValue(damageReductionType),
599
+ );
600
+ const actualDamage = Math.round(damageAfterBlock * (1 - damageReduction));
601
+ const reducedDamage = damageAfterBlock - actualDamage;
602
+ if (reducedDamage > 0) {
603
+ console.log(`伤害减免减少了 ${reducedDamage} 点伤害`);
604
+ }
605
+ return actualDamage;
606
+ }
607
+
608
+ private logDefenseChange(defenseType: PlayerBuffType, reducedDamage: number): void {
609
+ if (reducedDamage === 0) return;
610
+
611
+ const damageTypeName = defenseType === PlayerBuffType.MagicDefense ? '魔法' : '物理';
612
+ if (reducedDamage > 0) {
613
+ console.log(`${damageTypeName}防御减少了 ${reducedDamage} 点伤害`);
614
+ } else {
615
+ console.log(`${damageTypeName}负抗性增加了 ${Math.abs(reducedDamage)} 点伤害`);
616
+ }
617
+ }
618
+
619
+ /** 按实际造成的伤害和吸血比例计算回复量。 */
620
+ public calculateLifesteal(damage: number): number {
621
+ const safeDamage = Number.isFinite(damage) ? Math.max(0, damage) : 0;
622
+ return Math.floor(safeDamage * this.getBuffValue(PlayerBuffType.Lifesteal));
623
+ }
624
+
625
+ /** 按攻速 Buff 缩短基础攻击间隔,间隔单位为秒。 */
626
+ public getAttackInterval(baseAttackInterval: number): number {
627
+ const safeBaseInterval = Number.isFinite(baseAttackInterval)
628
+ ? Math.max(0, baseAttackInterval)
629
+ : 0;
630
+ return safeBaseInterval / (1 + this.getBuffValue(PlayerBuffType.AttackSpeed));
631
+ }
632
+
633
+ /** 按最终攻击间隔占用一次攻击机会,并由 Buff 系统管理冷却。 */
634
+ public tryUseAttack(baseAttackInterval: number): boolean {
635
+ if (this.isAttackCoolingDown) return false;
636
+
637
+ const attackInterval = this.getAttackInterval(baseAttackInterval);
638
+ if (attackInterval <= 0) return true;
639
+
640
+ console.log(`当前攻击间隔:${attackInterval} 秒`);
641
+ this.isAttackCoolingDown = true;
642
+ this.scheduleOnce(() => {
643
+ this.isAttackCoolingDown = false;
644
+ }, attackInterval);
645
+ return true;
646
+ }
647
+
648
+ /** 按基础伤害、目标最大生命值伤害、增伤和暴击计算攻击结果。 */
649
+ public calculateAttack(baseDamage: number, targetMaxHp = 0): PlayerBuffAttackResult {
650
+ /** 将非法或负数基础伤害归零。 */
651
+ const safeBaseDamage = Number.isFinite(baseDamage) ? Math.max(0, baseDamage) : 0;
652
+ /** 按目标最大生命值比例计算额外伤害。 */
653
+ const safeTargetMaxHp = Number.isFinite(targetMaxHp) ? Math.max(0, targetMaxHp) : 0;
654
+ const maxHealthDamage = safeTargetMaxHp * this.getBuffValue(PlayerBuffType.MaxHealthDamage);
655
+ /** 加入固定攻击力和最大生命值伤害后,再应用百分比伤害加成。 */
656
+ const flatDamage = this.getBuffValue(PlayerBuffType.FlatDamage);
657
+ const damageBonus = this.getBuffValue(PlayerBuffType.PercentDamage);
658
+ /** 按暴击 Buff 的触发概率决定本次是否暴击。 */
659
+ const criticalBuff = this.getBuffData(PlayerBuffType.CriticalDamage);
660
+ const isCritical = !!criticalBuff && Math.random() < criticalBuff.chance;
661
+ /** 暴击时叠加额外暴击倍率,否则仍按普通伤害结算。 */
662
+ const criticalMultiplier = isCritical
663
+ ? criticalBuff.value + this.getBuffValue(PlayerBuffType.CriticalDamageBonus)
664
+ : 1;
665
+ /** 应用百分比加成和暴击倍率,并取整为整数伤害。 */
666
+ const damage = Math.round((safeBaseDamage + flatDamage + maxHealthDamage) * (1 + damageBonus) * criticalMultiplier);
667
+ return {
668
+ damage,
669
+ isCritical,
670
+ lifestealAmount: this.calculateLifesteal(damage),
671
+ };
672
+ }
673
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "ver": "4.0.24",
3
+ "importer": "typescript",
4
+ "imported": true,
5
+ "uuid": "3af632c8-e289-4d12-9969-f34fd71ef44b",
6
+ "files": [],
7
+ "subMetas": {},
8
+ "userData": {}
9
+ }