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/battle/combat/TowerRuinHost.ts

261 lines
9.3 KiB

3 weeks ago
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) || bag.merge.isComboGlyphAt(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;
}
}