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.
302 lines
9.5 KiB
302 lines
9.5 KiB
|
11 hours ago
|
import { isValid, Node, UIOpacity, UITransform, Vec3 } from 'cc';
|
||
|
|
import { GBundle } from '../../game/ConfigRes';
|
||
|
|
import type { MapManager } from './legacy/MapManager';
|
||
|
|
import type { MapGrid } from './legacy/MapGrid';
|
||
|
|
|
||
|
|
/** 宝箱预制路径 */
|
||
|
|
const CHEST_TIP_PATH = 'prefab/';
|
||
|
|
/** 宝箱预制名(原格子锁) */
|
||
|
|
const CHEST_TIP_NAME = '宝箱';
|
||
|
|
/** 每局埋宝箱数 */
|
||
|
|
const CHEST_COUNT = 6;
|
||
|
|
/** 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;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 开战埋 6 个宝箱(仅逻辑),并刷新 Dig 皮为默认无皮。
|
||
|
|
* @param map 地图
|
||
|
|
*/
|
||
|
|
init(map: MapManager): void {
|
||
|
|
this.clear();
|
||
|
|
this._map = map;
|
||
|
|
const 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) {
|
||
|
|
digs.push(g);
|
||
|
|
g.updateGrid();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (digs.length <= 0) {
|
||
|
|
console.warn('[MapDigChestHost] 无可开挖格,未埋宝箱');
|
||
|
|
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(CHEST_COUNT, shuffled.length);
|
||
|
|
if (shuffled.length < CHEST_COUNT) {
|
||
|
|
console.warn('[MapDigChestHost] 可开挖格不足', shuffled.length, '目标', CHEST_COUNT);
|
||
|
|
}
|
||
|
|
for (let i = 0; i < n; i++) {
|
||
|
|
this._slots.push({
|
||
|
|
index: shuffled[i].Index,
|
||
|
|
revealed: false,
|
||
|
|
dug: false,
|
||
|
|
tip: null,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
console.log('[MapDigChestHost] 埋宝箱', n, this._slots.map((s) => s.index));
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @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);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 挂宝箱到地图根(与格子锁同层),对齐格子世界坐标,避免被字块层挡住。
|
||
|
|
* @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 mapRoot = this._map?.mapGridParent;
|
||
|
|
if (!mapRoot?.isValid) {
|
||
|
|
console.warn('[MapDigChestHost] mapGridParent 无效,无法挂宝箱', grid.Index);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (slot.tip?.isValid) {
|
||
|
|
this._placeTipOnMap(slot.tip, cell, mapRoot);
|
||
|
|
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, mapRoot);
|
||
|
|
this._resetTipVisual(tip);
|
||
|
|
tip.active = true;
|
||
|
|
slot.tip = tip;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @param tip 宝箱节点
|
||
|
|
* @param cell 格子节点
|
||
|
|
* @param mapRoot 地图格子父节点
|
||
|
|
*/
|
||
|
|
private _placeTipOnMap(tip: Node, cell: Node, mapRoot: Node): void {
|
||
|
|
tip.parent = mapRoot;
|
||
|
|
tip.setSiblingIndex(mapRoot.children.length - 1);
|
||
|
|
cell.getWorldPosition(_tipWorld);
|
||
|
|
const ut = mapRoot.getComponent(UITransform);
|
||
|
|
if (ut) {
|
||
|
|
ut.convertToNodeSpaceAR(_tipWorld, _tipLocal);
|
||
|
|
tip.setPosition(_tipLocal);
|
||
|
|
} else {
|
||
|
|
tip.setWorldPosition(_tipWorld);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/** 对象池取出后复位显隐(飞入曾关 bg / 改 opacity) */
|
||
|
|
private _resetTipVisual(tip: Node): void {
|
||
|
|
tip.setScale(1, 1, 1);
|
||
|
|
const bg = tip.getChildByName('bg');
|
||
|
|
if (bg?.isValid) bg.active = true;
|
||
|
|
const box = tip.getChildByName('box');
|
||
|
|
if (box?.isValid) box.active = true;
|
||
|
|
let 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();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|