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.
353 lines
12 KiB
353 lines
12 KiB
import { isValid, Node, UIOpacity, UITransform, Vec3 } from 'cc';
|
|
import { GBundle } from '../../game/ConfigRes';
|
|
import { ConstNumType } from '../../game/GameDataEnums';
|
|
import type { MapManager } from './legacy/MapManager';
|
|
import type { MapGrid } from './legacy/MapGrid';
|
|
|
|
/** 宝箱预制路径 */
|
|
const CHEST_TIP_PATH = 'prefab/';
|
|
/** 宝箱预制名(原格子锁) */
|
|
const CHEST_TIP_NAME = '宝箱';
|
|
/** tip 世界坐标复用 */
|
|
const _tipWorld = new Vec3();
|
|
/** tip 本地坐标复用 */
|
|
const _tipLocal = new Vec3();
|
|
|
|
/** 单格宝箱运行时 */
|
|
interface DigChestSlot {
|
|
/** 格子 Index */
|
|
index: number;
|
|
/** 是否已八邻揭示 */
|
|
revealed: boolean;
|
|
/** 是否已被铲开 */
|
|
dug: boolean;
|
|
/** 宝箱 tip */
|
|
tip: Node | null;
|
|
}
|
|
|
|
/** 铲成功后的宝箱处理结果 */
|
|
export interface DigChestOutcome {
|
|
/** 本次是否铲到宝箱 */
|
|
dugChest: boolean;
|
|
/** 本局已铲开宝箱数(含本次) */
|
|
dugChestCount: number;
|
|
/** 飞入起点世界坐标;未铲到宝箱为 null */
|
|
flyWorld: Vec3 | null;
|
|
}
|
|
|
|
/**
|
|
* 地图开挖宝箱:开战埋点、八邻揭示宝箱、铲开计数。
|
|
*/
|
|
export class MapDigChestHost {
|
|
private _map: MapManager | null = null;
|
|
private readonly _slots: DigChestSlot[] = [];
|
|
private _dugChestCount = 0;
|
|
|
|
/**
|
|
* 开战埋宝箱:Const38 为**每个阵营**固定个数,只在各自 CampArea 的 Dig 格内随机位置。
|
|
* @param map 地图
|
|
*/
|
|
init(map: MapManager): void {
|
|
this.clear();
|
|
this._map = map;
|
|
const digsByCamp: { camp: number; digs: MapGrid[] }[] = [];
|
|
for (let i = 0; i < map.mapGrids.length; i++) {
|
|
const g = map.mapGrids[i];
|
|
if (!g) continue;
|
|
g.chestRevealed = false;
|
|
if (!(g.isCanBeDig && !g.isCanPutText && !g.isCanWalk)) continue;
|
|
g.updateGrid();
|
|
const camp = (g.CampAreaID | 0);
|
|
let bucket: { camp: number; digs: MapGrid[] } | null = null;
|
|
for (let b = 0; b < digsByCamp.length; b++) {
|
|
if (digsByCamp[b].camp === camp) {
|
|
bucket = digsByCamp[b];
|
|
break;
|
|
}
|
|
}
|
|
if (!bucket) {
|
|
bucket = { camp, digs: [] };
|
|
digsByCamp.push(bucket);
|
|
}
|
|
bucket.digs.push(g);
|
|
}
|
|
if (digsByCamp.length <= 0) {
|
|
console.warn('[MapDigChestHost] 无可开挖格,未埋宝箱');
|
|
return;
|
|
}
|
|
const want = this._resolveChestCount();
|
|
if (want <= 0) {
|
|
console.warn('[MapDigChestHost] Const38 宝箱个数无效,未埋宝箱', want);
|
|
return;
|
|
}
|
|
for (let c = 0; c < digsByCamp.length; c++) {
|
|
this._buryInCamp(digsByCamp[c].camp, digsByCamp[c].digs, want);
|
|
}
|
|
console.log('[MapDigChestHost] 埋宝箱合计', this._slots.length, this._slots.map((s) => s.index));
|
|
}
|
|
|
|
/**
|
|
* 在单个阵营 Dig 池内埋固定个数(只随机位置)。
|
|
* @param camp CampAreaID
|
|
* @param digs 该阵营可开挖格
|
|
* @param want Const38 个数
|
|
*/
|
|
private _buryInCamp(camp: number, digs: MapGrid[], want: number): void {
|
|
if (digs.length <= 0) return;
|
|
const shuffled = digs.slice();
|
|
for (let i = shuffled.length - 1; i > 0; i--) {
|
|
const j = Math.floor(Math.random() * (i + 1));
|
|
const tmp = shuffled[i];
|
|
shuffled[i] = shuffled[j];
|
|
shuffled[j] = tmp;
|
|
}
|
|
const n = Math.min(want, shuffled.length);
|
|
if (shuffled.length < want) {
|
|
console.warn('[MapDigChestHost] 阵营可开挖格不足', camp, shuffled.length, '目标', want);
|
|
}
|
|
for (let i = 0; i < n; i++) {
|
|
this._slots.push({
|
|
index: shuffled[i].Index,
|
|
revealed: false,
|
|
dug: false,
|
|
tip: null,
|
|
});
|
|
}
|
|
console.log('[MapDigChestHost] 阵营埋宝箱', camp, n, shuffled.slice(0, n).map((g) => g.Index));
|
|
}
|
|
|
|
/** Const38 关卡技能宝箱个数 */
|
|
private _resolveChestCount(): number {
|
|
if (typeof gg === 'undefined' || !gg.data?.table?.getConst) {
|
|
console.warn('[MapDigChestHost] gg.data.table 未就绪,无法读 Const38');
|
|
return 0;
|
|
}
|
|
return Math.floor(Number(gg.data.table.getConst(ConstNumType.BattleDigChestCount)) || 0);
|
|
}
|
|
|
|
/**
|
|
* @param index 格 Index
|
|
* @returns 是否仍有未铲宝箱
|
|
*/
|
|
hasChest(index: number): boolean {
|
|
const s = this._slotOf(index);
|
|
return !!(s && !s.dug);
|
|
}
|
|
|
|
/**
|
|
* @param index 格 Index
|
|
* @returns 是否已揭示且未铲
|
|
*/
|
|
isRevealed(index: number): boolean {
|
|
const s = this._slotOf(index);
|
|
return !!(s && s.revealed && !s.dug);
|
|
}
|
|
|
|
/**
|
|
* 铲子逻辑开挖成功后立刻调用:只做八邻揭示(不等动画结束)。
|
|
* @param index 被铲格 Index
|
|
*/
|
|
revealAround(index: number): void {
|
|
this._revealNeighbors(index);
|
|
}
|
|
|
|
/**
|
|
* 铲子开挖动画收尾:铲宝箱则计入并返回飞入信息;并补一次八邻揭示。
|
|
* @param index 被铲格 Index
|
|
*/
|
|
onDigged(index: number): DigChestOutcome {
|
|
const outcome: DigChestOutcome = {
|
|
dugChest: false,
|
|
dugChestCount: this._dugChestCount,
|
|
flyWorld: null,
|
|
};
|
|
const dugSlot = this._slotOf(index);
|
|
if (dugSlot && !dugSlot.dug) {
|
|
dugSlot.dug = true;
|
|
this._dugChestCount++;
|
|
outcome.dugChest = true;
|
|
outcome.dugChestCount = this._dugChestCount;
|
|
outcome.flyWorld = this._takeFlyWorld(dugSlot);
|
|
this._releaseTip(dugSlot);
|
|
const grid = this._map?.getMapGridByIndex(index);
|
|
if (grid) {
|
|
grid.chestRevealed = false;
|
|
grid.updateGrid();
|
|
}
|
|
}
|
|
// 补揭示:防止动画中途回调丢失时邻箱未亮
|
|
this._revealNeighbors(index);
|
|
return outcome;
|
|
}
|
|
|
|
/** 退局:归还 tip、清标记 */
|
|
clear(): void {
|
|
for (let i = 0; i < this._slots.length; i++) {
|
|
const s = this._slots[i];
|
|
this._releaseTip(s);
|
|
const g = this._map?.getMapGridByIndex(s.index);
|
|
if (g) {
|
|
g.chestRevealed = false;
|
|
}
|
|
}
|
|
this._slots.length = 0;
|
|
this._dugChestCount = 0;
|
|
this._map = null;
|
|
}
|
|
|
|
private _slotOf(index: number): DigChestSlot | null {
|
|
for (let i = 0; i < this._slots.length; i++) {
|
|
if (this._slots[i].index === index) return this._slots[i];
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* 八邻未挖宝箱揭示。
|
|
* @param centerIndex 刚铲开的格
|
|
*/
|
|
private _revealNeighbors(centerIndex: number): void {
|
|
const map = this._map;
|
|
if (!map) return;
|
|
const center = map.getMapGridByIndex(centerIndex);
|
|
if (!center) return;
|
|
for (let dr = -1; dr <= 1; dr++) {
|
|
for (let dc = -1; dc <= 1; dc++) {
|
|
if (dr === 0 && dc === 0) continue;
|
|
const nb = map.getMapGrid(center.Row + dr, center.Col + dc);
|
|
if (!nb) continue;
|
|
const slot = this._slotOf(nb.Index);
|
|
if (!slot || slot.dug || slot.revealed) continue;
|
|
this._revealSlot(slot, nb);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param slot 宝箱槽
|
|
* @param grid 格子
|
|
*/
|
|
private _revealSlot(slot: DigChestSlot, grid: MapGrid): void {
|
|
slot.revealed = true;
|
|
grid.chestRevealed = true;
|
|
if (!grid.waitUnlockedSprite) {
|
|
console.warn('[MapDigChestHost] 揭示缺 waitUnlockedSprite', grid.Index);
|
|
}
|
|
grid.updateGrid();
|
|
this._ensureTip(slot, grid);
|
|
}
|
|
|
|
/**
|
|
* 挂宝箱到 MapManager 根(勿挂「格子」:该节点有 Layout,多一个子节点会整图重排偏移)。
|
|
* @param slot 宝箱槽
|
|
* @param grid 格子
|
|
*/
|
|
private _ensureTip(slot: DigChestSlot, grid: MapGrid): void {
|
|
const cell = grid.node;
|
|
if (!cell?.isValid) {
|
|
console.warn('[MapDigChestHost] 格子节点无效,无法挂宝箱', grid.Index);
|
|
return;
|
|
}
|
|
const tipLayer = this._tipLayer();
|
|
if (!tipLayer?.isValid) {
|
|
console.warn('[MapDigChestHost] 宝箱挂点无效', grid.Index);
|
|
return;
|
|
}
|
|
if (slot.tip?.isValid) {
|
|
this._placeTipOnMap(slot.tip, cell, tipLayer);
|
|
this._resetTipVisual(slot.tip);
|
|
slot.tip.active = true;
|
|
return;
|
|
}
|
|
if (typeof gg === 'undefined' || !gg.res?.getNode) {
|
|
console.warn('[MapDigChestHost] gg.res 未就绪,无法加载宝箱');
|
|
return;
|
|
}
|
|
const tip = gg.res.getNode(CHEST_TIP_PATH, CHEST_TIP_NAME, GBundle.BattleGame);
|
|
if (!tip?.isValid) {
|
|
console.warn('[MapDigChestHost] 缺少宝箱预制体', CHEST_TIP_NAME);
|
|
return;
|
|
}
|
|
this._placeTipOnMap(tip, cell, tipLayer);
|
|
this._resetTipVisual(tip);
|
|
tip.active = true;
|
|
slot.tip = tip;
|
|
}
|
|
|
|
/**
|
|
* 宝箱 tip 层:MapManager 根下独立节点,压在「格子」之上、不受 Layout 影响。
|
|
*/
|
|
private _tipLayer(): Node | null {
|
|
const map = this._map;
|
|
if (!map?.node?.isValid) return null;
|
|
let layer = map.node.getChildByName('宝箱提示层');
|
|
if (layer?.isValid) return layer;
|
|
layer = new Node('宝箱提示层');
|
|
layer.layer = map.node.layer;
|
|
layer.parent = map.node;
|
|
const grids = map.mapGridParent;
|
|
if (grids?.isValid) {
|
|
layer.setSiblingIndex(grids.getSiblingIndex() + 1);
|
|
} else {
|
|
layer.setSiblingIndex(map.node.children.length - 1);
|
|
}
|
|
return layer;
|
|
}
|
|
|
|
/**
|
|
* @param tip 宝箱节点
|
|
* @param cell 格子节点
|
|
* @param tipLayer 宝箱提示层
|
|
*/
|
|
private _placeTipOnMap(tip: Node, cell: Node, tipLayer: Node): void {
|
|
tip.parent = tipLayer;
|
|
tip.setSiblingIndex(tipLayer.children.length - 1);
|
|
cell.getWorldPosition(_tipWorld);
|
|
const ut = tipLayer.getComponent(UITransform) ?? tipLayer.addComponent(UITransform);
|
|
ut.convertToNodeSpaceAR(_tipWorld, _tipLocal);
|
|
tip.setPosition(_tipLocal);
|
|
}
|
|
|
|
/** 对象池取出后复位显隐(飞入曾改 scale / opacity) */
|
|
private _resetTipVisual(tip: Node): void {
|
|
tip.setScale(1, 1, 1);
|
|
const box = tip.getChildByName('box');
|
|
if (box?.isValid) box.active = true;
|
|
const op = tip.getComponent(UIOpacity);
|
|
if (op) op.opacity = 255;
|
|
}
|
|
|
|
/**
|
|
* 取飞入起点世界坐标。
|
|
* @param slot 宝箱槽
|
|
*/
|
|
private _takeFlyWorld(slot: DigChestSlot): Vec3 {
|
|
const tip = slot.tip;
|
|
if (tip?.isValid) {
|
|
tip.getWorldPosition(_tipWorld);
|
|
return _tipWorld.clone();
|
|
}
|
|
const grid = this._map?.getMapGridByIndex(slot.index);
|
|
if (grid?.node?.isValid) {
|
|
grid.node.getWorldPosition(_tipWorld);
|
|
return _tipWorld.clone();
|
|
}
|
|
console.warn('[MapDigChestHost] 铲宝箱无飞入坐标', slot.index);
|
|
return new Vec3();
|
|
}
|
|
|
|
/**
|
|
* @param slot 宝箱槽
|
|
*/
|
|
private _releaseTip(slot: DigChestSlot): void {
|
|
const tip = slot.tip;
|
|
slot.tip = null;
|
|
if (!tip || !isValid(tip)) return;
|
|
tip.active = false;
|
|
if (typeof gg !== 'undefined' && tip['resKey'] && gg.res?.putNode) {
|
|
gg.res.putNode(tip);
|
|
} else {
|
|
tip.destroy();
|
|
}
|
|
}
|
|
}
|
|
|