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/map/MapDigChestHost.ts

354 lines
12 KiB

10 hours ago
import { isValid, Node, UIOpacity, UITransform, Vec3 } from 'cc';
import { GBundle } from '../../game/ConfigRes';
10 hours ago
import { ConstNumType } from '../../game/GameDataEnums';
10 hours ago
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;
/**
10 hours ago
* 开战埋宝箱:Const38 为**每个阵营**固定个数,只在各自 CampArea 的 Dig 格内随机位置。
10 hours ago
* @param map 地图
*/
init(map: MapManager): void {
this.clear();
this._map = map;
10 hours ago
const digsByCamp: { camp: number; digs: MapGrid[] }[] = [];
10 hours ago
for (let i = 0; i < map.mapGrids.length; i++) {
const g = map.mapGrids[i];
if (!g) continue;
g.chestRevealed = false;
10 hours ago
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;
}
10 hours ago
}
10 hours ago
if (!bucket) {
bucket = { camp, digs: [] };
digsByCamp.push(bucket);
}
bucket.digs.push(g);
10 hours ago
}
10 hours ago
if (digsByCamp.length <= 0) {
10 hours ago
console.warn('[MapDigChestHost] 无可开挖格,未埋宝箱');
return;
}
10 hours ago
const want = this._resolveChestCount();
if (want <= 0) {
console.warn('[MapDigChestHost] Const38 宝箱个数无效,未埋宝箱', want);
return;
}
10 hours ago
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;
10 hours ago
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;
}
10 hours ago
const n = Math.min(want, shuffled.length);
if (shuffled.length < want) {
10 hours ago
console.warn('[MapDigChestHost] 阵营可开挖格不足', camp, shuffled.length, '目标', want);
10 hours ago
}
for (let i = 0; i < n; i++) {
this._slots.push({
index: shuffled[i].Index,
revealed: false,
dug: false,
tip: null,
});
}
10 hours ago
console.log('[MapDigChestHost] 阵营埋宝箱', camp, n, shuffled.slice(0, n).map((g) => g.Index));
10 hours ago
}
10 hours ago
/** 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);
}
10 hours ago
/**
* @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);
}
/**
10 hours ago
* 挂宝箱到 MapManager 根(勿挂「格子」:该节点有 Layout,多一个子节点会整图重排偏移)。
10 hours ago
* @param slot 宝箱槽
* @param grid 格子
*/
private _ensureTip(slot: DigChestSlot, grid: MapGrid): void {
const cell = grid.node;
if (!cell?.isValid) {
console.warn('[MapDigChestHost] 格子节点无效,无法挂宝箱', grid.Index);
return;
}
10 hours ago
const tipLayer = this._tipLayer();
if (!tipLayer?.isValid) {
console.warn('[MapDigChestHost] 宝箱挂点无效', grid.Index);
10 hours ago
return;
}
if (slot.tip?.isValid) {
10 hours ago
this._placeTipOnMap(slot.tip, cell, tipLayer);
10 hours ago
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;
}
10 hours ago
this._placeTipOnMap(tip, cell, tipLayer);
10 hours ago
this._resetTipVisual(tip);
tip.active = true;
slot.tip = tip;
}
10 hours ago
/**
* 宝箱 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;
}
10 hours ago
/**
* @param tip 宝箱节点
* @param cell 格子节点
10 hours ago
* @param tipLayer 宝箱提示层
10 hours ago
*/
10 hours ago
private _placeTipOnMap(tip: Node, cell: Node, tipLayer: Node): void {
tip.parent = tipLayer;
tip.setSiblingIndex(tipLayer.children.length - 1);
10 hours ago
cell.getWorldPosition(_tipWorld);
10 hours ago
const ut = tipLayer.getComponent(UITransform) ?? tipLayer.addComponent(UITransform);
ut.convertToNodeSpaceAR(_tipWorld, _tipLocal);
tip.setPosition(_tipLocal);
10 hours ago
}
10 hours ago
/** 对象池取出后复位显隐(飞入曾改 scale / opacity) */
10 hours ago
private _resetTipVisual(tip: Node): void {
tip.setScale(1, 1, 1);
const box = tip.getChildByName('box');
if (box?.isValid) box.active = true;
10 hours ago
const op = tip.getComponent(UIOpacity);
10 hours ago
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();
}
}
}