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.
261 lines
9.3 KiB
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;
|
||
|
|
}
|
||
|
|
}
|