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

467 lines
16 KiB

//*********************
// 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<void> {
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);
}
}