You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
AdLike2048/assets/script/ui/BattleGame/BattleGameRuoshuiBuffPresen...

362 lines
14 KiB

import { Node } from 'cc';
import type { BattleEvent, EntityId } from 'db://assets/mx/module/battle';
import { BattleEventType } from 'db://assets/mx/module/battle';
import { AiHeadlessBagHost } from '../../battle/ai/runtime/AiHeadlessBagHost';
import { getBattleBuffEnemySlowHost } from '../../battle/combat/BattleBuffEnemySlowHost';
import {
collectSpawnPortsForCampArea,
resolveEnemyCampAreaIdForDefender,
resolveGridAheadOfSpawnPort,
} from '../../battle/map/MapPathQueries';
import type { MapGrid } from '../../battle/map/legacy/MapGrid';
import type { MapManager } from '../../battle/map/legacy/MapManager';
import { PassiveIconArcFlight } from '../../battle/view/PassiveIconArcFlight';
import { BATTLE_HERO_BUFF_PATH } from '../../battle/res/BattleHeroResCollector';
import type { ProjectBattleHandle } from '../../battle/runtime/BattleRuntime';
import { schedulePresentation } from '../../battle/skill/BattlePresentationClock';
import { SPAWN_PRESENT_SEC, type CocosBattleMonsterPresenter } from '../../battle/view/CocosBattleMonsterPresenter';
import { getMonsterStatusText } from '../../battle/view/MonsterStatusText';
import { GBundle } from '../../game/ConfigRes';
import { BattleBuffCarryStore } from '../../manager/battleBuff/BattleBuffCarryStore';
import { resolveCarriedEnemySlowWan } from '../../manager/battleBuff/BattleBuffPassiveSkillUtil';
import { BattleBuffFunctionType } from '../../manager/battleBuff/BattleBuffTypes';
import type { BattleGameOtherSkillView } from './BattleGameOtherSkillView';
import type { BattleGamePassiveSkillView } from './BattleGamePassiveSkillView';
/** 弱水预制体(battleHero/prefab/buff/弱水) */
const RUOSHUI_PREFAB_NAME = '弱水';
/** 开局弱水 icon 飞行动画延迟(逻辑秒) */
const RUOSHUI_FLIGHT_START_DELAY_SEC = 2;
/** 已放置的弱水表现 */
type RuoshuiVisualEntry = {
/** 携带被动防守方 */
defenderPlayerId: number;
/** 出怪口 */
port: number;
/** 地图格 index */
gridIndex: number;
/** 节点 */
node: Node;
};
/** 弱水开局飞行动画 UI 上下文 */
export type BattleGameRuoshuiBuffUiContext = {
/** 飞行层(ui_fly) */
flyLayer: Node | null;
/** 本机被动槽 */
passiveSkillView: BattleGamePassiveSkillView | null;
/** 敌方被动槽 */
otherSkillView: BattleGameOtherSkillView | null;
};
/**
* 被动 functiontype=23 弱水表现:
* - 开局复制被动 icon 影子,沿进路弧线飞到进攻方出怪口下一格后消失,再挂弱水预制体;
* - 受弱水影响的出怪口怪物出现后,头顶飘「减速」。
*/
export class BattleGameRuoshuiBuffPresenter {
private _battle: ProjectBattleHandle | null = null;
private _map: MapManager | null = null;
private _monsterPresenter: CocosBattleMonsterPresenter | null = null;
private _flyLayer: Node | null = null;
private _passiveSkillView: BattleGamePassiveSkillView | null = null;
private _otherSkillView: BattleGameOtherSkillView | null = null;
private readonly _visuals: RuoshuiVisualEntry[] = [];
private readonly _inflightFlightHosts: Node[] = [];
private readonly _inflightGridIndices: number[] = [];
private _spawnFxGen = 0;
private _spawnListenerBound = false;
private _onUnitSpawned: ((event: BattleEvent) => void) | null = null;
/**
* @param battle 战斗句柄
* @param map 地图
* @param monsterPresenter 怪物表现(头顶减速)
* @param ui 被动 icon / 飞行层
*/
bind(
battle: ProjectBattleHandle | null,
map: MapManager | null,
monsterPresenter?: CocosBattleMonsterPresenter | null,
ui?: BattleGameRuoshuiBuffUiContext | null,
): void {
this.unbind();
this._battle = battle;
this._map = map?.gridsInitialized ? map : null;
this._monsterPresenter = monsterPresenter ?? null;
this._flyLayer = ui?.flyLayer?.isValid ? ui.flyLayer : null;
this._passiveSkillView = ui?.passiveSkillView ?? null;
this._otherSkillView = ui?.otherSkillView ?? null;
}
/** 释放全部弱水节点并注销出怪监听 */
unbind(): void {
this._spawnFxGen++;
this._abortInflightFlights();
this._unbindSpawnHeadFx();
this._clearVisuals();
this._battle = null;
this._map = null;
this._monsterPresenter = null;
this._flyLayer = null;
this._passiveSkillView = null;
this._otherSkillView = null;
}
/** 按 CarryStore 补齐弱水表现(已放置格不重复飞行) */
refresh(): void {
const battle = this._battle;
const map = this._map;
if (!battle || !map?.gridsInitialized) {
this._unbindSpawnHeadFx();
this._clearVisuals();
return;
}
this._pruneStaleVisuals();
this._tryPlaceForDefender(battle.localPlayerId);
this._tryPlaceForDefender(AiHeadlessBagHost.AI_PLAYER_ID);
this._syncSpawnHeadFx(battle);
}
/** 若防守方携带弱水,在其敌方出怪口下一格播放飞行并布置预制体 */
private _tryPlaceForDefender(defenderPlayerId: number): void {
const battle = this._battle;
const map = this._map;
if (!battle || !map) return;
const adjust = resolveCarriedEnemySlowWan(BattleBuffCarryStore.getPassives(defenderPlayerId));
if (!adjust.active || adjust.slowWan <= 0) return;
const enemyCamp = resolveEnemyCampAreaIdForDefender(battle.session, defenderPlayerId);
if (enemyCamp == null) {
console.log('[RuoshuiBuff] 进攻方阵营区域缺失', defenderPlayerId);
return;
}
const ports = collectSpawnPortsForCampArea(battle.session, enemyCamp);
for (let i = 0; i < ports.length; i++) {
const port = ports[i];
const grid = resolveGridAheadOfSpawnPort(map, battle.session, port, defenderPlayerId, 1);
if (!grid?.node?.isValid) {
console.log('[RuoshuiBuff] 出怪口下一格缺失', defenderPlayerId, port);
continue;
}
this._playFlightThenSpawn(grid, defenderPlayerId, port);
}
}
/** 开局延迟后复制被动 icon 影子飞到目标格,消失后挂弱水预制体 */
private _playFlightThenSpawn(grid: MapGrid, defenderPlayerId: number, port: number): void {
const gridIndex = grid.Index;
for (let i = 0; i < this._visuals.length; i++) {
if (this._visuals[i].gridIndex === gridIndex) return;
}
if (this._inflightGridIndices.indexOf(gridIndex) >= 0) return;
this._inflightGridIndices.push(gridIndex);
const gen = this._spawnFxGen;
schedulePresentation(RUOSHUI_FLIGHT_START_DELAY_SEC, () => {
if (gen !== this._spawnFxGen) {
this._removeInflightGridIndex(gridIndex);
return;
}
if (!grid?.node?.isValid) {
this._removeInflightGridIndex(gridIndex);
return;
}
for (let i = 0; i < this._visuals.length; i++) {
if (this._visuals[i].gridIndex === gridIndex) {
this._removeInflightGridIndex(gridIndex);
return;
}
}
const iconNode = this._resolvePassiveIconNode(defenderPlayerId);
const flyLayer = this._flyLayer;
const targetWorld = grid.node.worldPosition;
if (!iconNode?.isValid || !flyLayer?.isValid) {
console.log('[RuoshuiBuff] 飞行动画跳过:icon 或 flyLayer 缺失', defenderPlayerId, port);
this._removeInflightGridIndex(gridIndex);
this._spawnOnGrid(grid, defenderPlayerId, port);
return;
}
const startWorld = iconNode.worldPosition.clone();
const flightRef: { host: Node | null } = { host: null };
flightRef.host = PassiveIconArcFlight.play(iconNode, targetWorld, flyLayer, () => {
this._removeInflightGridIndex(gridIndex);
const host = flightRef.host;
if (host?.isValid) {
const idx = this._inflightFlightHosts.indexOf(host);
if (idx >= 0) this._inflightFlightHosts.splice(idx, 1);
}
if (gen !== this._spawnFxGen) return;
this._spawnOnGrid(grid, defenderPlayerId, port);
}, {
startWorld,
showTrail: true,
});
if (flightRef.host?.isValid) {
this._inflightFlightHosts.push(flightRef.host);
}
});
}
/**
* 按携带方取弱水被动 icon。
* @param defenderPlayerId 携带弱水的防守方
*/
private _resolvePassiveIconNode(defenderPlayerId: number): Node | null {
const ft = BattleBuffFunctionType.EnemySlow;
const battle = this._battle;
if (!battle) return null;
if (defenderPlayerId === battle.localPlayerId) {
return this._passiveSkillView?.getPassiveIconNode(ft) ?? null;
}
if (defenderPlayerId === AiHeadlessBagHost.AI_PLAYER_ID) {
return this._otherSkillView?.getPassiveIconNode(ft) ?? null;
}
console.log('[RuoshuiBuff] 未知携带方', defenderPlayerId);
return null;
}
/** 同步出怪口头顶减速监听 */
private _syncSpawnHeadFx(battle: ProjectBattleHandle): void {
if (!this._monsterPresenter || !this._hasEnemySlowPassive(battle)) {
this._unbindSpawnHeadFx();
return;
}
if (this._spawnListenerBound) return;
this._onUnitSpawned = this._handleUnitSpawned;
battle.session.events.on(BattleEventType.UnitSpawned, this._onUnitSpawned);
this._spawnListenerBound = true;
}
/** 任一方是否携带弱水 */
private _hasEnemySlowPassive(battle: ProjectBattleHandle): boolean {
const local = resolveCarriedEnemySlowWan(BattleBuffCarryStore.getPassives(battle.localPlayerId));
if (local.active && local.slowWan > 0) return true;
const ai = resolveCarriedEnemySlowWan(BattleBuffCarryStore.getPassives(AiHeadlessBagHost.AI_PLAYER_ID));
return ai.active && ai.slowWan > 0;
}
/** 出怪口怪物出现后播头顶减速 */
private _handleUnitSpawned = (event: BattleEvent): void => {
if (event.type !== BattleEventType.UnitSpawned) return;
if (!(event.spawnPort > 0)) return;
const entityId = event.entity;
if (getBattleBuffEnemySlowHost().resolveSlowWanForEntity(entityId) == null) return;
const gen = this._spawnFxGen;
schedulePresentation(SPAWN_PRESENT_SEC, () => {
if (gen !== this._spawnFxGen) return;
this._playSpawnHeadSlowFx(entityId);
});
};
/** 出怪口怪物头顶飘「减速」 */
private _playSpawnHeadSlowFx(entityId: EntityId): void {
const battle = this._battle;
const presenter = this._monsterPresenter;
if (!battle || !presenter) return;
if (!battle.session.world.isAlive(entityId)) return;
if (!presenter.getEntityNodeMap().get(entityId)?.isValid) return;
getMonsterStatusText().play(entityId, '减速');
}
/** 在地图格挂载弱水预制体 */
private _spawnOnGrid(grid: MapGrid, defenderPlayerId: number, port: number): void {
const gridIndex = grid.Index;
for (let i = 0; i < this._visuals.length; i++) {
if (this._visuals[i].gridIndex === gridIndex) return;
}
let node: Node | null = null;
try {
node = gg.res.getNode(BATTLE_HERO_BUFF_PATH, RUOSHUI_PREFAB_NAME, GBundle.BattleHero);
} catch (e) {
console.log('[RuoshuiBuff] 弱水预制体加载失败', String(e));
return;
}
if (!node?.isValid) {
console.log('[RuoshuiBuff] 弱水预制体缺失', RUOSHUI_PREFAB_NAME);
return;
}
node.parent = grid.node;
node.setPosition(0, 0, 0);
node.active = true;
this._visuals.push({ defenderPlayerId, port, gridIndex, node });
}
/** 注销出怪头顶减速监听 */
private _unbindSpawnHeadFx(): void {
const battle = this._battle;
if (battle && this._spawnListenerBound && this._onUnitSpawned) {
battle.session.events.off(BattleEventType.UnitSpawned, this._onUnitSpawned);
}
this._spawnListenerBound = false;
this._onUnitSpawned = null;
}
/** 移除不再携带弱水的防守方表现 */
private _pruneStaleVisuals(): void {
const battle = this._battle;
if (!battle) return;
const next: RuoshuiVisualEntry[] = [];
for (let i = 0; i < this._visuals.length; i++) {
const entry = this._visuals[i];
const adjust = resolveCarriedEnemySlowWan(BattleBuffCarryStore.getPassives(entry.defenderPlayerId));
if (!adjust.active || adjust.slowWan <= 0) {
this._releaseNode(entry.node);
continue;
}
next.push(entry);
}
this._visuals.length = 0;
for (let i = 0; i < next.length; i++) {
this._visuals.push(next[i]);
}
}
/** 回收全部弱水节点 */
private _clearVisuals(): void {
for (let i = 0; i < this._visuals.length; i++) {
this._releaseNode(this._visuals[i].node);
}
this._visuals.length = 0;
}
/** 中断进行中的飞行动画 */
private _abortInflightFlights(): void {
const pending = this._inflightFlightHosts.slice();
this._inflightFlightHosts.length = 0;
this._inflightGridIndices.length = 0;
for (let i = 0; i < pending.length; i++) {
PassiveIconArcFlight.abort(pending[i]);
}
}
/** 移除飞行中的地图格标记 */
private _removeInflightGridIndex(gridIndex: number): void {
const idx = this._inflightGridIndices.indexOf(gridIndex);
if (idx >= 0) this._inflightGridIndices.splice(idx, 1);
}
/** 归还节点到对象池 */
private _releaseNode(node: Node | null): void {
if (!node?.isValid) return;
gg.res.putNode(node);
}
}
let _inst: BattleGameRuoshuiBuffPresenter | null = null;
/** 弱水被动表现(单局复用) */
export function getBattleGameRuoshuiBuffPresenter(): BattleGameRuoshuiBuffPresenter {
if (!_inst) _inst = new BattleGameRuoshuiBuffPresenter();
return _inst;
}