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/ui/PanelGemstone/UIPanelGemstone.ts

556 lines
20 KiB

//*********************
// create by 流云
// time: Wed Oct 07 2026
// desc: 宝石镶嵌 / 页签仓库 / 一键合成
//*********************
import { _decorator, EventMouse, EventTouch, Input, input, instantiate, Node, sp, Sprite, UIOpacity, UITransform, Vec2, Vec3 } from 'cc';
import { SpriteLoad } from 'db://assets/mx/components/SpriteLoad';
import { ensureSpineSkeletonDataRuntime } from 'db://assets/mx/module/lang/spineRuntimePatch';
import { GemGrid } from '../../com/GemGrid';
import { FrameFlowHost } from '../../game/FrameFlowHost';
import { GBundle, UIID } from '../../game/ConfigRes';
import { GEM_SLOT_COUNT, IGemBagEntry } from '../../game/GameDataEnums';
import { Auto_PanelGemstone } from './Auto_PanelGemstone';
const { ccclass } = _decorator;
const LOG = '[PanelGemstone]';
/** 按下后移动超过此值开始拖 */
const DRAG_THRESHOLD = 18;
const GHOST_OPACITY = 180;
const ELEMENT_COUNT = 5;
const SLOT_NAMES = ['g1', 'g2', 'g3', 'g4'];
const ELEMENT_SKINS = ['', '金', '木', '水', '火', '土'];
/** 仓库每帧实例化数量 */
const BAG_PER_FRAME = 6;
/** 默认页签:通用 Weapon.type=1 */
const DEFAULT_FILTER = 1;
/**
* 宝石页:五行镶嵌 + 页签仓库(无滚动)+ 拖拽镶嵌/卸下 + 一键合成。
* 页签 btn0~btn5 → type 1~6(通用/金/木/水/火/土),默认通用。
*/
@ccclass('UIPanelGemstone')
export class UIPanelGemstone extends Auto_PanelGemstone {
/** 五行格子模板 */
private _itemTpl: Node | null = null;
/** 仓库格子模板 */
private _bagTpl: Node | null = null;
/** 已创建的五行格 */
private _elementItems: Node[] = [];
/** 仓库动态格 */
private _bagItems: Node[] = [];
/** 当前筛选:1 通用;2~6 金木水火土 */
private _filterType = DEFAULT_FILTER;
/** 仓库数据 */
private _bagList: IGemBagEntry[] = [];
/** 仓库分帧 */
private _flow: FrameFlowHost | null = null;
/** 仓库填充代数 */
private _bagToken = 0;
/** 已分帧填充下标 */
private _bagFillIndex = 0;
private _pressing = false;
private _dragging = false;
private _dragStart = new Vec2();
private _hitPos = new Vec2();
private _ghostPos = new Vec3();
private _ghost: Node | null = null;
private _dragListening = false;
/** 拖中的宝石 id */
private _dragGemId = 0;
/** 从槽拖出:五行 1~5;0=仓库 */
private _dragFromElement = 0;
/** 从槽拖出:槽下标 0~3;-1=仓库 */
private _dragFromSlot = -1;
onLoad(): void {
super.onLoad();
this._itemTpl = this.ui_list?.getChildByName('itemTemplate') ?? null;
if (this._itemTpl) this._itemTpl.active = false;
this._bagTpl = this.ui_content?.getChildByName('bagItemTemplate') ?? null;
if (this._bagTpl) this._bagTpl.active = false;
this._bindDrag(true);
}
onEnable(): void {
super.onEnable();
this._bindDrag(true);
}
onDisable(): void {
this._bindDrag(false);
this._endDrag();
this._stopBagFill();
super.onDisable();
}
onInit(): void {
this._filterType = DEFAULT_FILTER;
this._selectTag(this.btn0);
void this._refresh();
}
click_btnClose(): void {
void this.close(2);
}
click_btn0(): void { this._onTag(1, this.btn0); }
click_btn1(): void { this._onTag(2, this.btn1); }
click_btn2(): void { this._onTag(3, this.btn2); }
click_btn3(): void { this._onTag(4, this.btn3); }
click_btn4(): void { this._onTag(5, this.btn4); }
click_btn5(): void { this._onTag(6, this.btn5); }
click_btnFuse(): void {
const gains = gg.gem.fuseAll();
this._rebuildBag();
this._refreshAllSlots();
if (!gains.length) {
gg.ui.showToast('没有可合成的宝石');
return;
}
void gg.ui.openPanel(UIID.PopGetGem, { data: gains });
}
/** 页签切换 */
private _onTag(filterType: number, btn: Node): void {
if (this._filterType === filterType) return;
this._filterType = filterType;
this._selectTag(btn);
this._rebuildBag();
}
/** 标签底移到当前按钮 */
private _selectTag(btn: Node | null): void {
if (!this.ui_tagBtnBg?.isValid || !btn?.isValid) return;
this.ui_tagBtnBg.setWorldPosition(btn.worldPosition);
}
/** 全量刷新 */
private async _refresh(): Promise<void> {
await gg.gem.ensureTable();
if (!this.isValid) return;
this._buildElements();
this._rebuildBag();
}
/** 实例化五行 */
private _buildElements(): void {
if (!this.ui_list?.isValid || !this._itemTpl?.isValid) {
console.warn(LOG, 'ui_list/itemTemplate 缺失');
return;
}
for (let i = 0; i < this._elementItems.length; i++) {
const n = this._elementItems[i];
if (n?.isValid) n.destroy();
}
this._elementItems.length = 0;
for (let e = 1; e <= ELEMENT_COUNT; e++) {
const node = instantiate(this._itemTpl);
node.parent = this.ui_list;
node.active = true;
node.name = `element_${e}`;
(node as any)._element = e;
this._applyNumSkin(node, e);
this._refreshElementSlots(node, e);
this._elementItems.push(node);
}
}
/** numSkin 切五行皮肤 */
private _applyNumSkin(item: Node, element: number): void {
const skinNode = item.getChildByName('numSkin');
const sk = skinNode?.getComponent(sp.Skeleton);
if (!sk) {
console.warn(LOG, 'numSkin Skeleton 缺失', element);
return;
}
const skin = ELEMENT_SKINS[element] || '';
if (!skin) {
console.warn(LOG, '五行皮肤名无效', element);
return;
}
if (sk.skeletonData) ensureSpineSkeletonDataRuntime(sk.skeletonData);
try {
sk.setSkin(skin);
sk.setSlotsToSetupPose();
if (sk.findAnimation('待机')) sk.setAnimation(0, '待机', true);
} catch (err) {
console.warn(LOG, 'setSkin 失败', skin, err);
}
}
/** 刷新某一行五行的 4 槽 */
private _refreshElementSlots(item: Node, element: number): void {
const slots = gg.gem.getElementSlots(element);
for (let s = 0; s < GEM_SLOT_COUNT; s++) {
const slotNode = item.getChildByName(SLOT_NAMES[s]);
if (!slotNode?.isValid) {
console.warn(LOG, '镶嵌位缺失', SLOT_NAMES[s], element);
continue;
}
(slotNode as any)._element = element;
(slotNode as any)._slot = s;
this._paintSlot(slotNode, slots[s] || 0);
}
}
/** 刷新全部镶嵌 */
private _refreshAllSlots(): void {
for (let i = 0; i < this._elementItems.length; i++) {
const item = this._elementItems[i];
const e = Number((item as any)?._element) || (i + 1);
this._refreshElementSlots(item, e);
}
}
/** 画单个镶嵌位 */
private _paintSlot(slotNode: Node, gemId: number): void {
const bg = slotNode.getChildByName('bg');
const icon = slotNode.getChildByName('icon');
const conf = gemId > 0 ? gg.gem.getGem(gemId) : null;
const quality = conf ? Math.floor(Number(conf.quality) || 0) : 0;
this._setWeaponSprite(bg, `weapon/宝石框小${Math.max(0, Math.min(5, quality))}`);
const iconName = conf ? String(conf.icon || '').trim() : '';
if (conf && !iconName) console.warn(LOG, 'icon 缺失', gemId);
this._setWeaponSprite(icon, iconName ? `weapon/${iconName}` : '');
if (icon?.isValid) icon.active = !!iconName;
}
/**
* 重建当前页签仓库(打开 / 切页签 / 合成)。
* 镶嵌成败勿调用。
*/
private _rebuildBag(): void {
if (!this.ui_content?.isValid || !this._bagTpl?.isValid) {
console.warn(LOG, 'ui_content/bagItemTemplate 缺失');
return;
}
this._stopBagFill();
for (let i = 0; i < this._bagItems.length; i++) {
const n = this._bagItems[i];
if (n?.isValid) n.destroy();
}
this._bagItems.length = 0;
this._bagList = gg.gem.getBagList(this._filterType);
this._bagFillIndex = 0;
this._bagToken++;
this._pumpBagFill(this._bagToken);
}
/** 停掉仓库分帧 */
private _stopBagFill(): void {
this._bagToken++;
this._flow?.dispose();
this._flow = null;
}
/** 分帧填充仓库格 */
private _pumpBagFill(token: number): void {
if (!this.isValid || token !== this._bagToken) return;
if (!this.ui_content?.isValid || !this._bagTpl?.isValid) return;
const end = Math.min(this._bagFillIndex + BAG_PER_FRAME, this._bagList.length);
for (let i = this._bagFillIndex; i < end; i++) {
this._bagItems.push(this._spawnBagItem(this._bagList[i]));
}
this._bagFillIndex = end;
if (this._bagFillIndex >= this._bagList.length) return;
if (!this._flow) this._flow = new FrameFlowHost();
this._flow.waitFrames(1, () => this._pumpBagFill(token));
}
/** 创建一格仓库项 */
private _spawnBagItem(entry: IGemBagEntry): Node {
const node = instantiate(this._bagTpl!);
node.parent = this.ui_content;
node.active = true;
const grid = node.getComponent(GemGrid) || node.addComponent(GemGrid);
grid.setData(gg.gem.getGem(entry.gemId), entry.count);
(node as any)._gemId = entry.gemId;
return node;
}
/**
* 镶嵌/卸下后局部同步仓库数量。
* @param gemIds 涉及的宝石 id
*/
private _patchBagGems(gemIds: readonly number[]): void {
for (let i = 0; i < gemIds.length; i++) {
const gemId = Math.floor(Number(gemIds[i]) || 0);
if (gemId <= 0) continue;
const free = gg.gem.getFreeCount(gemId);
const conf = gg.gem.getGem(gemId);
const matchFilter = this._matchFilter(conf);
const idx = this._findBagItemIndex(gemId);
if (free <= 0 || !matchFilter) {
if (idx >= 0) this._removeBagItemAt(idx);
continue;
}
if (idx >= 0) {
const node = this._bagItems[idx];
node.getComponent(GemGrid)?.setData(conf, free);
this._bagList[idx] = { gemId, count: free };
continue;
}
const entry: IGemBagEntry = { gemId, count: free };
this._bagList.push(entry);
this._bagItems.push(this._spawnBagItem(entry));
}
}
/** 当前页签是否应展示该宝石 */
private _matchFilter(conf: ITableWeapon | null): boolean {
if (!conf) return false;
return Math.floor(Number(conf.type) || 0) === this._filterType;
}
/** 查找仓库格下标 */
private _findBagItemIndex(gemId: number): number {
for (let i = 0; i < this._bagItems.length; i++) {
if (Number((this._bagItems[i] as any)._gemId) === gemId) return i;
}
return -1;
}
/** 移除一格仓库 */
private _removeBagItemAt(idx: number): void {
const node = this._bagItems[idx];
if (node?.isValid) node.destroy();
this._bagItems.splice(idx, 1);
this._bagList.splice(idx, 1);
}
/** icons/weapon */
private _setWeaponSprite(node: Node | null, path: string): void {
if (!node?.isValid) return;
if (!path) {
node.getComponent(SpriteLoad)?.clear();
const spComp = node.getComponent(Sprite);
if (spComp) spComp.spriteFrame = null;
return;
}
if (!node.getComponent(Sprite)) node.addComponent(Sprite);
node.getOrAddComponent(SpriteLoad).setSprite(path, GBundle.Items);
}
// ---------- 拖拽镶嵌 ----------
private _bindDrag(on: boolean): void {
if (this._dragListening === on) return;
this._dragListening = on;
if (on) {
this.node.on(Node.EventType.TOUCH_START, this._onTouchStart, this, true);
this.node.on(Node.EventType.TOUCH_MOVE, this._onTouchMove, this, true);
this.node.on(Node.EventType.TOUCH_END, this._onTouchEnd, this, true);
this.node.on(Node.EventType.TOUCH_CANCEL, this._onTouchEnd, this, true);
input.on(Input.EventType.MOUSE_DOWN, this._onMouseDown, this);
input.on(Input.EventType.MOUSE_MOVE, this._onMouseMove, this);
input.on(Input.EventType.MOUSE_UP, this._onMouseUp, this);
} else {
this.node.off(Node.EventType.TOUCH_START, this._onTouchStart, this, true);
this.node.off(Node.EventType.TOUCH_MOVE, this._onTouchMove, this, true);
this.node.off(Node.EventType.TOUCH_END, this._onTouchEnd, this, true);
this.node.off(Node.EventType.TOUCH_CANCEL, this._onTouchEnd, this, true);
input.off(Input.EventType.MOUSE_DOWN, this._onMouseDown, this);
input.off(Input.EventType.MOUSE_MOVE, this._onMouseMove, this);
input.off(Input.EventType.MOUSE_UP, this._onMouseUp, this);
}
}
private _onTouchStart(e: EventTouch): void { this._pointerStart(e.getUILocation()); }
private _onTouchMove(e: EventTouch): void { this._pointerMove(e.getUILocation()); }
private _onTouchEnd(e: EventTouch): void { this._pointerEnd(e.getUILocation()); }
private _onMouseDown(e: EventMouse): void {
if (e.getButton() !== EventMouse.BUTTON_LEFT) return;
this._pointerStart(e.getUILocation());
}
private _onMouseMove(e: EventMouse): void { this._pointerMove(e.getUILocation()); }
private _onMouseUp(e: EventMouse): void { this._pointerEnd(e.getUILocation()); }
private _pointerStart(loc: Vec2): void {
if (this._pressing || this._dragging) return;
const bag = this._hitBag(loc);
if (bag) {
const gemId = Number((bag as any)._gemId) || 0;
if (gemId <= 0) return;
this._resetDrag();
this._pressing = true;
this._dragStart.set(loc);
this._dragGemId = gemId;
this._dragFromElement = 0;
this._dragFromSlot = -1;
return;
}
const slot = this._hitSlot(loc);
if (!slot) return;
const element = Number((slot as any)._element) || 0;
const si = Number((slot as any)._slot);
if (element <= 0 || si < 0) return;
const gemId = gg.gem.getSlotGem(element, si);
if (gemId <= 0) return;
this._resetDrag();
this._pressing = true;
this._dragStart.set(loc);
this._dragGemId = gemId;
this._dragFromElement = element;
this._dragFromSlot = si;
}
private _pointerMove(loc: Vec2): void {
if (this._dragging) {
this._moveGhost(loc);
return;
}
if (!this._pressing || this._dragGemId <= 0) return;
const dx = loc.x - this._dragStart.x;
const dy = loc.y - this._dragStart.y;
if (dx * dx + dy * dy < DRAG_THRESHOLD * DRAG_THRESHOLD) return;
this._beginDrag(loc);
}
private _pointerEnd(loc: Vec2): void {
if (!this._pressing && !this._dragging) return;
if (this._dragging) {
this._finishDrag(loc);
return;
}
// 镶嵌槽轻点看信息;仓库格由 GemGrid 自行处理
const gemId = this._dragGemId;
const fromSlot = this._dragFromElement > 0 && this._dragFromSlot >= 0;
this._endDrag();
if (fromSlot && gemId > 0) GemGrid.openInfo(gemId);
}
private _beginDrag(loc: Vec2): void {
this._dragging = true;
this._spawnGhost(loc);
this._moveGhost(loc);
}
private _finishDrag(loc: Vec2): void {
const gemId = this._dragGemId;
const fromE = this._dragFromElement;
const fromS = this._dragFromSlot;
const toSlot = this._hitSlot(loc);
const toBag = this._hitBagArea(loc);
this._endDrag();
if (gemId <= 0) return;
if (toSlot) {
const te = Number((toSlot as any)._element) || 0;
const ts = Number((toSlot as any)._slot);
if (te <= 0 || ts < 0) return;
if (fromE > 0 && fromS >= 0) {
if (fromE === te && fromS === ts) return;
if (!gg.gem.swapSlots(fromE, fromS, te, ts)) {
gg.ui.showToast('无法镶嵌到该位置');
return;
}
this._refreshAllSlots();
return;
}
const before = gg.gem.getSlotGem(te, ts);
if (!gg.gem.wearFromBag(te, ts, gemId)) {
gg.ui.showToast('无法镶嵌到该位置');
return;
}
this._refreshAllSlots();
const patch = [gemId];
if (before > 0 && before !== gemId) patch.push(before);
this._patchBagGems(patch);
return;
}
if (fromE > 0 && fromS >= 0 && toBag) {
const worn = gg.gem.getSlotGem(fromE, fromS);
if (worn <= 0) return;
if (!gg.gem.unequip(fromE, fromS)) return;
this._refreshAllSlots();
this._patchBagGems([worn]);
}
}
private _spawnGhost(loc: Vec2): void {
this._clearGhost();
const tpl = this._bagTpl;
if (!tpl?.isValid) return;
const conf = gg.gem.getGem(this._dragGemId);
if (!conf) return;
const ghost = instantiate(tpl);
ghost.parent = this.node;
ghost.active = true;
const grid = ghost.getComponent(GemGrid) || ghost.addComponent(GemGrid);
grid.canClick = false;
grid.setData(conf, -1);
const op = ghost.getComponent(UIOpacity) || ghost.addComponent(UIOpacity);
op.opacity = GHOST_OPACITY;
const ut = ghost.getComponent(UITransform);
if (ut) ut.hitTest = () => false;
this._ghost = ghost;
this._moveGhost(loc);
}
private _moveGhost(loc: Vec2): void {
if (this._ghost?.isValid) {
this._ghost.setWorldPosition(this._ghostPos.set(loc.x, loc.y, 0));
}
}
private _clearGhost(): void {
if (this._ghost?.isValid) this._ghost.destroy();
this._ghost = null;
}
private _endDrag(): void {
this._clearGhost();
this._resetDrag();
}
private _resetDrag(): void {
this._pressing = false;
this._dragging = false;
this._dragGemId = 0;
this._dragFromElement = 0;
this._dragFromSlot = -1;
}
private _hitBag(loc: Vec2): Node | null {
if (!this.ui_content?.isValid) return null;
this._hitPos.set(loc.x, loc.y);
for (let i = this._bagItems.length - 1; i >= 0; i--) {
const child = this._bagItems[i];
if (!child?.activeInHierarchy) continue;
const ut = child.getComponent(UITransform);
if (ut?.getBoundingBoxToWorld().contains(this._hitPos)) return child;
}
return null;
}
private _hitSlot(loc: Vec2): Node | null {
this._hitPos.set(loc.x, loc.y);
for (let i = 0; i < this._elementItems.length; i++) {
const item = this._elementItems[i];
if (!item?.activeInHierarchy) continue;
for (let s = 0; s < SLOT_NAMES.length; s++) {
const slot = item.getChildByName(SLOT_NAMES[s]);
if (!slot?.activeInHierarchy) continue;
const ut = slot.getComponent(UITransform);
if (ut?.getBoundingBoxToWorld().contains(this._hitPos)) return slot;
}
}
return null;
}
/** 是否落在仓库区(卸下) */
private _hitBagArea(loc: Vec2): boolean {
const root = this.ui_content;
if (!root?.isValid) return false;
this._hitPos.set(loc.x, loc.y);
const ut = root.getComponent(UITransform);
return !!ut?.getBoundingBoxToWorld().contains(this._hitPos);
}
}