import type { EntityId } from 'db://assets/mx/module/ecs'; import { TeamComp } from 'db://assets/mx/module/battle'; import type { ProjectBattleHandle } from '../runtime/BattleRuntime'; import { findEntityIdByGridIndex, findGridIndexForEntity } from '../runtime/DeployEntityQuery'; import type { CocosBagBoardView } from '../view/CocosBagBoardView'; import type { TextGrid } from '../view/TextGrid'; /** 一次损毁失效 */ interface RuinEntry { /** 被损毁实体(进背包后为 0) */ entity: EntityId; /** 字块所属玩家 */ playerId: number; } let _host: TowerRuinHost | null = null; /** 牛魔王 atk_buff 类型12:字块失效直至合成升级 */ export function getTowerRuinHost(): TowerRuinHost { if (!_host) _host = new TowerRuinHost(); return _host; } /** * 基础字损毁:禁攻,可拖可换位,合成升级后恢复。 * 状态写在字块记录上(进背包/换位跟字走);禁攻按实体登记。 */ export class TowerRuinHost { private readonly _entries: RuinEntry[] = []; private _bagView: CocosBagBoardView | null = null; private _battle: ProjectBattleHandle | null = null; /** 字块损毁变化后才全图刷倒下 */ private _visualDirty = false; /** 无登记时降频扫格 */ private _scanAcc = 0; /** * 绑定棋盘视图(刷损毁字形)。 * @param view 本机棋盘 */ bindBagView(view: CocosBagBoardView | null): void { this._bagView = view; } /** * 施加损毁。已失效则只刷新表现。 * @param battle 战斗句柄 * @param target 目标塔 */ apply(battle: ProjectBattleHandle, target: EntityId): void { this._battle = battle; 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; const bag = player?.bagSession; if (!player || !map || !bag) { console.log('[TowerRuin] 损毁缺少玩家或地图', target, playerId); return; } const gridIndex = findGridIndexForEntity(target, player, session, map); if (gridIndex == null) { console.log('[TowerRuin] 损毁找不到格子', target); return; } if (bag.merge.isMultiCellCompositionAt(gridIndex)) { console.log('[TowerRuin] 损毁只作用于非多格合成单字', target, gridIndex); return; } const tile = bag.merge.map.getTile(gridIndex); if (!tile) { console.log('[TowerRuin] 损毁找不到字块', target, gridIndex); return; } tile.ruined = true; this._visualDirty = true; session.presentation.cancelByCaster(target); if (!this._find(target)) { this._entries.push({ entity: target, playerId }); } this._setMapRuinStyle(gridIndex, true); } /** * 该实体是否处于损毁禁攻。 * @param entity 实体 */ isEntityRuined(entity: EntityId): boolean { if (this._find(entity)) return true; return this._entityTileRuined(entity); } /** * 合成升级后解除该格字块损毁。 * @param playerId 玩家 * @param gridIndex 升级格 */ releaseByUpgrade(playerId: number, gridIndex: number): void { const battle = this._battle; const player = battle?.getPlayer(playerId); const tile = player?.bagSession?.merge.map.getTile(gridIndex); if (tile) tile.ruined = false; const live = battle && player && battle.Map ? findEntityIdByGridIndex(gridIndex, battle.session, battle.Map, player) : null; this._dropEntity(live); this._setMapRuinStyle(gridIndex, false); this._visualDirty = true; } /** * 背包合成升级后解除该槽字块损毁。 * @param playerId 玩家 * @param slotIndex 背包槽 */ releaseBagUpgrade(playerId: number, slotIndex: number): void { const slot = this._battle?.getPlayer(playerId)?.bagSession?.merge.bag.getSlot(slotIndex); if (slot) slot.ruined = false; this._bagView?.getBagGrids().get(slotIndex)?.setRuinStyle(false); this._visualDirty = true; } /** * 把损毁登记到当前字块实体,并按字块记录刷新倒下表现。 * @param battle 战斗句柄 */ tick(battle: ProjectBattleHandle): void { this._battle = battle; if (battle.isBagDragSlowActive) return; this._scanAcc++; if (this._entries.length > 0 || this._scanAcc % 20 === 0) { this._rebindFromTiles(battle); } if (!this._visualDirty) return; this._syncVisuals(battle); this._visualDirty = false; } /** 战斗结束:清字形与登记 */ clear(): void { const view = this._bagView; if (view) { view.getMapGrids().forEach((grid: TextGrid) => grid.setRuinStyle(false, true)); view.getBagGrids().forEach((grid: TextGrid) => grid.setRuinStyle(false, true)); view.forEachEnemyMapTextGrid((grid) => grid.setRuinStyle(false, true)); } this._entries.length = 0; this._visualDirty = false; this._scanAcc = 0; this._bagView = null; this._battle = null; } /** * 场上损毁字块:实体重建后重新登记禁攻。 * @param battle 战斗句柄 */ private _rebindFromTiles(battle: ProjectBattleHandle): void { const players = battle.players; const map = battle.Map; for (let i = this._entries.length - 1; i >= 0; i--) { const e = this._entries[i]; if (e.entity !== 0 && battle.session.world.isAlive(e.entity) && this._entityTileRuined(e.entity)) continue; this._entries.splice(i, 1); } if (!map) return; for (let p = 0; p < players.length; p++) { const player = players[p]; const bag = player?.bagSession; if (!player || !bag) continue; bag.merge.map.forEachTile((gridIndex, tile) => { if (!tile.ruined) return; const live = findEntityIdByGridIndex(gridIndex, battle.session, map, player); if (live == null || this._find(live)) return; this._entries.push({ entity: live, playerId: player.playerId }); this._visualDirty = true; battle.session.presentation.cancelByCaster(live); }); } } /** * 按本机与敌方字块 ruined 刷新倒下。 * @param battle 战斗句柄 */ private _syncVisuals(battle: ProjectBattleHandle): void { const view = this._bagView; const bag = battle.player?.bagSession; if (view && bag) { view.getMapGrids().forEach((grid: TextGrid, idx: number) => { grid.setRuinStyle(!!bag.merge.map.getTile(idx)?.ruined); }); const slots = bag.getBagSlots(); view.getBagGrids().forEach((grid: TextGrid, slotIndex: number) => { grid.setRuinStyle(!!slots[slotIndex]?.ruined); }); } const remoteBag = battle.remotePlayer?.bagSession; if (!view || !remoteBag) return; view.forEachEnemyMapTextGrid((grid, idx) => { grid.setRuinStyle(!!remoteBag.merge.map.getTile(idx)?.ruined); }); } /** * 己方或敌方地图字块倒下。 * @param gridIndex 地图格 * @param on 是否损毁 */ private _setMapRuinStyle(gridIndex: number, on: boolean): void { const local = this._bagView?.getMapGrids().get(gridIndex); if (local?.isValid) { local.setRuinStyle(on); return; } this._bagView?.getEnemyMapTextGrid(gridIndex)?.setRuinStyle(on); } /** * 实体当前格的字块是否损毁。 * @param entity 实体 */ private _entityTileRuined(entity: EntityId): boolean { const battle = this._battle; if (!battle?.session.world.isAlive(entity)) return false; const team = battle.session.world.getComponent(entity, TeamComp); const playerId = Math.floor(Number(team?.ownerId) || 0); const player = battle.getPlayer(playerId); const map = battle.Map; if (!player || !map) return false; const gridIndex = findGridIndexForEntity(entity, player, battle.session, map); if (gridIndex == null) return false; return !!player.bagSession?.merge.map.getTile(gridIndex)?.ruined; } /** * 去掉实体登记。 * @param entity 实体 */ private _dropEntity(entity: EntityId | null): void { if (entity == null) return; for (let i = this._entries.length - 1; i >= 0; i--) { if (this._entries[i].entity === entity) this._entries.splice(i, 1); } } /** * 按实体查找。 * @param entity 实体 */ private _find(entity: EntityId): RuinEntry | null { if (entity === 0) return null; for (let i = 0; i < this._entries.length; i++) { if (this._entries[i].entity === entity) return this._entries[i]; } return null; } }