|
|
|
@ -20,16 +20,19 @@ import { BattleSpinePlayback } from '../presentation/BattleSpinePlayback'; |
|
|
|
import { SkillTransformDriver, computeAimAngleDeg } from '../skill/SkillTransformDriver'; |
|
|
|
import { SkillTransformDriver, computeAimAngleDeg } from '../skill/SkillTransformDriver'; |
|
|
|
import { |
|
|
|
import { |
|
|
|
HOMING_DRAGON_EVENT, |
|
|
|
HOMING_DRAGON_EVENT, |
|
|
|
|
|
|
|
ICE_CONE_SPLIT_EVENT, |
|
|
|
LINE_DRAGON_EVENT, |
|
|
|
LINE_DRAGON_EVENT, |
|
|
|
SWORD_ARRAY_EVENT, |
|
|
|
SWORD_ARRAY_EVENT, |
|
|
|
SWORD_WAVE_EVENT, |
|
|
|
SWORD_WAVE_EVENT, |
|
|
|
LIGHTNING_CHAIN_EVENT, |
|
|
|
LIGHTNING_CHAIN_EVENT, |
|
|
|
type HomingDragonCue, |
|
|
|
type HomingDragonCue, |
|
|
|
|
|
|
|
type IceConeSplitCue, |
|
|
|
type LineDragonCue, |
|
|
|
type LineDragonCue, |
|
|
|
type SwordArrayCue, |
|
|
|
type SwordArrayCue, |
|
|
|
type SwordWaveCue, |
|
|
|
type SwordWaveCue, |
|
|
|
type LightningChainCue, |
|
|
|
type LightningChainCue, |
|
|
|
} from '../combat/PassiveSkillRuntime'; |
|
|
|
} from '../combat/PassiveSkillRuntime'; |
|
|
|
|
|
|
|
import { BattleMapSpec } from '../map/BattleMapSpec'; |
|
|
|
import { traceCombatDamage } from 'db://assets/mx/tools/CombatDamageTrace'; |
|
|
|
import { traceCombatDamage } from 'db://assets/mx/tools/CombatDamageTrace'; |
|
|
|
|
|
|
|
|
|
|
|
type MonsterView = IBattleMonsterView & { |
|
|
|
type MonsterView = IBattleMonsterView & { |
|
|
|
@ -63,11 +66,16 @@ const BURN_BUFF_ID = 'debuff_1'; |
|
|
|
const BLEED_BUFF_ID = 'debuff_2'; |
|
|
|
const BLEED_BUFF_ID = 'debuff_2'; |
|
|
|
const GHOST_BITE_BUFF_ID = 'debuff_6'; |
|
|
|
const GHOST_BITE_BUFF_ID = 'debuff_6'; |
|
|
|
const ELEC_BUFF_ID = 'debuff_7'; |
|
|
|
const ELEC_BUFF_ID = 'debuff_7'; |
|
|
|
|
|
|
|
const FREEZE_BUFF_ID = 'debuff_13'; |
|
|
|
const BIND_NET_BUFF_ID = 'debuff_bind_net'; |
|
|
|
const BIND_NET_BUFF_ID = 'debuff_bind_net'; |
|
|
|
const FIRE_TRAP_BUFF_ID = 'debuff_fire_trap'; |
|
|
|
const FIRE_TRAP_BUFF_ID = 'debuff_fire_trap'; |
|
|
|
const KNOCKBACK_APPLIED_EVENT = 'KnockbackApplied'; |
|
|
|
const KNOCKBACK_APPLIED_EVENT = 'KnockbackApplied'; |
|
|
|
const SWORD_ARRAY_PREFAB = '剑阵'; |
|
|
|
const SWORD_ARRAY_PREFAB = '剑阵'; |
|
|
|
const SWORD_ARRAY_PREFAB_PATH = 'prefab/skill/'; |
|
|
|
const SWORD_ARRAY_PREFAB_PATH = 'prefab/skill/'; |
|
|
|
|
|
|
|
/** 分裂冰锥表现用「水弹」 */ |
|
|
|
|
|
|
|
const ICE_CONE_PREFAB = '水弹'; |
|
|
|
|
|
|
|
/** 水弹预制体原图宽约 322,缩到约一格 */ |
|
|
|
|
|
|
|
const ICE_CONE_SRC_W = 322; |
|
|
|
/** 剑阵根节点参考边长(预制体 UITransform 200,直径=边长) */ |
|
|
|
/** 剑阵根节点参考边长(预制体 UITransform 200,直径=边长) */ |
|
|
|
const SWORD_ARRAY_REF_SIDE = 200; |
|
|
|
const SWORD_ARRAY_REF_SIDE = 200; |
|
|
|
const HOMING_DRAGON_PREFAB = '追踪火龙'; |
|
|
|
const HOMING_DRAGON_PREFAB = '追踪火龙'; |
|
|
|
@ -106,8 +114,11 @@ export class CocosBattleCombatPresenter { |
|
|
|
private readonly _lineDragonFx: Node[] = []; |
|
|
|
private readonly _lineDragonFx: Node[] = []; |
|
|
|
private readonly _swordWaveFx: Node[] = []; |
|
|
|
private readonly _swordWaveFx: Node[] = []; |
|
|
|
private readonly _lightningFx: Node[] = []; |
|
|
|
private readonly _lightningFx: Node[] = []; |
|
|
|
|
|
|
|
private readonly _iceConeFx: Node[] = []; |
|
|
|
private readonly _dragonFrom = new Vec3(); |
|
|
|
private readonly _dragonFrom = new Vec3(); |
|
|
|
private readonly _dragonTo = new Vec3(); |
|
|
|
private readonly _dragonTo = new Vec3(); |
|
|
|
|
|
|
|
private readonly _iceConeFrom = new Vec3(); |
|
|
|
|
|
|
|
private readonly _iceConeTo = new Vec3(); |
|
|
|
|
|
|
|
|
|
|
|
bind(battle: ProjectBattleHandle, monsterPresenter: CocosBattleMonsterPresenter, displayRoot: Node): void { |
|
|
|
bind(battle: ProjectBattleHandle, monsterPresenter: CocosBattleMonsterPresenter, displayRoot: Node): void { |
|
|
|
this.unbind(); |
|
|
|
this.unbind(); |
|
|
|
@ -126,6 +137,7 @@ export class CocosBattleCombatPresenter { |
|
|
|
this._recycleAllLineDragons(); |
|
|
|
this._recycleAllLineDragons(); |
|
|
|
this._recycleAllSwordWaves(); |
|
|
|
this._recycleAllSwordWaves(); |
|
|
|
this._recycleAllLightning(); |
|
|
|
this._recycleAllLightning(); |
|
|
|
|
|
|
|
this._recycleAllIceCones(); |
|
|
|
if (this._battle && this._listener) { |
|
|
|
if (this._battle && this._listener) { |
|
|
|
this._battle.off(BATTLE_EVENT_ALL, this._listener); |
|
|
|
this._battle.off(BATTLE_EVENT_ALL, this._listener); |
|
|
|
} |
|
|
|
} |
|
|
|
@ -173,6 +185,8 @@ export class CocosBattleCombatPresenter { |
|
|
|
this._onStunApplied(event.target, event.duration); |
|
|
|
this._onStunApplied(event.target, event.duration); |
|
|
|
} else if (event.buffId === SLOW_BUFF_ID) { |
|
|
|
} else if (event.buffId === SLOW_BUFF_ID) { |
|
|
|
this._onSlowApplied(event.target, event.duration); |
|
|
|
this._onSlowApplied(event.target, event.duration); |
|
|
|
|
|
|
|
} else if (event.buffId === FREEZE_BUFF_ID) { |
|
|
|
|
|
|
|
this._onFreezeApplied(event.target, event.duration); |
|
|
|
} else if (event.buffId === BURN_BUFF_ID) { |
|
|
|
} else if (event.buffId === BURN_BUFF_ID) { |
|
|
|
this._onBurnApplied(event.target, event.duration); |
|
|
|
this._onBurnApplied(event.target, event.duration); |
|
|
|
} else if (event.buffId === BLEED_BUFF_ID) { |
|
|
|
} else if (event.buffId === BLEED_BUFF_ID) { |
|
|
|
@ -192,6 +206,8 @@ export class CocosBattleCombatPresenter { |
|
|
|
this._onStunRemoved(event.target); |
|
|
|
this._onStunRemoved(event.target); |
|
|
|
} else if (event.buffId === SLOW_BUFF_ID) { |
|
|
|
} else if (event.buffId === SLOW_BUFF_ID) { |
|
|
|
this._onSlowRemoved(event.target); |
|
|
|
this._onSlowRemoved(event.target); |
|
|
|
|
|
|
|
} else if (event.buffId === FREEZE_BUFF_ID) { |
|
|
|
|
|
|
|
this._onFreezeRemoved(event.target); |
|
|
|
} else if (event.buffId === BURN_BUFF_ID) { |
|
|
|
} else if (event.buffId === BURN_BUFF_ID) { |
|
|
|
this._onBurnRemoved(event.target); |
|
|
|
this._onBurnRemoved(event.target); |
|
|
|
} else if (event.buffId === BLEED_BUFF_ID) { |
|
|
|
} else if (event.buffId === BLEED_BUFF_ID) { |
|
|
|
@ -205,6 +221,7 @@ export class CocosBattleCombatPresenter { |
|
|
|
case BattleEventType.EntityKilled: |
|
|
|
case BattleEventType.EntityKilled: |
|
|
|
this._onStunRemoved(event.victim); |
|
|
|
this._onStunRemoved(event.victim); |
|
|
|
this._onSlowRemoved(event.victim); |
|
|
|
this._onSlowRemoved(event.victim); |
|
|
|
|
|
|
|
this._onFreezeRemoved(event.victim); |
|
|
|
this._onBurnRemoved(event.victim); |
|
|
|
this._onBurnRemoved(event.victim); |
|
|
|
this._onBleedRemoved(event.victim); |
|
|
|
this._onBleedRemoved(event.victim); |
|
|
|
this._onGhostBiteRemoved(event.victim); |
|
|
|
this._onGhostBiteRemoved(event.victim); |
|
|
|
@ -213,6 +230,7 @@ export class CocosBattleCombatPresenter { |
|
|
|
case BattleEventType.UnitDespawned: |
|
|
|
case BattleEventType.UnitDespawned: |
|
|
|
this._onStunRemoved(event.entity); |
|
|
|
this._onStunRemoved(event.entity); |
|
|
|
this._onSlowRemoved(event.entity); |
|
|
|
this._onSlowRemoved(event.entity); |
|
|
|
|
|
|
|
this._onFreezeRemoved(event.entity); |
|
|
|
this._onBurnRemoved(event.entity); |
|
|
|
this._onBurnRemoved(event.entity); |
|
|
|
this._onBleedRemoved(event.entity); |
|
|
|
this._onBleedRemoved(event.entity); |
|
|
|
this._onGhostBiteRemoved(event.entity); |
|
|
|
this._onGhostBiteRemoved(event.entity); |
|
|
|
@ -234,6 +252,8 @@ export class CocosBattleCombatPresenter { |
|
|
|
this._playSwordWave(event.payload as SwordWaveCue | undefined); |
|
|
|
this._playSwordWave(event.payload as SwordWaveCue | undefined); |
|
|
|
} else if (event.name === LIGHTNING_CHAIN_EVENT) { |
|
|
|
} else if (event.name === LIGHTNING_CHAIN_EVENT) { |
|
|
|
this._playLightningChain(event.payload as LightningChainCue | undefined); |
|
|
|
this._playLightningChain(event.payload as LightningChainCue | undefined); |
|
|
|
|
|
|
|
} else if (event.name === ICE_CONE_SPLIT_EVENT) { |
|
|
|
|
|
|
|
this._playIceConeSplit(event.payload as IceConeSplitCue | undefined); |
|
|
|
} |
|
|
|
} |
|
|
|
break; |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
@ -261,6 +281,18 @@ export class CocosBattleCombatPresenter { |
|
|
|
this._getMonsterView(target)?.clearSlowFx?.(); |
|
|
|
this._getMonsterView(target)?.clearSlowFx?.(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** 冰冻:复用怪_冰表现并飘「冰冻」 */ |
|
|
|
|
|
|
|
private _onFreezeApplied(target: EntityId, duration: number): void { |
|
|
|
|
|
|
|
const monster = this._getMonsterView(target); |
|
|
|
|
|
|
|
if (!monster || !Number.isFinite(duration) || duration <= 0) return; |
|
|
|
|
|
|
|
monster.showSlowFx?.(Math.max(0.05, duration)); |
|
|
|
|
|
|
|
this._playStatusText(target, '冰冻'); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private _onFreezeRemoved(target: EntityId): void { |
|
|
|
|
|
|
|
this._getMonsterView(target)?.clearSlowFx?.(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private _onBurnApplied(target: EntityId, duration: number): void { |
|
|
|
private _onBurnApplied(target: EntityId, duration: number): void { |
|
|
|
const monster = this._getMonsterView(target); |
|
|
|
const monster = this._getMonsterView(target); |
|
|
|
if (!monster || !Number.isFinite(duration) || duration <= 0) return; |
|
|
|
if (!monster || !Number.isFinite(duration) || duration <= 0) return; |
|
|
|
@ -873,6 +905,91 @@ export class CocosBattleCombatPresenter { |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* 分裂冰锥:水弹从触发点飞向目标。 |
|
|
|
|
|
|
|
* @param cue 逻辑层 cue |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
private _playIceConeSplit(cue: IceConeSplitCue | undefined): void { |
|
|
|
|
|
|
|
if (!cue || !(cue.duration > 0)) return; |
|
|
|
|
|
|
|
const parent = SkillPresentationEnv.flyLayer ?? this._displayRoot; |
|
|
|
|
|
|
|
if (!parent?.isValid) { |
|
|
|
|
|
|
|
console.log('[CombatPresenter] 冰锥无父节点'); |
|
|
|
|
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
this._iceConeFrom.set(cue.fromX, cue.fromY, 0); |
|
|
|
|
|
|
|
this._iceConeTo.set(cue.toX, cue.toY, 0); |
|
|
|
|
|
|
|
const angleDeg = computeAimAngleDeg(this._iceConeFrom, this._iceConeTo); |
|
|
|
|
|
|
|
if (angleDeg == null) { |
|
|
|
|
|
|
|
console.log('[CombatPresenter] 冰锥朝向为零'); |
|
|
|
|
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
let node: Node | null = null; |
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
node = gg.res.getNode(SWORD_ARRAY_PREFAB_PATH, ICE_CONE_PREFAB, GBundle.BattleHero); |
|
|
|
|
|
|
|
} catch (err) { |
|
|
|
|
|
|
|
console.log('[CombatPresenter] 冰锥 getNode 失败', String(err)); |
|
|
|
|
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (!node?.isValid) { |
|
|
|
|
|
|
|
console.log('[CombatPresenter] 冰锥预制体未加载', ICE_CONE_PREFAB); |
|
|
|
|
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
parent.active = true; |
|
|
|
|
|
|
|
const flyUt = parent.getComponent(UITransform); |
|
|
|
|
|
|
|
const scale = BattleMapSpec.CELL_SIZE / ICE_CONE_SRC_W; |
|
|
|
|
|
|
|
node.active = true; |
|
|
|
|
|
|
|
node.layer = parent.layer; |
|
|
|
|
|
|
|
node.setScale(scale, scale, 1); |
|
|
|
|
|
|
|
node.parent = parent; |
|
|
|
|
|
|
|
const startLocal = flyUt |
|
|
|
|
|
|
|
? flyUt.convertToNodeSpaceAR(this._iceConeFrom) |
|
|
|
|
|
|
|
: this._iceConeFrom.clone(); |
|
|
|
|
|
|
|
const endLocal = flyUt |
|
|
|
|
|
|
|
? flyUt.convertToNodeSpaceAR(this._iceConeTo) |
|
|
|
|
|
|
|
: this._iceConeTo.clone(); |
|
|
|
|
|
|
|
node.setPosition(startLocal); |
|
|
|
|
|
|
|
node.angle = angleDeg; |
|
|
|
|
|
|
|
node.setSiblingIndex(parent.children.length - 1); |
|
|
|
|
|
|
|
this._iceConeFx.push(node); |
|
|
|
|
|
|
|
SkillTransformDriver.cancelNode(node); |
|
|
|
|
|
|
|
SkillTransformDriver.animate({ |
|
|
|
|
|
|
|
node, |
|
|
|
|
|
|
|
duration: cue.duration, |
|
|
|
|
|
|
|
fromPos: startLocal, |
|
|
|
|
|
|
|
toPos: endLocal, |
|
|
|
|
|
|
|
fromAngle: angleDeg, |
|
|
|
|
|
|
|
toAngle: angleDeg, |
|
|
|
|
|
|
|
easing: SkillTransformDriver.easeLinear, |
|
|
|
|
|
|
|
onComplete: this._onIceConeArrived, |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private readonly _onIceConeArrived = (node?: Node): void => { |
|
|
|
|
|
|
|
if (node) this._recycleIceCone(node); |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** 回收全部分裂冰锥表现 */ |
|
|
|
|
|
|
|
private _recycleAllIceCones(): void { |
|
|
|
|
|
|
|
for (let i = this._iceConeFx.length - 1; i >= 0; i--) { |
|
|
|
|
|
|
|
this._recycleIceCone(this._iceConeFx[i]); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* 停飞并回池冰锥表现。 |
|
|
|
|
|
|
|
* @param node 水弹节点 |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
private _recycleIceCone(node: Node): void { |
|
|
|
|
|
|
|
for (let i = this._iceConeFx.length - 1; i >= 0; i--) { |
|
|
|
|
|
|
|
if (this._iceConeFx[i] === node) this._iceConeFx.splice(i, 1); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (!node?.isValid) return; |
|
|
|
|
|
|
|
SkillTransformDriver.cancelNode(node); |
|
|
|
|
|
|
|
node.angle = 0; |
|
|
|
|
|
|
|
node.setScale(1, 1, 1); |
|
|
|
|
|
|
|
gg.res.putNode(node); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* 停飞并回池。 |
|
|
|
* 停飞并回池。 |
|
|
|
* @param node 刀光节点 |
|
|
|
* @param node 刀光节点 |
|
|
|
|