|
|
|
|
@ -45,7 +45,6 @@ import { TableNames } from '../../game/ConfigTableData'; |
|
|
|
|
import type { BattleWeaponDesc1PanelData } from '../../ui/BattleWeaponDesc1/UIBattleWeaponDesc1'; |
|
|
|
|
import type { BattleWeaponDescHeroPanelData } from '../../ui/BattleWeaponDescHero/UIBattleWeaponDescHero'; |
|
|
|
|
import { BattleBagSession } from '../bag/BattleBagSession'; |
|
|
|
|
import { FIXED_MAP_LEVEL_ID } from '../config/ChapterLevelBuilder'; |
|
|
|
|
import { DeploySync } from '../runtime/DeploySync'; |
|
|
|
|
import { composeKey } from '../runtime/ComposeUnitKey'; |
|
|
|
|
import { findEntityIdByGridIndex } from '../runtime/DeployEntityQuery'; |
|
|
|
|
@ -141,6 +140,8 @@ export class CocosBagBoardView { |
|
|
|
|
private _bagList: Node | null = null; |
|
|
|
|
/** ui_textList 开启 / 待解锁 / 未解锁 */ |
|
|
|
|
private readonly _land = new TextListLandHost(); |
|
|
|
|
/** 开格展示字块补显重试帧计数(textGrid 预制体异步加载未就绪时留空格) */ |
|
|
|
|
private _landGlyphRetryFrame = 0; |
|
|
|
|
/** AI 神偷落背包时飞行目标(BattleGame ui_otherBag) */ |
|
|
|
|
private _otherBagAnchor: Node | null = null; |
|
|
|
|
private _mapRoot: Node | null = null; |
|
|
|
|
@ -268,10 +269,9 @@ export class CocosBagBoardView { |
|
|
|
|
private _fillMissingBagSlotGrids(usedTextGrids?: MembershipSet<TextGrid>): void { |
|
|
|
|
if (!this._bagSession || !this._bagList) return; |
|
|
|
|
const slots = this._bagSession.getBagSlots(); |
|
|
|
|
const children = this._bagList.children; |
|
|
|
|
for (let i = 0; i < slots.length; i++) { |
|
|
|
|
const record = slots[i]; |
|
|
|
|
const slotNode = children[i]; |
|
|
|
|
const slotNode = this._bagSlotNodeAt(i); |
|
|
|
|
if (!record) { |
|
|
|
|
if (slotNode?.isValid && !this._findFlightGridForBag(i)) this._purgeTextGridsOnNode(slotNode); |
|
|
|
|
this._slotGrids.delete(i); |
|
|
|
|
@ -302,6 +302,16 @@ export class CocosBagBoardView { |
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** 槽位下标对应的 ui_textList 格子节点(gridN → N;temp 与无名节点不算) */ |
|
|
|
|
private _bagSlotNodeAt(slotIndex: number): Node | null { |
|
|
|
|
if (!this._bagList?.isValid) return null; |
|
|
|
|
const children = this._bagList.children; |
|
|
|
|
for (let i = 0; i < children.length; i++) { |
|
|
|
|
if (TextListLandHost.indexOfNode(children[i]) === slotIndex) return children[i]; |
|
|
|
|
} |
|
|
|
|
return null; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** 节点是否挂在地图格 MapGrid 下 */ |
|
|
|
|
private _isNodeUnderMapCell(node: Node | null | undefined): boolean { |
|
|
|
|
if (!node?.isValid || !this._map?.mapGrids?.length) return false; |
|
|
|
|
@ -401,7 +411,7 @@ export class CocosBagBoardView { |
|
|
|
|
if (!this._bagSession || !this._bagList || !grid?.isValid || !grid.node?.isValid) return; |
|
|
|
|
const record = this._bagSession.getBagSlots()[slotIndex]; |
|
|
|
|
if (!record) return; |
|
|
|
|
const slotNode = this._bagList.children[slotIndex]; |
|
|
|
|
const slotNode = this._bagSlotNodeAt(slotIndex); |
|
|
|
|
if (!slotNode?.isValid) return; |
|
|
|
|
const entity = this._buildBagSlotEntity(slotIndex); |
|
|
|
|
if (!entity) return; |
|
|
|
|
@ -444,7 +454,7 @@ export class CocosBagBoardView { |
|
|
|
|
const parent = this._map?.getMapGridByIndex(target.gridIndex)?.node; |
|
|
|
|
this._commitMapTextGridView(target.gridIndex, swap ? grid : this._keeperAfterDrop(grid, parent)); |
|
|
|
|
} else { |
|
|
|
|
const slotNode = this._bagList?.children[target.slotIndex]; |
|
|
|
|
const slotNode = this._bagSlotNodeAt(target.slotIndex); |
|
|
|
|
const incoming = swap && grid.weapon2Id !== 0 ? grid : this._keeperAfterDrop(grid, slotNode); |
|
|
|
|
this._commitBagTextGridView(target.slotIndex, incoming); |
|
|
|
|
} |
|
|
|
|
@ -679,7 +689,7 @@ export class CocosBagBoardView { |
|
|
|
|
/** 保证背包槽有 TextGrid(逻辑有 record、视图缺失或节点已回池时重建) */ |
|
|
|
|
ensureBagSlotGrid(slotIndex: number): TextGrid | null { |
|
|
|
|
if (!this._bagSession || !this._bagList) return null; |
|
|
|
|
const slotNode = this._bagList.children[slotIndex]; |
|
|
|
|
const slotNode = this._bagSlotNodeAt(slotIndex); |
|
|
|
|
const record = this._bagSession.getBagSlots()[slotIndex]; |
|
|
|
|
if (!record || !slotNode?.isValid) return null; |
|
|
|
|
const entity = buildTextGridEntityData( |
|
|
|
|
@ -820,7 +830,7 @@ export class CocosBagBoardView { |
|
|
|
|
this._fillMissingBagSlotGrids(); |
|
|
|
|
for (let i = 0; i < slots.length; i++) { |
|
|
|
|
if (!slots[i]) continue; |
|
|
|
|
const slotNode = this._bagList.children[i]; |
|
|
|
|
const slotNode = this._bagSlotNodeAt(i); |
|
|
|
|
if (!slotNode?.isValid) continue; |
|
|
|
|
const live = this._resolveBagSlotLiveGrid(i, slotNode, slots[i].weapon2Id) ?? this.ensureBagSlotGrid(i); |
|
|
|
|
if (!live?.isValid) continue; |
|
|
|
|
@ -869,7 +879,7 @@ export class CocosBagBoardView { |
|
|
|
|
const bagSlot = this._deferredBagUpgradeSlot; |
|
|
|
|
if (bagSlot != null) { |
|
|
|
|
this._deferredBagUpgradeSlot = null; |
|
|
|
|
const slotNode = this._bagList?.children[bagSlot]; |
|
|
|
|
const slotNode = this._bagSlotNodeAt(bagSlot); |
|
|
|
|
if (this._bagSession?.getBagSlots()[bagSlot] && slotNode?.isValid) { |
|
|
|
|
const grid = this._resolveBagTextGridForUpgrade(bagSlot, slotNode); |
|
|
|
|
const record = this._bagSession.getBagSlots()[bagSlot]; |
|
|
|
|
@ -886,7 +896,7 @@ export class CocosBagBoardView { |
|
|
|
|
|
|
|
|
|
private _vacateBagSlotView(slotIndex: number): void { |
|
|
|
|
this._grids.deleteBagSlot(slotIndex); |
|
|
|
|
const slotNode = this._bagList?.children[slotIndex]; |
|
|
|
|
const slotNode = this._bagSlotNodeAt(slotIndex); |
|
|
|
|
if (!slotNode?.isValid) return; |
|
|
|
|
for (const child of [...slotNode.children]) { |
|
|
|
|
const tg = child.getComponent(TextGrid); |
|
|
|
|
@ -1136,7 +1146,7 @@ export class CocosBagBoardView { |
|
|
|
|
/** 技能产桃:本机格心飞向 ui_money;敌方格心 coinFly 原地弹跳 */ |
|
|
|
|
private _skillPeachFlyHandler: ((gridIndex: number, peachCount: number, ownerPlayerId: number) => void) | null = null; |
|
|
|
|
|
|
|
|
|
/** 砚台(btnIllustrated)世界坐标,供背包刷新从砚台出字 */ |
|
|
|
|
/** 出字起点(btnRefresh)世界坐标 */ |
|
|
|
|
setInkstoneWorldPosProvider(provider: (() => Readonly<Vec3> | null) | null): void { |
|
|
|
|
this._inkstoneWorldPosProvider = provider; |
|
|
|
|
} |
|
|
|
|
@ -1183,6 +1193,7 @@ export class CocosBagBoardView { |
|
|
|
|
|
|
|
|
|
/** 帧更新:己方/敌方地图蟠桃树按技能 CD 切换桃字1/桃字2 循环 */ |
|
|
|
|
tickPeachTreePresentations(): void { |
|
|
|
|
this._tickLandGlyphRetry(); |
|
|
|
|
for (const grid of this._mapGrids.values()) { |
|
|
|
|
if (grid?.isValid) grid.tickPeachTreeCdPresentation(); |
|
|
|
|
} |
|
|
|
|
@ -1194,6 +1205,15 @@ export class CocosBagBoardView { |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** 空开启槽补显:每 10 帧检查一次,textGrid 预制体就绪后自动补齐;前 30 秒后停轮询 */ |
|
|
|
|
private _tickLandGlyphRetry(): void { |
|
|
|
|
this._landGlyphRetryFrame++; |
|
|
|
|
if (this._landGlyphRetryFrame >= 1800) return; // 30 秒后停
|
|
|
|
|
if (this._landGlyphRetryFrame % 10 !== 0) return; |
|
|
|
|
if (!this._bagSession) return; |
|
|
|
|
this._fillMissingBagSlotGrids(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
getInkstoneWorldPosition(): Readonly<Vec3> | null { |
|
|
|
|
return this._inkstoneWorldPosProvider?.() ?? null; |
|
|
|
|
} |
|
|
|
|
@ -1229,7 +1249,7 @@ export class CocosBagBoardView { |
|
|
|
|
this._battle = battle; |
|
|
|
|
this._map = map; |
|
|
|
|
this._bagList = bagList; |
|
|
|
|
this._land.bind(FIXED_MAP_LEVEL_ID, bagList); |
|
|
|
|
this._land.bind(battle.LevelConfigId, bagList); |
|
|
|
|
this._mapRoot = mapRoot; |
|
|
|
|
this._dragLayer = dragLayer ?? mapRoot; |
|
|
|
|
if (this._dragLayer?.isValid) { |
|
|
|
|
@ -1240,8 +1260,16 @@ export class CocosBagBoardView { |
|
|
|
|
this._linePresenter.bind(this._lineNode); |
|
|
|
|
this._rangePresenter.bind(rangeNode ?? null, mapRoot?.parent ?? null); |
|
|
|
|
|
|
|
|
|
this._bagSession = new BattleBagSession(map, battle.LevelConfigId, battle.session); |
|
|
|
|
// 槽位下标与格子下标(gridN → N)一致,槽数按最大格子下标 + 1 分配(slot0 对应不到格子,保持空)
|
|
|
|
|
let maxLandIndex = 0; |
|
|
|
|
const bagChildren = bagList.children; |
|
|
|
|
for (let i = 0; i < bagChildren.length; i++) { |
|
|
|
|
const idx = TextListLandHost.indexOfNode(bagChildren[i]); |
|
|
|
|
if (idx != null && idx > maxLandIndex) maxLandIndex = idx; |
|
|
|
|
} |
|
|
|
|
this._bagSession = new BattleBagSession(map, battle.LevelConfigId, battle.session, maxLandIndex + 1); |
|
|
|
|
battle.bagSession = this._bagSession; |
|
|
|
|
this._bagSession.landStateOf = (slotIndex) => this._land.stateOf(slotIndex); |
|
|
|
|
battle.charmController.bindBagView(this); |
|
|
|
|
battle.charmController.bindMergeSession(this._bagSession.merge); |
|
|
|
|
getTowerLevelDownHost().bindBagView(this); |
|
|
|
|
@ -1258,9 +1286,12 @@ export class CocosBagBoardView { |
|
|
|
|
this._bagSession.on(MERGE_EVENT_ALL, this._mergeListener); |
|
|
|
|
|
|
|
|
|
this._bindMapGridClicks(); |
|
|
|
|
this._rebuildBagFromSession(true); |
|
|
|
|
this._warmTextGridPool(); |
|
|
|
|
// 开局只给 tolockland 放字;normalland 留空;grid17 不出字
|
|
|
|
|
this._bagSession.autoFillOpenSlots(); |
|
|
|
|
this._rebuildBagFromSession(true); |
|
|
|
|
this._refreshDragEnabled(); |
|
|
|
|
this._fillMissingBagSlotGrids(); |
|
|
|
|
CocosBagBoardView._bound = this; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -1681,7 +1712,7 @@ export class CocosBagBoardView { |
|
|
|
|
if (placement.kind === 'map') { |
|
|
|
|
return this._map?.getMapGridByIndex(placement.gridIndex)?.node ?? null; |
|
|
|
|
} |
|
|
|
|
return this._bagList?.children[placement.slotIndex] ?? null; |
|
|
|
|
return this._bagSlotNodeAt(placement.slotIndex) ?? null; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** 预览妙手神偷落点(敌方字块绿/红 select) */ |
|
|
|
|
@ -2975,7 +3006,7 @@ export class CocosBagBoardView { |
|
|
|
|
|
|
|
|
|
/** 背包槽世界坐标(拉线终点) */ |
|
|
|
|
getBagSlotWorldPosition(slotIndex: number): Vec3 | null { |
|
|
|
|
const slot = this._bagList?.children[slotIndex]; |
|
|
|
|
const slot = this._bagSlotNodeAt(slotIndex); |
|
|
|
|
if (!slot?.isValid) return null; |
|
|
|
|
return slot.worldPosition.clone(); |
|
|
|
|
} |
|
|
|
|
@ -3091,6 +3122,35 @@ export class CocosBagBoardView { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* 只让新补上的空格字从砚台飞入,已有字的格子保持不动。 |
|
|
|
|
* @param slotIndices 本次写入的槽 |
|
|
|
|
* @param fromWorld 起点世界坐标 |
|
|
|
|
*/ |
|
|
|
|
playNewSlotEntrance(slotIndices: readonly number[], fromWorld?: Readonly<Vec3> | null): void { |
|
|
|
|
this._fillMissingBagSlotGrids(); |
|
|
|
|
const flying = new Map<number, TextGrid>(); |
|
|
|
|
for (let i = 0; i < slotIndices.length; i++) { |
|
|
|
|
const grid = this._slotGrids.get(slotIndices[i]); |
|
|
|
|
if (!grid?.isValid) continue; |
|
|
|
|
grid.enableDrag(false); |
|
|
|
|
flying.set(slotIndices[i], grid); |
|
|
|
|
} |
|
|
|
|
if (flying.size === 0) return; |
|
|
|
|
this._battle?.forceEndBagDragSlowMo(); |
|
|
|
|
const layout = this._bagList?.getComponent(Layout) ?? null; |
|
|
|
|
if (layout) layout.enabled = false; |
|
|
|
|
this._bagEntrancePlaying = true; |
|
|
|
|
this._bagEntranceStartHandler?.(); |
|
|
|
|
BagSlotEntranceAnimator.play( |
|
|
|
|
flying, |
|
|
|
|
this._bagList, |
|
|
|
|
this._onBagEntranceFinished, |
|
|
|
|
fromWorld, |
|
|
|
|
this._onBagEntranceSlotLanded, |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private _playBagEntrance(fromWorld?: Readonly<Vec3> | null): void { |
|
|
|
|
this._battle?.forceEndBagDragSlowMo(); |
|
|
|
|
this._fillMissingBagSlotGrids(); |
|
|
|
|
@ -3142,11 +3202,10 @@ export class CocosBagBoardView { |
|
|
|
|
private _snapBagSlotsAfterEntrance(): void { |
|
|
|
|
if (!this._bagSession || !this._bagList) return; |
|
|
|
|
const slots = this._bagSession.getBagSlots(); |
|
|
|
|
const children = this._bagList.children; |
|
|
|
|
for (let i = 0; i < slots.length; i++) { |
|
|
|
|
const record = slots[i]; |
|
|
|
|
if (!record) continue; |
|
|
|
|
const slotNode = children[i]; |
|
|
|
|
const slotNode = this._bagSlotNodeAt(i); |
|
|
|
|
let grid = this._slotGrids.get(i); |
|
|
|
|
if (!grid?.node?.isValid || (slotNode?.isValid && grid.node.parent !== slotNode)) { |
|
|
|
|
grid = this.ensureBagSlotGrid(i) ?? undefined; |
|
|
|
|
@ -3197,7 +3256,10 @@ export class CocosBagBoardView { |
|
|
|
|
const comp = node.getComponent(TextGrid) ?? node.addComponent(TextGrid); |
|
|
|
|
this._writeGridData(comp, entity); |
|
|
|
|
comp.rememberOrigin(); |
|
|
|
|
comp.enableDrag(enableDragNow && this._canDrag()); |
|
|
|
|
const dragOn = entity.location.kind === 'bag' |
|
|
|
|
? this._canEnableBagGridDrag(comp) |
|
|
|
|
: this._canDrag(); |
|
|
|
|
comp.enableDrag(enableDragNow && dragOn); |
|
|
|
|
comp.setMapClickHandler((g) => this._onTextGridClick(g)); |
|
|
|
|
this._bindDrag(comp); |
|
|
|
|
return comp; |
|
|
|
|
@ -3378,7 +3440,7 @@ export class CocosBagBoardView { |
|
|
|
|
this._showMergeTargetShake(this._activeDrag); |
|
|
|
|
if (lineDragMode) { |
|
|
|
|
if (source.kind === 'bag') { |
|
|
|
|
BagTextDragDebugLog.slotChildren(source.slotIndex, this._bagList?.children[source.slotIndex], 'beforeGhost'); |
|
|
|
|
BagTextDragDebugLog.slotChildren(source.slotIndex, this._bagSlotNodeAt(source.slotIndex), 'beforeGhost'); |
|
|
|
|
} |
|
|
|
|
this._ensureDragGhost(grid); |
|
|
|
|
BagTextDragDebugLog.snap(debugSeq, 'ghostReady', this._dragGhost, 'ghost'); |
|
|
|
|
@ -3433,7 +3495,7 @@ export class CocosBagBoardView { |
|
|
|
|
const bagSwap = fromSlot >= 0 && bagSlotIndex !== fromSlot; |
|
|
|
|
const mapToBag = fromMapIndex >= 0; |
|
|
|
|
if (bagSwap || mapToBag) { |
|
|
|
|
const bagSlotNode = this._bagList?.children[bagSlotIndex]; |
|
|
|
|
const bagSlotNode = this._bagSlotNodeAt(bagSlotIndex); |
|
|
|
|
if (bagSlotNode?.isValid) { |
|
|
|
|
this._setDragSelectTarget({ kind: 'bag', index: bagSlotIndex }); |
|
|
|
|
if (lineFrom) { |
|
|
|
|
@ -3539,6 +3601,10 @@ export class CocosBagBoardView { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const target = this._resolveDropTarget(event, drag.source); |
|
|
|
|
if (target.kind === 'bag' && this._land.isBlank(target.slotIndex)) { |
|
|
|
|
this._discardGlyphToBlank(grid, drag); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
if (target.kind === 'bag' && this._interceptLandDrop(target.slotIndex)) { |
|
|
|
|
if (drag.type3Backup) { |
|
|
|
|
this._bagSession.revertType3Pickup(drag.type3Backup.gridIndex, drag.type3Backup.tile); |
|
|
|
|
@ -3625,6 +3691,10 @@ export class CocosBagBoardView { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const bagSlotIndex = this._resolveBagSlotAt(uiPos); |
|
|
|
|
if (bagSlotIndex != null && this._land.isBlank(bagSlotIndex)) { |
|
|
|
|
this._discardGlyphToBlank(grid, drag); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
if (bagSlotIndex != null && this._interceptLandDrop(bagSlotIndex)) { |
|
|
|
|
this._lineDragCancel(grid, drag); |
|
|
|
|
return; |
|
|
|
|
@ -3721,7 +3791,7 @@ export class CocosBagBoardView { |
|
|
|
|
BagTextDragDebugLog.snap(seqMap, 'bagToMap', grid, `fromSlot=${slotIndex}→toMap=${mapIndex}`); |
|
|
|
|
} |
|
|
|
|
const targetWorld = mapGrid.node.worldPosition.clone(); |
|
|
|
|
const bagSlotNode = slotIndex >= 0 ? this._bagList?.children[slotIndex] : null; |
|
|
|
|
const bagSlotNode = slotIndex >= 0 ? this._bagSlotNodeAt(slotIndex) : null; |
|
|
|
|
const displacedGrid = this._mapGrids.get(mapIndex) ?? null; |
|
|
|
|
const swapToBag = displacedGrid?.node?.isValid |
|
|
|
|
&& bagSlotNode?.isValid |
|
|
|
|
@ -3801,8 +3871,8 @@ export class CocosBagBoardView { |
|
|
|
|
toSlot: number, |
|
|
|
|
event: EventTouch, |
|
|
|
|
): void { |
|
|
|
|
const targetBagNode = this._bagList?.children[toSlot]; |
|
|
|
|
const sourceBagNode = this._bagList?.children[fromSlot]; |
|
|
|
|
const targetBagNode = this._bagSlotNodeAt(toSlot); |
|
|
|
|
const sourceBagNode = this._bagSlotNodeAt(fromSlot); |
|
|
|
|
if (!targetBagNode?.isValid || !sourceBagNode?.isValid || !this._dragLayer?.isValid) { |
|
|
|
|
grid.restoreOrigin(); |
|
|
|
|
return; |
|
|
|
|
@ -3872,7 +3942,7 @@ export class CocosBagBoardView { |
|
|
|
|
toSlot: number, |
|
|
|
|
event: EventTouch, |
|
|
|
|
): void { |
|
|
|
|
const targetBagNode = this._bagList?.children[toSlot]; |
|
|
|
|
const targetBagNode = this._bagSlotNodeAt(toSlot); |
|
|
|
|
const sourceMapGrid = this._map?.getMapGridByIndex(fromMapIndex); |
|
|
|
|
if (!targetBagNode?.isValid || !sourceMapGrid?.node?.isValid || !this._dragLayer?.isValid) { |
|
|
|
|
this._lineDragCancel(grid, drag); |
|
|
|
|
@ -4053,11 +4123,13 @@ export class CocosBagBoardView { |
|
|
|
|
/** 背包槽互换:仅同级可合成时不交换;异级/满级都交换 */ |
|
|
|
|
private _willBagBagSwap(fromSlot: number, toSlot: number): boolean { |
|
|
|
|
if (fromSlot === toSlot || !this._bagSession) return false; |
|
|
|
|
if (this._land.stateOf(toSlot) !== TextListCellState.Open) return false; |
|
|
|
|
const slots = this._bagSession.getBagSlots(); |
|
|
|
|
const src = slots[fromSlot]; |
|
|
|
|
const dst = slots[toSlot]; |
|
|
|
|
if (!src || !dst) return false; |
|
|
|
|
if (this._willSingleMergeUpgrade(src, dst)) return false; |
|
|
|
|
if (this._willElementMerge(src, dst)) return false; |
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -4092,6 +4164,10 @@ export class CocosBagBoardView { |
|
|
|
|
|
|
|
|
|
/** 地图字块落子到已占背包槽时,是否会与背包字块交换到地图 */ |
|
|
|
|
private _willMapBagSwap(fromMapIndex: number, toSlot: number): boolean { |
|
|
|
|
if (this._land.stateOf(toSlot) !== TextListCellState.Open) return false; |
|
|
|
|
const tile = this._bagSession?.merge.map.getTile(fromMapIndex); |
|
|
|
|
const slot = this._bagSession?.getBagSlots()[toSlot]; |
|
|
|
|
if (tile && slot && this._willElementMerge(tile, slot)) return false; |
|
|
|
|
return this._willBagMapSwap(toSlot, fromMapIndex); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -4292,6 +4368,20 @@ export class CocosBagBoardView { |
|
|
|
|
return this._bagSession?.isMergeUpgradeBlocked(gridIndex) === true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* 五行同类型同等级会在背包合成(含满级拒绝),不走交换。 |
|
|
|
|
* @param src 拖来的字 |
|
|
|
|
* @param dst 目标格上的字 |
|
|
|
|
*/ |
|
|
|
|
private _willElementMerge(src: { weapon2Id: number; level?: number }, dst: { weapon2Id: number; level?: number }): boolean { |
|
|
|
|
WeaponComposeConfigProvider.instance.load(); |
|
|
|
|
const provider = WeaponComposeConfigProvider.instance; |
|
|
|
|
const typeA = Math.floor(provider.getWeapon2Type(src.weapon2Id)); |
|
|
|
|
const typeB = Math.floor(provider.getWeapon2Type(dst.weapon2Id)); |
|
|
|
|
if (typeA < 1 || typeA > 5 || typeA !== typeB) return false; |
|
|
|
|
return Math.floor(src.level ?? 1) === Math.floor(dst.level ?? 1); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** type1 同 Weapon1 同等级可合成升级 */ |
|
|
|
|
private _willSingleMergeUpgrade( |
|
|
|
|
src: { weapon2Id: number; weapon1Id?: number; level?: number }, |
|
|
|
|
@ -4331,12 +4421,97 @@ export class CocosBagBoardView { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* 待解锁格放行拖入走正常合成(BagMerged 后解锁下标+1),其余吃掉拖放。 |
|
|
|
|
* 背包或地图上的字拖到空装饰格:飞到 grid17 后消失,不留在格子上。 |
|
|
|
|
* @param grid 被拖的字 |
|
|
|
|
* @param drag 本次拖拽 |
|
|
|
|
*/ |
|
|
|
|
private _discardGlyphToBlank(grid: TextGrid, drag: NonNullable<typeof this._activeDrag>): void { |
|
|
|
|
const session = this._bagSession; |
|
|
|
|
const blank = this._blankLandNode(); |
|
|
|
|
const layer = this._dragLayer; |
|
|
|
|
if (!session || !blank?.isValid || !layer?.isValid || !grid.node?.isValid) { |
|
|
|
|
this._discardGlyphData(drag); |
|
|
|
|
if (grid.node?.isValid) this._forceRecycleTextGrid(grid); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
const from: GridDetachSource = drag.source.kind === 'bag' |
|
|
|
|
? { kind: 'bag', slotIndex: drag.source.slotIndex } |
|
|
|
|
: { kind: 'map', gridIndex: drag.mapDrag?.fromGridIndex ?? drag.source.gridIndex }; |
|
|
|
|
const home = from.kind === 'bag' |
|
|
|
|
? this._bagSlotNodeAt(from.slotIndex) |
|
|
|
|
: this._map?.getMapGridByIndex(from.gridIndex)?.node ?? null; |
|
|
|
|
const startWorld = grid.node.worldPosition.clone(); |
|
|
|
|
this._mapDropAnimInflight++; |
|
|
|
|
this._detachGridForFlight(grid, from); |
|
|
|
|
grid.node.parent = layer; |
|
|
|
|
grid.node.setWorldPosition(startWorld); |
|
|
|
|
grid.preparePlaceFlightVisual(); |
|
|
|
|
grid.enableDrag(false); |
|
|
|
|
if (!this._discardGlyphData(drag)) { |
|
|
|
|
if (this._mapDropAnimInflight > 0) this._mapDropAnimInflight--; |
|
|
|
|
this._grids.removeFromFlight(grid); |
|
|
|
|
if (home?.isValid) { |
|
|
|
|
grid.node.parent = home; |
|
|
|
|
grid.node.setPosition(0, 0, 0); |
|
|
|
|
} |
|
|
|
|
this._lineDragCancel(grid, drag); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
const target = blank.worldPosition.clone(); |
|
|
|
|
BagToMapPlaceAnimator.play(grid, target, layer, () => { |
|
|
|
|
this._finishDiscardFlight(grid); |
|
|
|
|
}, { showTrail: DeviceProfile.enableBoardPlaceTrail }); |
|
|
|
|
const seq = drag.debugSeq ?? this._textDragDebugSeq; |
|
|
|
|
if (seq) BagTextDragDebugLog.end(seq, grid, 'discardBlank'); |
|
|
|
|
this._textDragDebugSeq = 0; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* 删掉拖来的背包槽或地图字,不退回字池。 |
|
|
|
|
* @param drag 本次拖拽 |
|
|
|
|
*/ |
|
|
|
|
private _discardGlyphData(drag: NonNullable<typeof this._activeDrag>): boolean { |
|
|
|
|
const session = this._bagSession; |
|
|
|
|
if (!session) return false; |
|
|
|
|
if (drag.source.kind === 'bag') return session.discardBagSlot(drag.source.slotIndex); |
|
|
|
|
const mapIndex = drag.mapDrag?.fromGridIndex ?? drag.source.gridIndex; |
|
|
|
|
return session.discardMapGlyph(mapIndex); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* 飞到空装饰格后回收字块,并补上推迟的盘面刷新。 |
|
|
|
|
* @param grid 飞行中的字 |
|
|
|
|
*/ |
|
|
|
|
private _finishDiscardFlight(grid: TextGrid): void { |
|
|
|
|
this._grids.removeFromFlight(grid); |
|
|
|
|
if (grid?.node?.isValid) this._forceRecycleTextGrid(grid); |
|
|
|
|
if (this._mapDropAnimInflight > 0) this._mapDropAnimInflight--; |
|
|
|
|
this._flushAllPendingRebuilds(); |
|
|
|
|
this._refreshDragEnabled(); |
|
|
|
|
this._refreshCanUpgradeHints(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** 空装饰格节点(grid17) */ |
|
|
|
|
private _blankLandNode(): Node | null { |
|
|
|
|
const list = this._bagList; |
|
|
|
|
if (!list?.isValid) return null; |
|
|
|
|
const children = list.children; |
|
|
|
|
for (let i = 0; i < children.length; i++) { |
|
|
|
|
const index = TextListLandHost.indexOfNode(children[i]); |
|
|
|
|
if (index != null && this._land.isBlank(index)) return children[i]; |
|
|
|
|
} |
|
|
|
|
return null; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* 未解锁格吃掉拖放。待开启格放行,走同类型同等级合成。 |
|
|
|
|
* 背包槽范围外的格子不落子,避免写入无效槽。 |
|
|
|
|
* @param slotIndex 命中的格子下标 |
|
|
|
|
*/ |
|
|
|
|
private _interceptLandDrop(slotIndex: number): boolean { |
|
|
|
|
if (this._land.stateOf(slotIndex) === TextListCellState.OpenNoDrag) return false; |
|
|
|
|
const state = this._land.stateOf(slotIndex); |
|
|
|
|
if (state === TextListCellState.Blank || state === TextListCellState.Locked) return true; |
|
|
|
|
if (state === TextListCellState.OpenNoDrag) return false; |
|
|
|
|
const slotCount = this._bagSession?.merge.bag.slotCount ?? 0; |
|
|
|
|
if (slotIndex >= 0 && slotIndex < slotCount) return false; |
|
|
|
|
return this._land.stateOf(slotIndex) !== TextListCellState.Open; |
|
|
|
|
@ -4388,11 +4563,8 @@ export class CocosBagBoardView { |
|
|
|
|
private _resolveBagSlotIndexForTextGrid(grid: TextGrid): number | null { |
|
|
|
|
const parent = grid.node?.parent; |
|
|
|
|
if (!parent?.isValid || !this._bagList?.isValid) return null; |
|
|
|
|
const children = this._bagList.children; |
|
|
|
|
for (let i = 0; i < children.length; i++) { |
|
|
|
|
if (children[i] === parent) return i; |
|
|
|
|
} |
|
|
|
|
return null; |
|
|
|
|
// 槽位下标 = 格子下标(gridN → N),temp 与其他节点返回 null
|
|
|
|
|
return TextListLandHost.indexOfNode(parent); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** 拖放源以节点实际挂接位置为准,location 仅作兜底 */ |
|
|
|
|
@ -4549,9 +4721,13 @@ export class CocosBagBoardView { |
|
|
|
|
this._scheduleComposeFxFlush(); |
|
|
|
|
break; |
|
|
|
|
case MergeEventType.BagMerged: |
|
|
|
|
// 待解锁格吃到的合成(相同类型相同等级)解锁下标 +1 的格子
|
|
|
|
|
// 待开启格吃到同类型同等级:自身变开启,四周未解锁格变待开启并各生成一个字
|
|
|
|
|
if (this._land.stateOf(event.slotIndex) === TextListCellState.OpenNoDrag) { |
|
|
|
|
this._land.unlockNext(event.slotIndex); |
|
|
|
|
const born = this._land.promoteAround(event.slotIndex); |
|
|
|
|
for (let i = 0; i < born.length; i++) { |
|
|
|
|
this._bagSession?.fillGateUnlockSlot(born[i]); |
|
|
|
|
} |
|
|
|
|
this._fillMissingBagSlotGrids(); |
|
|
|
|
} |
|
|
|
|
this._reportMergeUpgraded(event.weapon1Id, event.level); |
|
|
|
|
if (this._dropSyncPhase === 'awaitingCommit') { |
|
|
|
|
@ -4819,13 +4995,12 @@ export class CocosBagBoardView { |
|
|
|
|
): void { |
|
|
|
|
if (!this._bagSession || !this._bagList) return; |
|
|
|
|
const slots = this._bagSession.getBagSlots(); |
|
|
|
|
const children = this._bagList.children; |
|
|
|
|
const bagNeedSpawn: number[] = []; |
|
|
|
|
const mutableFlight = [...flightGrids]; |
|
|
|
|
|
|
|
|
|
for (let i = 0; i < slots.length; i++) { |
|
|
|
|
const record = slots[i]; |
|
|
|
|
const slotNode = children[i]; |
|
|
|
|
const slotNode = this._bagSlotNodeAt(i); |
|
|
|
|
if (!record || !slotNode) { |
|
|
|
|
if (record && !slotNode) { |
|
|
|
|
console.log('[CocosBagBoardView] 背包槽节点缺失', 'slot=', i, 'weapon2Id=', record.weapon2Id); |
|
|
|
|
@ -4905,7 +5080,7 @@ export class CocosBagBoardView { |
|
|
|
|
for (let n = 0; n < bagNeedSpawn.length; n++) { |
|
|
|
|
const i = bagNeedSpawn[n]; |
|
|
|
|
const record = slots[i]; |
|
|
|
|
const slotNode = children[i]; |
|
|
|
|
const slotNode = this._bagSlotNodeAt(i); |
|
|
|
|
if (!record || !slotNode?.isValid) continue; |
|
|
|
|
const entity = buildTextGridEntityData( |
|
|
|
|
{ kind: 'bag', slotIndex: i }, |
|
|
|
|
@ -5202,7 +5377,11 @@ export class CocosBagBoardView { |
|
|
|
|
this._writeGridData(existing, entity); |
|
|
|
|
existing.rememberOrigin(); |
|
|
|
|
existing.node.name = 'textGrid'; |
|
|
|
|
existing.enableDrag(this._canDrag() && !BagSlotEntranceAnimator.isPlaying(existing)); |
|
|
|
|
existing.enableDrag( |
|
|
|
|
entity.location.kind === 'bag' |
|
|
|
|
? this._canEnableBagGridDrag(existing) |
|
|
|
|
: this._canDrag() && !BagSlotEntranceAnimator.isPlaying(existing), |
|
|
|
|
); |
|
|
|
|
this._ensureMapClickHandler(existing); |
|
|
|
|
return existing; |
|
|
|
|
} |
|
|
|
|
@ -5448,7 +5627,7 @@ export class CocosBagBoardView { |
|
|
|
|
private _applyBagUpgradeInPlace(slotIndex: number, newLevel: number): boolean { |
|
|
|
|
if (!this._bagSession || !this._bagList) return false; |
|
|
|
|
if (!this._bagSession.getBagSlots()[slotIndex]) return false; |
|
|
|
|
const slotNode = this._bagList.children[slotIndex]; |
|
|
|
|
const slotNode = this._bagSlotNodeAt(slotIndex); |
|
|
|
|
if (!slotNode?.isValid) return false; |
|
|
|
|
|
|
|
|
|
const grid = this._resolveBagTextGridForUpgrade(slotIndex, slotNode); |
|
|
|
|
@ -5490,7 +5669,7 @@ export class CocosBagBoardView { |
|
|
|
|
private _applyBagGlyphRewriteInPlace(slotIndex: number): boolean { |
|
|
|
|
if (!this._bagSession || !this._bagList) return false; |
|
|
|
|
if (!this._bagSession.getBagSlots()[slotIndex]) return false; |
|
|
|
|
const slotNode = this._bagList.children[slotIndex]; |
|
|
|
|
const slotNode = this._bagSlotNodeAt(slotIndex); |
|
|
|
|
if (!slotNode?.isValid) return false; |
|
|
|
|
|
|
|
|
|
const grid = this._resolveBagTextGridForUpgrade(slotIndex, slotNode); |
|
|
|
|
@ -5817,9 +5996,12 @@ export class CocosBagBoardView { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** 背包字块:入场未落地前不可拖 */ |
|
|
|
|
/** 背包字块:入场未落地、待解锁格、空装饰格不可拖 */ |
|
|
|
|
private _canEnableBagGridDrag(grid: TextGrid): boolean { |
|
|
|
|
return this._canDrag() && !BagSlotEntranceAnimator.isPlaying(grid); |
|
|
|
|
if (!this._canDrag() || BagSlotEntranceAnimator.isPlaying(grid)) return false; |
|
|
|
|
const loc = grid.location; |
|
|
|
|
if (loc?.kind === 'bag' && this._land.isNoDrag(loc.slotIndex)) return false; |
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** 入场未落地的字块不显示可合成箭头 */ |
|
|
|
|
@ -5834,7 +6016,7 @@ export class CocosBagBoardView { |
|
|
|
|
const enabled = this._canDrag(); |
|
|
|
|
for (const g of this._slotGrids.values()) { |
|
|
|
|
if (!g?.isValid) continue; |
|
|
|
|
g.enableDrag(enabled && !BagSlotEntranceAnimator.isPlaying(g)); |
|
|
|
|
g.enableDrag(this._canEnableBagGridDrag(g)); |
|
|
|
|
this._ensureMapClickHandler(g); |
|
|
|
|
} |
|
|
|
|
for (const g of this._mapGrids.values()) { |
|
|
|
|
@ -6017,7 +6199,7 @@ export class CocosBagBoardView { |
|
|
|
|
/** ghost 创建后:确认源字仍挂在背包槽,被偷走则立刻挂回并补齐 */ |
|
|
|
|
private _assertBagSourceStillInSlot(grid: TextGrid, source: DragSource): void { |
|
|
|
|
if (source.kind !== 'bag') return; |
|
|
|
|
const slotNode = this._bagList?.children[source.slotIndex]; |
|
|
|
|
const slotNode = this._bagSlotNodeAt(source.slotIndex); |
|
|
|
|
BagTextDragDebugLog.slotChildren(source.slotIndex, slotNode, 'afterGhost'); |
|
|
|
|
if (!slotNode?.isValid) return; |
|
|
|
|
if (grid.node?.isValid && grid.node.parent === slotNode) { |
|
|
|
|
@ -6095,7 +6277,7 @@ export class CocosBagBoardView { |
|
|
|
|
const anchor = this._lineDragAnchorWorld(grid, drag); |
|
|
|
|
if (anchor) return anchor.clone(); |
|
|
|
|
if (drag.source.kind === 'bag') { |
|
|
|
|
const slotNode = this._bagList?.children[drag.source.slotIndex]; |
|
|
|
|
const slotNode = this._bagSlotNodeAt(drag.source.slotIndex); |
|
|
|
|
if (slotNode?.isValid) return slotNode.worldPosition.clone(); |
|
|
|
|
} else { |
|
|
|
|
const mapIndex = drag.mapDrag?.fromGridIndex ?? drag.source.gridIndex; |
|
|
|
|
@ -6296,7 +6478,7 @@ export class CocosBagBoardView { |
|
|
|
|
/** 起飞后清原格残留 TextGrid(飞走的节点已挂到 dragLayer) */ |
|
|
|
|
private _purgeVacatedCell(from: GridDetachSource): void { |
|
|
|
|
const parent = from.kind === 'bag' |
|
|
|
|
? this._bagList?.children[from.slotIndex] |
|
|
|
|
? this._bagSlotNodeAt(from.slotIndex) |
|
|
|
|
: this._map?.getMapGridByIndex(from.gridIndex)?.node; |
|
|
|
|
if (!parent?.isValid) return; |
|
|
|
|
for (const child of [...parent.children]) { |
|
|
|
|
@ -6798,7 +6980,7 @@ export class CocosBagBoardView { |
|
|
|
|
return mg?.node?.isValid ? mg.node.worldPosition.clone() : null; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const slotNode = this._bagList?.children[target.index]; |
|
|
|
|
const slotNode = this._bagSlotNodeAt(target.index); |
|
|
|
|
const centerNode = grid.node?.isValid ? grid.node : slotNode; |
|
|
|
|
return centerNode?.isValid ? centerNode.worldPosition.clone() : null; |
|
|
|
|
} |
|
|
|
|
@ -6929,7 +7111,7 @@ export class CocosBagBoardView { |
|
|
|
|
|
|
|
|
|
/** 背包槽 select:有 textGrid 时高亮字块,空槽时高亮槽位上的 select 节点 */ |
|
|
|
|
private _setBagSlotSelect(slotIndex: number, selected: boolean): void { |
|
|
|
|
const slotNode = this._bagList?.children[slotIndex]; |
|
|
|
|
const slotNode = this._bagSlotNodeAt(slotIndex); |
|
|
|
|
const slotSelect = slotNode?.getChildByName('select') ?? null; |
|
|
|
|
const tg = this._slotGrids.get(slotIndex); |
|
|
|
|
|
|
|
|
|
@ -7261,7 +7443,7 @@ export class CocosBagBoardView { |
|
|
|
|
|
|
|
|
|
if (claimed.target === 'bag') { |
|
|
|
|
const slotIndex = claimed.slotIndex; |
|
|
|
|
const slotNode = this._bagList?.children[slotIndex]; |
|
|
|
|
const slotNode = this._bagSlotNodeAt(slotIndex); |
|
|
|
|
if (!slotNode?.isValid) { |
|
|
|
|
settle(false); |
|
|
|
|
return false; |
|
|
|
|
@ -7553,7 +7735,7 @@ export class CocosBagBoardView { |
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const slotNode = this._bagList?.children[slotIndex]; |
|
|
|
|
const slotNode = this._bagSlotNodeAt(slotIndex); |
|
|
|
|
if (!slotNode?.isValid) { |
|
|
|
|
onComplete(false); |
|
|
|
|
return false; |
|
|
|
|
@ -7668,7 +7850,7 @@ export class CocosBagBoardView { |
|
|
|
|
grid.setDragFollow(true); |
|
|
|
|
|
|
|
|
|
if (source.kind === 'bag') { |
|
|
|
|
const slotNode = this._bagList?.children[source.slotIndex]; |
|
|
|
|
const slotNode = this._bagSlotNodeAt(source.slotIndex); |
|
|
|
|
if (!slotNode?.isValid) return false; |
|
|
|
|
if (grid.node.parent !== slotNode) { |
|
|
|
|
grid.node.parent = slotNode; |
|
|
|
|
@ -7709,7 +7891,7 @@ export class CocosBagBoardView { |
|
|
|
|
|
|
|
|
|
const target: DropTarget = { kind: 'map', gridIndex: mapIndex }; |
|
|
|
|
const targetWorld = mapGrid.node.worldPosition.clone(); |
|
|
|
|
const bagSlotNode = this._bagList?.children[slotIndex]; |
|
|
|
|
const bagSlotNode = this._bagSlotNodeAt(slotIndex); |
|
|
|
|
const displacedGrid = this._mapGrids.get(mapIndex) ?? null; |
|
|
|
|
const swapToBag = displacedGrid?.node?.isValid |
|
|
|
|
&& bagSlotNode?.isValid |
|
|
|
|
@ -7785,8 +7967,8 @@ export class CocosBagBoardView { |
|
|
|
|
toSlot: number, |
|
|
|
|
finish: (ok: boolean) => void, |
|
|
|
|
): void { |
|
|
|
|
const targetBagNode = this._bagList?.children[toSlot]; |
|
|
|
|
const sourceBagNode = this._bagList?.children[fromSlot]; |
|
|
|
|
const targetBagNode = this._bagSlotNodeAt(toSlot); |
|
|
|
|
const sourceBagNode = this._bagSlotNodeAt(fromSlot); |
|
|
|
|
if (!targetBagNode?.isValid || !sourceBagNode?.isValid || !this._bagSession) { |
|
|
|
|
finish(false); |
|
|
|
|
return; |
|
|
|
|
|