//********************* // create by 流云 // time: Wed Oct 07 2026 // desc: 宝石镶嵌 / 仓库 / 一键合成 //********************* import { _decorator, EventMouse, EventTouch, Input, input, instantiate, Node, ScrollView, 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 { 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 = ['', '金', '木', '水', '火', '土']; /** * 宝石页:五行镶嵌 + 仓库筛选 + 拖拽装备/卸下 + 一键合成。 */ @ccclass('UIPanelGemstone') export class UIPanelGemstone extends Auto_PanelGemstone { /** 五行格子模板 */ private _itemTpl: Node | null = null; /** 仓库格子模板 */ private _bagTpl: Node | null = null; /** 已创建的五行格 */ private _elementItems: Node[] = []; /** 仓库动态格 */ private _bagItems: Node[] = []; /** 当前筛选:0全部 1通用 2~6五行 */ private _filterType = 0; /** 仓库数据 */ private _bagList: IGemBagEntry[] = []; /** ScrollView 节点(卸下命中区) */ private _scrollNode: Node | null = null; 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._scrollNode = this.ui_content?.parent?.parent ?? null; this._bindDrag(true); } onEnable(): void { super.onEnable(); this._bindDrag(true); } onDisable(): void { this._bindDrag(false); this._endDrag(); super.onDisable(); } onInit(): void { this._filterType = 0; this._selectTag(this.btnAll); void this._refresh(); } click_btnClose(): void { void this.close(2); } click_btnAll(): void { this._onTag(0, this.btnAll); } 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._refreshBag(); this._refreshAllSlots(); if (!gains.length) { gg.ui.showToast('没有可合成的宝石'); return; } void gg.ui.openPanel(UIID.PopGetGem, { data: gains }); } /** 筛选切换 */ private _onTag(filterType: number, btn: Node): void { this._filterType = filterType; this._selectTag(btn); this._refreshBag(); } /** 标签底移到当前按钮 */ private _selectTag(btn: Node | null): void { if (!this.ui_tagBtnBg?.isValid || !btn?.isValid) return; this.ui_tagBtnBg.setWorldPosition(btn.worldPosition); } /** 全量刷新 */ private async _refresh(): Promise { await gg.gem.ensureTable(); if (!this.isValid) return; this._buildElements(); this._refreshBag(); } /** 实例化五行 */ 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 _refreshBag(): void { if (!this.ui_content?.isValid || !this._bagTpl?.isValid) { console.warn(LOG, 'ui_content/bagItemTemplate 缺失'); return; } 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); for (let i = 0; i < this._bagList.length; i++) { const entry = this._bagList[i]; 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; this._bagItems.push(node); } } /** 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; } this._endDrag(); } private _beginDrag(loc: Vec2): void { this._dragging = true; this._setScrollEnabled(false); 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('无法镶嵌到该位置'); } } else { if (!gg.gem.wearFromBag(te, ts, gemId)) { gg.ui.showToast('无法镶嵌到该位置'); } } this._refreshAllSlots(); this._refreshBag(); return; } if (fromE > 0 && fromS >= 0 && toBag) { gg.gem.unequip(fromE, fromS); this._refreshAllSlots(); this._refreshBag(); } } 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.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._setScrollEnabled(true); this._resetDrag(); } /** 拖拽时禁用仓库滚动 */ private _setScrollEnabled(on: boolean): void { const sv = this._scrollNode?.getComponent(ScrollView); if (sv) sv.enabled = on; } 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); const kids = this._bagItems; for (let i = kids.length - 1; i >= 0; i--) { const child = kids[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; } /** 是否落在仓库 ScrollView 区域 */ private _hitBagArea(loc: Vec2): boolean { const root = this._scrollNode; if (!root?.isValid) return false; this._hitPos.set(loc.x, loc.y); const ut = root.getComponent(UITransform); return !!ut?.getBoundingBoxToWorld().contains(this._hitPos); } }