import type { EntityId } from 'db://assets/mx/module/ecs'; import { TeamComp, type BattleSession } from 'db://assets/mx/module/battle'; import type { ProjectBattleHandle } from '../runtime/BattleRuntime'; import { findCompositionForEntity, findGridIndexForEntity } from '../runtime/DeployEntityQuery'; import type { Player } from '../../game/Player'; import type { CocosBagBoardView } from '../view/CocosBagBoardView'; /** 降级后按新等级重绑战斗(由 BattleRuntime 注入,避免 Host 回引 CombatSetup) */ export interface ILevelDownCombatRefresh { /** * @param session 战斗会话 * @param entityId 塔实体 * @param level 新合成等级 * @param player 所属玩家 */ (session: BattleSession, entityId: EntityId, level: number, player: Player): void; } /** 一次限时降级 */ interface LevelDownEntry { /** 被降级实体 */ entity: EntityId; /** 字块所属玩家 */ playerId: number; /** 当前格 */ gridIndex: number; /** 施加前等级 */ originLevel: number; /** 实际降低级数 */ applied: number; /** 剩余秒 */ remain: number; } let _host: TowerLevelDownHost | null = null; /** 龟丞相 atk_buff 类型7:限时降级 */ export function getTowerLevelDownHost(): TowerLevelDownHost { if (!_host) _host = new TowerLevelDownHost(); return _host; } /** * 非组合字限时降级:最低 1 级,期间禁合成升级、可移动;到期恢复。 * 表现只改等级字色,不加框。 */ export class TowerLevelDownHost { private readonly _entries: LevelDownEntry[] = []; private _bagView: CocosBagBoardView | null = null; /** 降级后刷新战斗;未绑定时只改合成等级 */ private _refreshCombat: ILevelDownCombatRefresh | null = null; /** * 绑定棋盘视图(刷等级字色)。 * @param view 本机棋盘 */ bindBagView(view: CocosBagBoardView | null): void { this._bagView = view; } /** * 绑定降级后的战斗刷新。 * @param fn 刷新函数;传 null 清除 */ bindCombatRefresh(fn: ILevelDownCombatRefresh | null): void { this._refreshCombat = fn; } /** * 施加或刷新降级。 * @param battle 战斗句柄 * @param target 目标塔 * @param delta 降低级数 * @param durationSec 持续时间 */ apply(battle: ProjectBattleHandle, target: EntityId, delta: number, durationSec: number): void { const d = Math.max(1, Math.floor(Number(delta) || 1)); const dur = Number(durationSec) || 0; if (!(dur > 0)) { console.log('[TowerLevelDown] atk_buff 类型7缺少持续时间', target, durationSec); this._dropOnce(battle, target, d); return; } const session = battle.session; if (!session.world.isAlive(target)) return; const existing = this._find(target); if (existing) { existing.remain = dur; this._syncGrid(battle, existing); this._setFx(existing.gridIndex, true); return; } const team = session.world.getComponent(target, TeamComp); const playerId = Math.floor(Number(team?.ownerId) || 0); const player = battle.getPlayer(playerId); const map = battle.Map; if (!player || !map) return; const comp = findCompositionForEntity(target, player, session, map); if (!comp || comp.gridIndices.length > 1) return; const gridIndex = findGridIndexForEntity(target, player, session, map); if (gridIndex == null) return; const origin = Math.max(1, comp.level); const next = Math.max(1, origin - d); const applied = origin - next; if (applied > 0 && !this._setLevel(battle, playerId, gridIndex, next, target)) return; this._entries.push({ entity: target, playerId, gridIndex, originLevel: origin, applied, remain: dur }); this._setFx(gridIndex, true); } /** * 该格是否处于降级禁合成。 * @param playerId 玩家 * @param gridIndex 地图格 */ isGridBlocked(playerId: number, gridIndex: number): boolean { for (let i = 0; i < this._entries.length; i++) { const e = this._entries[i]; if (e.playerId === playerId && e.gridIndex === gridIndex) return true; } return false; } /** * 推进计时并跟随位移。 * @param battle 战斗句柄 * @param dt 逻辑秒 */ tick(battle: ProjectBattleHandle, dt: number): void { if (this._entries.length <= 0) return; for (let i = this._entries.length - 1; i >= 0; i--) { const e = this._entries[i]; this._syncGrid(battle, e); if (!battle.session.world.isAlive(e.entity)) { this._end(battle, i, false); continue; } e.remain -= dt; if (e.remain <= 0) this._end(battle, i, true); else this._setFx(e.gridIndex, true); } } /** 战斗结束:清字色,不回等级 */ clear(): void { for (let i = this._entries.length - 1; i >= 0; i--) { this._setFx(this._entries[i].gridIndex, false); } this._entries.length = 0; this._bagView = null; } /** * 无持续时间:只降一次级。 * @param battle 战斗句柄 * @param target 目标 * @param delta 降低级数 */ private _dropOnce(battle: ProjectBattleHandle, target: EntityId, delta: number): void { const session = battle.session; if (!session.world.isAlive(target)) return; const team = session.world.getComponent(target, TeamComp); const playerId = Math.floor(Number(team?.ownerId) || 0); const player = battle.getPlayer(playerId); const map = battle.Map; if (!player || !map) return; const comp = findCompositionForEntity(target, player, session, map); if (!comp || comp.gridIndices.length > 1) return; const gridIndex = findGridIndexForEntity(target, player, session, map); if (gridIndex == null) return; const next = Math.max(1, comp.level - delta); if (next === comp.level) return; this._setLevel(battle, playerId, gridIndex, next, target); } /** * 同步实体当前格并迁移字色。 * @param battle 战斗句柄 * @param e 运行态 */ private _syncGrid(battle: ProjectBattleHandle, e: LevelDownEntry): void { const player = battle.getPlayer(e.playerId); const map = battle.Map; if (!player || !map || !battle.session.world.isAlive(e.entity)) return; const live = findGridIndexForEntity(e.entity, player, battle.session, map); if (live == null || live === e.gridIndex) return; this._setFx(e.gridIndex, false); e.gridIndex = live; this._setFx(live, true); } /** * 结束一条:可选恢复等级。 * @param battle 战斗句柄 * @param index 下标 * @param restore 是否加回等级 */ private _end(battle: ProjectBattleHandle, index: number, restore: boolean): void { const e = this._entries[index]; this._entries.splice(index, 1); this._setFx(e.gridIndex, false); if (!restore || e.applied <= 0) return; if (!battle.session.world.isAlive(e.entity)) return; const player = battle.getPlayer(e.playerId); const map = battle.Map; if (!player || !map) return; const grid = findGridIndexForEntity(e.entity, player, battle.session, map) ?? e.gridIndex; const comp = findCompositionForEntity(e.entity, player, battle.session, map); const cur = comp?.level ?? 0; if (!(cur > 0)) return; this._setLevel(battle, e.playerId, grid, cur + e.applied, e.entity); } /** * 改合成等级并刷新战斗属性。 * @param battle 战斗句柄 * @param playerId 玩家 * @param gridIndex 格 * @param newLevel 目标等级 * @param entity 实体 */ private _setLevel(battle: ProjectBattleHandle, playerId: number, gridIndex: number, newLevel: number, entity: EntityId): boolean { const player = battle.getPlayer(playerId); const bag = player?.bagSession; if (!bag) return false; const view = this._bagView; view?.setSuppressUpgradeFx(true); const ok = bag.merge.setCompositionLevelAtGrid(gridIndex, newLevel); view?.setSuppressUpgradeFx(false); if (!ok) return false; if (!battle.session.world.isAlive(entity)) return true; if (!this._refreshCombat) { console.warn('[TowerLevelDown] 战斗刷新未绑定', entity); return true; } this._refreshCombat(battle.session, entity, newLevel, player); return true; } /** * 按实体查找。 * @param entity 实体 */ private _find(entity: EntityId): LevelDownEntry | null { for (let i = 0; i < this._entries.length; i++) { if (this._entries[i].entity === entity) return this._entries[i]; } return null; } /** * 开关格子降级字色。 * @param gridIndex 格 * @param on 是否降级中 */ private _setFx(gridIndex: number, on: boolean): void { this._bagView?.getMapGrids().get(gridIndex)?.setLevelDownStyle(on); } }