|
|
|
|
|
|
|
|
|
//*********************
|
|
|
|
|
// create by 流云
|
|
|
|
|
// time: Tue Aug 25 2026
|
|
|
|
|
// desc:
|
|
|
|
|
//*********************
|
|
|
|
|
import { _decorator, assetManager, AssetManager, Component, EventMouse, EventTouch, Input, input, instantiate, Label, Layout, Node, find, Prefab, Rect, sp, Sprite, Tween, tween, UIOpacity, UITransform, Vec2, Vec3 } from 'cc';
|
|
|
|
|
import { Auto_PanelShenjiang } from './Auto_PanelShenjiang';
|
|
|
|
|
import { GBundle, GPath, UIID } from '../../game/ConfigRes';
|
|
|
|
|
import { TableNames } from '../../game/ConfigTableData';
|
|
|
|
|
import { RankReport } from '../../manager/RankReport';
|
|
|
|
|
import { shenjiangItem } from './shenjiangItem';
|
|
|
|
|
import type { IMingjiangkuBagEntry, MingjiangkuBagKind } from '../../manager/EquipDataManager';
|
|
|
|
|
import { SpriteLoad } from 'db://assets/mx/components/SpriteLoad';
|
|
|
|
|
import { ensureSpineSkeletonDataRuntime } from 'db://assets/mx/module/lang/spineRuntimePatch';
|
|
|
|
|
import { RedDotView } from 'db://assets/mx/module/redDot/RedDotView';
|
|
|
|
|
import { UIMainUI } from '../MainUI/UIMainUI';
|
|
|
|
|
import { HERO_DEFAULT_EQUIP_ID } from '../../game/GameDataEnums';
|
|
|
|
|
import { FrameFlowHost } from '../../game/FrameFlowHost';
|
|
|
|
|
const { ccclass } = _decorator;
|
|
|
|
|
|
|
|
|
|
/** ui_atk 相对神将格在 center 本地 Y 偏移 */
|
|
|
|
|
const ATK_HINT_OFFSET_Y = 90;
|
|
|
|
|
const DRAG_THRESHOLD = 18;
|
|
|
|
|
const GHOST_OPACITY = 180;
|
|
|
|
|
const LOG = '[PanelShenjiang]';
|
|
|
|
|
/** 未穿戴成品武器时 name 节点图 */
|
|
|
|
|
const WEAPON_NAME_EMPTY_SPRITE = 'weapon/无';
|
|
|
|
|
/** 武器库每页格子数 */
|
|
|
|
|
const BAG_PAGE_SIZE = 16;
|
|
|
|
|
/** nameBg.name:不少于 5 字缩放 0.8,否则 1 */
|
|
|
|
|
const WEAPON_NAME_LONG_LEN = 5;
|
|
|
|
|
const HERO_NAMEBG_NAME_SCALE_LONG = 0.8;
|
|
|
|
|
const HERO_NAMEBG_NAME_SCALE_NORMAL = 1;
|
|
|
|
|
/** 武器冒出起始缩放 */
|
|
|
|
|
const WEAPON_POP_SCALE_FROM = 0.18;
|
|
|
|
|
/** 武器渐显秒 */
|
|
|
|
|
const WEAPON_POP_FADE_SEC = 0.2;
|
|
|
|
|
/** 武器回弹冒出秒 */
|
|
|
|
|
const WEAPON_POP_SCALE_SEC = 0.42;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 神将武器:合成神将网格 + 武器库拖拽穿戴
|
|
|
|
|
* 神将字:shenjiang/prefab/{Hero.res},Spine 为 res/spine/likeRole
|
|
|
|
|
*/
|
|
|
|
|
@ccclass('UIPanelShenjiang')
|
|
|
|
|
export class UIPanelShenjiang extends Auto_PanelShenjiang {
|
|
|
|
|
|
|
|
|
|
/** 武器库格子模板 */
|
|
|
|
|
private _bagItemTpl: Node | null = null;
|
|
|
|
|
/** 武器库拖拽幽灵模板(itemNode1) */
|
|
|
|
|
private _dragItemTpl: Node | null = null;
|
|
|
|
|
/** 当前页武器库格子节点 */
|
|
|
|
|
private _bagItems: Node[] = [];
|
|
|
|
|
/** 是否已用模板作为第一格 */
|
|
|
|
|
private _bagItemBuilt = false;
|
|
|
|
|
/** 武器库当前页(0 起) */
|
|
|
|
|
private _bagPageIndex = 0;
|
|
|
|
|
/** 武器库总页数 */
|
|
|
|
|
private _bagPageCount = 1;
|
|
|
|
|
|
|
|
|
|
/** 神将格子模板 */
|
|
|
|
|
private _heroTpl: Node | null = null;
|
|
|
|
|
/** 神将网格根(center/shenjiang) */
|
|
|
|
|
private _heroRoot: Node | null = null;
|
|
|
|
|
/** 已创建的神将格子 */
|
|
|
|
|
private _heroItems: Node[] = [];
|
|
|
|
|
/** 本轮打开页填充是否仍有效 */
|
|
|
|
|
private _fillAlive = false;
|
|
|
|
|
/** 填充代数,过期加载丢弃 */
|
|
|
|
|
private _fillToken = 0;
|
|
|
|
|
/** 武器库展示列表 */
|
|
|
|
|
private _bagList: IMingjiangkuBagEntry[] = [];
|
|
|
|
|
/** 正在拖的成品武器 id */
|
|
|
|
|
private _dragEquipId = 0;
|
|
|
|
|
/** 是否已进入拖拽 */
|
|
|
|
|
private _dragging = false;
|
|
|
|
|
/** 按下起点 */
|
|
|
|
|
private _dragStart = new Vec2();
|
|
|
|
|
/** 拖拽幽灵节点 */
|
|
|
|
|
private _ghost: Node | null = null;
|
|
|
|
|
/** 幽灵世界坐标缓存 */
|
|
|
|
|
private _ghostPos = new Vec3();
|
|
|
|
|
/** 命中检测坐标缓存 */
|
|
|
|
|
private _hitPos = new Vec2();
|
|
|
|
|
/** 拖拽监听是否已绑定 */
|
|
|
|
|
private _dragListening = false;
|
|
|
|
|
/** 武器库按下的格子 */
|
|
|
|
|
private _dragItem: shenjiangItem | null = null;
|
|
|
|
|
/** >0:从神将身上拖成品;0:从武器库拖 */
|
|
|
|
|
private _dragFromHeroId = 0;
|
|
|
|
|
/** 按下命中的成品/碎片,松手未拖则打开介绍 */
|
|
|
|
|
private _tapEquipId = 0;
|
|
|
|
|
/** 按下命中的武器库格子类型 */
|
|
|
|
|
private _tapBagKind: MingjiangkuBagKind = 'equip';
|
|
|
|
|
/** 本次按下是否来自神将格 */
|
|
|
|
|
private _pressFromHero = false;
|
|
|
|
|
/** 手指/鼠标已按下 */
|
|
|
|
|
private _pressing = false;
|
|
|
|
|
/** 回收弹窗打开中 */
|
|
|
|
|
private _openingHuishou = false;
|
|
|
|
|
/** center 根节点(ui_atk 定位参照) */
|
|
|
|
|
private _centerNode: Node | null = null;
|
|
|
|
|
/** ui_atk/atkUp */
|
|
|
|
|
private _atkUpNode: Node | null = null;
|
|
|
|
|
/** ui_atk/atkReduce */
|
|
|
|
|
private _atkReduceNode: Node | null = null;
|
|
|
|
|
/** 打开页分帧填充 */
|
|
|
|
|
private _flow: FrameFlowHost | null = null;
|
|
|
|
|
/** 本轮待填充神将 */
|
|
|
|
|
private _fillHeroes: ITableHero[] = [];
|
|
|
|
|
/** 已克隆神将格数 */
|
|
|
|
|
private _heroCloneIndex = 0;
|
|
|
|
|
/** 已绑名字条的神将下标 */
|
|
|
|
|
private _heroMetaIndex = 0;
|
|
|
|
|
/** 已填充的武器库格下标 */
|
|
|
|
|
private _bagFillIndex = 0;
|
|
|
|
|
/** 尚未回调的神将预制体预取 */
|
|
|
|
|
private _heroPrefabPending = 0;
|
|
|
|
|
/** 分帧泵已预约 */
|
|
|
|
|
private _pumpScheduled = false;
|
|
|
|
|
|
|
|
|
|
onLoad(): void {
|
|
|
|
|
super.onLoad();
|
|
|
|
|
this._centerNode = find('center', this.node);
|
|
|
|
|
if (this.ui_atk?.isValid) {
|
|
|
|
|
this._atkUpNode = this.ui_atk.getChildByName('atkUp');
|
|
|
|
|
this._atkReduceNode = this.ui_atk.getChildByName('atkReduce');
|
|
|
|
|
this.ui_atk.active = false;
|
|
|
|
|
}
|
|
|
|
|
if (this.ui_content?.isValid) {
|
|
|
|
|
this.ui_content.active = true;
|
|
|
|
|
this._dragItemTpl = find('center/ui_content/itemNode1', this.node)
|
|
|
|
|
?? this.ui_content.getChildByName('itemNode1')
|
|
|
|
|
?? this.ui_content.children[0]
|
|
|
|
|
?? null;
|
|
|
|
|
this._bagItemTpl = this._dragItemTpl;
|
|
|
|
|
if (this._bagItemTpl?.isValid) {
|
|
|
|
|
RedDotView.setLocal(this._bagItemTpl, false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
this._heroTpl = find('center/shenjiang/shenjiangNode', this.node);
|
|
|
|
|
this._heroRoot = this._heroTpl?.parent ?? null;
|
|
|
|
|
if (this._heroTpl) {
|
|
|
|
|
this._heroTpl.removeFromParent();
|
|
|
|
|
this._heroTpl.parent = this.node;
|
|
|
|
|
this._heroTpl.active = false;
|
|
|
|
|
this._setHeroWearMask(this._heroTpl, false);
|
|
|
|
|
this._hideHeroWeapon(this._heroTpl);
|
|
|
|
|
}
|
|
|
|
|
this._setupHeroGridLayout();
|
|
|
|
|
this._warmHeroPrefabs();
|
|
|
|
|
this._bindWeaponDrag(true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onEnable(): void {
|
|
|
|
|
super.onEnable();
|
|
|
|
|
this._bindWeaponDrag(true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onDisable(): void {
|
|
|
|
|
this._bindWeaponDrag(false);
|
|
|
|
|
this._endEquipDrag();
|
|
|
|
|
this._openingHuishou = false;
|
|
|
|
|
this._fillAlive = false;
|
|
|
|
|
this._fillToken++;
|
|
|
|
|
this._stopFillFlow();
|
|
|
|
|
this._stopAllWeaponPops();
|
|
|
|
|
gg.equip.markNewWeaponBadgeDismissed(gg.equip.collectVisibleNewWeaponBadgeIds());
|
|
|
|
|
this._hideAllBagNewWeaponTags();
|
|
|
|
|
super.onDisable();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onInit(): void {
|
|
|
|
|
this._bindWeaponDrag(true);
|
|
|
|
|
this._bagPageIndex = 0;
|
|
|
|
|
gg.equip.markHeroSeen();
|
|
|
|
|
void this._refresh().then(() => {
|
|
|
|
|
if (!this.isValid) return;
|
|
|
|
|
this._tryOpenPopNewWeapon();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 有待展示的新成品武器时弹出 PopNewWeapon */
|
|
|
|
|
private _tryOpenPopNewWeapon(): void {
|
|
|
|
|
if (gg.ui.hasPopPanelActiveOrQueued(UIID.PopNewWeapon)) return;
|
|
|
|
|
const ids = gg.equip.collectPendingPopNewWeaponIds();
|
|
|
|
|
if (!ids.length) return;
|
|
|
|
|
void gg.ui.openPanel(UIID.PopNewWeapon, { data: ids });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 主界面 Tab,遮罩不关闭 */
|
|
|
|
|
click_bgMask(): void {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 关闭神将页,回到首页 Tab */
|
|
|
|
|
click_btnClose(): void {
|
|
|
|
|
const main = gg.ui.getPanelBase(UIID.MainUI) as UIMainUI;
|
|
|
|
|
if (main) {
|
|
|
|
|
void main.showPage(UIID.Home);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
void this.close(2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 关闭按钮图标 */
|
|
|
|
|
click_icon(): void {
|
|
|
|
|
this.click_btnClose();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 打开回收弹窗 */
|
|
|
|
|
click_btn_huishou(): void {
|
|
|
|
|
if (this._openingHuishou || gg.ui.hasPopPanelActiveOrQueued(UIID.popHuishou)) return;
|
|
|
|
|
this._openingHuishou = true;
|
|
|
|
|
void gg.ui.openPanel(UIID.popHuishou, {
|
|
|
|
|
onClose: this._onHuishouClosed,
|
|
|
|
|
}).then(this._onHuishouOpenDone).catch(this._onHuishouOpenFail);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 武器库下一页 */
|
|
|
|
|
click_btnNext(): void {
|
|
|
|
|
if (this._bagPageIndex >= this._bagPageCount - 1) return;
|
|
|
|
|
this._bagPageIndex++;
|
|
|
|
|
this._renderBagPage();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 武器库上一页 */
|
|
|
|
|
click_btnPre(): void {
|
|
|
|
|
if (this._bagPageIndex <= 0) return;
|
|
|
|
|
this._bagPageIndex--;
|
|
|
|
|
this._renderBagPage();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _onHuishouClosed = (): void => {
|
|
|
|
|
this._openingHuishou = false;
|
|
|
|
|
this._refreshBagList();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
private _onHuishouOpenDone = (ok: boolean): void => {
|
|
|
|
|
if (!ok) this._openingHuishou = false;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
private _onHuishouOpenFail = (): void => {
|
|
|
|
|
this._openingHuishou = false;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
private _bindWeaponDrag(on: boolean): void {
|
|
|
|
|
if (this._dragListening === on) return;
|
|
|
|
|
this._dragListening = on;
|
|
|
|
|
const bagRoot = this.ui_content;
|
|
|
|
|
const heroRoot = this._heroRoot;
|
|
|
|
|
if (on) {
|
|
|
|
|
bagRoot?.on(Node.EventType.TOUCH_START, this._onBagAreaTouchStart, this, true);
|
|
|
|
|
bagRoot?.on(Node.EventType.TOUCH_MOVE, this._onBagAreaTouchMove, this, true);
|
|
|
|
|
bagRoot?.on(Node.EventType.TOUCH_END, this._onBagAreaTouchEnd, this, true);
|
|
|
|
|
bagRoot?.on(Node.EventType.TOUCH_CANCEL, this._onBagAreaTouchEnd, this, true);
|
|
|
|
|
heroRoot?.on(Node.EventType.TOUCH_START, this._onHeroTouchStart, this, true);
|
|
|
|
|
heroRoot?.on(Node.EventType.TOUCH_MOVE, this._onBagAreaTouchMove, this, true);
|
|
|
|
|
heroRoot?.on(Node.EventType.TOUCH_END, this._onBagAreaTouchEnd, this, true);
|
|
|
|
|
heroRoot?.on(Node.EventType.TOUCH_CANCEL, this._onBagAreaTouchEnd, this, true);
|
|
|
|
|
this.node.on(Node.EventType.TOUCH_MOVE, this._onBagAreaTouchMove, this, true);
|
|
|
|
|
this.node.on(Node.EventType.TOUCH_END, this._onBagAreaTouchEnd, this, true);
|
|
|
|
|
this.node.on(Node.EventType.TOUCH_CANCEL, this._onBagAreaTouchEnd, this, true);
|
|
|
|
|
input.on(Input.EventType.TOUCH_START, this._onBagTouchStart, this);
|
|
|
|
|
input.on(Input.EventType.TOUCH_MOVE, this._onBagTouchMove, this);
|
|
|
|
|
input.on(Input.EventType.TOUCH_END, this._onBagTouchEnd, this);
|
|
|
|
|
input.on(Input.EventType.TOUCH_CANCEL, this._onBagTouchEnd, this);
|
|
|
|
|
input.on(Input.EventType.MOUSE_DOWN, this._onBagMouseDown, this);
|
|
|
|
|
input.on(Input.EventType.MOUSE_MOVE, this._onBagMouseMove, this);
|
|
|
|
|
input.on(Input.EventType.MOUSE_UP, this._onBagMouseUp, this);
|
|
|
|
|
} else {
|
|
|
|
|
bagRoot?.off(Node.EventType.TOUCH_START, this._onBagAreaTouchStart, this, true);
|
|
|
|
|
bagRoot?.off(Node.EventType.TOUCH_MOVE, this._onBagAreaTouchMove, this, true);
|
|
|
|
|
bagRoot?.off(Node.EventType.TOUCH_END, this._onBagAreaTouchEnd, this, true);
|
|
|
|
|
bagRoot?.off(Node.EventType.TOUCH_CANCEL, this._onBagAreaTouchEnd, this, true);
|
|
|
|
|
heroRoot?.off(Node.EventType.TOUCH_START, this._onHeroTouchStart, this, true);
|
|
|
|
|
heroRoot?.off(Node.EventType.TOUCH_MOVE, this._onBagAreaTouchMove, this, true);
|
|
|
|
|
heroRoot?.off(Node.EventType.TOUCH_END, this._onBagAreaTouchEnd, this, true);
|
|
|
|
|
heroRoot?.off(Node.EventType.TOUCH_CANCEL, this._onBagAreaTouchEnd, this, true);
|
|
|
|
|
this.node.off(Node.EventType.TOUCH_MOVE, this._onBagAreaTouchMove, this, true);
|
|
|
|
|
this.node.off(Node.EventType.TOUCH_END, this._onBagAreaTouchEnd, this, true);
|
|
|
|
|
this.node.off(Node.EventType.TOUCH_CANCEL, this._onBagAreaTouchEnd, this, true);
|
|
|
|
|
input.off(Input.EventType.TOUCH_START, this._onBagTouchStart, this);
|
|
|
|
|
input.off(Input.EventType.TOUCH_MOVE, this._onBagTouchMove, this);
|
|
|
|
|
input.off(Input.EventType.TOUCH_END, this._onBagTouchEnd, this);
|
|
|
|
|
input.off(Input.EventType.TOUCH_CANCEL, this._onBagTouchEnd, this);
|
|
|
|
|
input.off(Input.EventType.MOUSE_DOWN, this._onBagMouseDown, this);
|
|
|
|
|
input.off(Input.EventType.MOUSE_MOVE, this._onBagMouseMove, this);
|
|
|
|
|
input.off(Input.EventType.MOUSE_UP, this._onBagMouseUp, this);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _onBagAreaTouchStart(e: EventTouch): void {
|
|
|
|
|
this._pointerStart(e.getUILocation());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _onBagAreaTouchMove(e: EventTouch): void {
|
|
|
|
|
this._pointerMove(e.getUILocation());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _onBagAreaTouchEnd(e: EventTouch): void {
|
|
|
|
|
this._pointerEnd(e.getUILocation());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _onHeroTouchStart(e: EventTouch): void {
|
|
|
|
|
this._pointerStart(e.getUILocation());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _onBagTouchStart(e: EventTouch): void {
|
|
|
|
|
this._pointerStart(e.getUILocation());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _onBagTouchMove(e: EventTouch): void {
|
|
|
|
|
this._pointerMove(e.getUILocation());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _onBagTouchEnd(e: EventTouch): void {
|
|
|
|
|
this._pointerEnd(e.getUILocation());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _onBagMouseDown(e: EventMouse): void {
|
|
|
|
|
if (e.getButton() !== EventMouse.BUTTON_LEFT) return;
|
|
|
|
|
this._pointerStart(e.getUILocation());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _onBagMouseMove(e: EventMouse): void {
|
|
|
|
|
this._pointerMove(e.getUILocation());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _onBagMouseUp(e: EventMouse): void {
|
|
|
|
|
this._pointerEnd(e.getUILocation());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _pointerStart(loc: Vec2): void {
|
|
|
|
|
if (this._pressing || this._dragging) return;
|
|
|
|
|
const bag = this._hitBagItem(loc);
|
|
|
|
|
if (bag) {
|
|
|
|
|
this._resetDragFields();
|
|
|
|
|
this._pressing = true;
|
|
|
|
|
this._pressFromHero = false;
|
|
|
|
|
this._dragStart.set(loc);
|
|
|
|
|
this._tapEquipId = bag.data?.equipId || 0;
|
|
|
|
|
this._tapBagKind = bag.data?.kind === 'fragment' ? 'fragment' : 'equip';
|
|
|
|
|
this._dragFromHeroId = 0;
|
|
|
|
|
this._dragging = false;
|
|
|
|
|
if (bag.data?.kind === 'equip' && this._tapEquipId > 0) {
|
|
|
|
|
this._dragEquipId = this._tapEquipId;
|
|
|
|
|
this._dragItem = bag;
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const heroNode = this._hitHero(loc);
|
|
|
|
|
const heroId = Number(heroNode?.['heroId']) || 0;
|
|
|
|
|
if (!heroNode || heroId <= 0) return;
|
|
|
|
|
const isDefault = gg.equip.isHeroDefaultWeapon(heroId);
|
|
|
|
|
const equipId = gg.equip.getHeroEquipId(heroId);
|
|
|
|
|
if (isDefault || equipId <= 0) return;
|
|
|
|
|
this._resetDragFields();
|
|
|
|
|
this._pressing = true;
|
|
|
|
|
this._pressFromHero = true;
|
|
|
|
|
this._dragStart.set(loc);
|
|
|
|
|
this._tapEquipId = equipId;
|
|
|
|
|
this._dragEquipId = equipId;
|
|
|
|
|
this._dragItem = null;
|
|
|
|
|
this._dragFromHeroId = heroId;
|
|
|
|
|
this._dragging = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _pointerMove(loc: Vec2): void {
|
|
|
|
|
if (this._dragging) {
|
|
|
|
|
this._moveGhost(loc);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!this._pressing) return;
|
|
|
|
|
if (this._dragEquipId <= 0 && this._tapEquipId <= 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;
|
|
|
|
|
if (this._dragEquipId > 0) {
|
|
|
|
|
this._beginEquipDrag(loc);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
this._tapEquipId = 0;
|
|
|
|
|
this._dragFromHeroId = 0;
|
|
|
|
|
this._dragItem = null;
|
|
|
|
|
this._pressFromHero = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _pointerEnd(loc: Vec2): void {
|
|
|
|
|
if (!this._pressing && !this._dragging) return;
|
|
|
|
|
if (this._dragging) {
|
|
|
|
|
this._finishEquipDrag(loc);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const tapEquipId = this._tapEquipId;
|
|
|
|
|
const tapKind = this._tapBagKind;
|
|
|
|
|
this._endEquipDrag();
|
|
|
|
|
if (tapEquipId > 0) {
|
|
|
|
|
if (tapKind === 'equip') {
|
|
|
|
|
gg.equip.markNewWeaponBadgeDismissed([tapEquipId]);
|
|
|
|
|
this._hideBagNewWeaponTag(tapEquipId);
|
|
|
|
|
}
|
|
|
|
|
gg.equip.markFragSeen(tapEquipId);
|
|
|
|
|
this._refreshBagList();
|
|
|
|
|
gg.ui.openPanel(UIID.mingjiangIntroduction, { data: { equipId: tapEquipId, kind: tapKind } });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _hitBagItem(loc: Vec2): shenjiangItem | null {
|
|
|
|
|
const content = this.ui_content;
|
|
|
|
|
if (!content?.isValid) return null;
|
|
|
|
|
this._hitPos.set(loc.x, loc.y);
|
|
|
|
|
const kids = content.children;
|
|
|
|
|
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)) continue;
|
|
|
|
|
const comp = child.getComponent(shenjiangItem);
|
|
|
|
|
if ((comp?.data?.equipId | 0) > 0) return comp;
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _finishEquipDrag(loc: Vec2): void {
|
|
|
|
|
const heroNode = this._hitHero(loc);
|
|
|
|
|
const toHeroId = Number(heroNode?.['heroId']) || 0;
|
|
|
|
|
const equipId = this._dragEquipId;
|
|
|
|
|
const fromHeroId = this._dragFromHeroId;
|
|
|
|
|
|
|
|
|
|
if (heroNode && toHeroId > 0) {
|
|
|
|
|
if (fromHeroId > 0) {
|
|
|
|
|
if (fromHeroId !== toHeroId && gg.equip.moveOrSwapHeroEquip(fromHeroId, toHeroId)) {
|
|
|
|
|
this._refreshHeroWeaponSkin(this._heroItemById(fromHeroId));
|
|
|
|
|
this._refreshHeroWeaponSkin(heroNode);
|
|
|
|
|
this._reportWear(toHeroId, gg.equip.getHeroEquipId(toHeroId));
|
|
|
|
|
}
|
|
|
|
|
} else if (gg.equip.wear(toHeroId, equipId)) {
|
|
|
|
|
this._refreshHeroWeaponSkin(heroNode);
|
|
|
|
|
this._reportWear(toHeroId, equipId);
|
|
|
|
|
}
|
|
|
|
|
} else if (fromHeroId > 0 && this._hitBagArea(loc)) {
|
|
|
|
|
if (gg.equip.unequip(fromHeroId)) {
|
|
|
|
|
this._refreshHeroWeaponSkin(this._heroItemById(fromHeroId));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
this._endEquipDrag();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _reportWear(heroId: number, equipId: number): void {
|
|
|
|
|
const hero = gg.data.table.getTableData<ITableHero>(TableNames.Hero, heroId);
|
|
|
|
|
const equip = gg.equip.getEquip(equipId);
|
|
|
|
|
RankReport.mingjiangWear(
|
|
|
|
|
String(hero?.name ?? '').trim(),
|
|
|
|
|
String(equip?.name ?? '').trim(),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _beginEquipDrag(loc: Vec2): void {
|
|
|
|
|
this._dragging = true;
|
|
|
|
|
this._applyWearHint(this._dragEquipId);
|
|
|
|
|
this._spawnBagDragGhost(loc);
|
|
|
|
|
if (this._dragFromHeroId <= 0) {
|
|
|
|
|
const left = Math.max(0, gg.equip.getFreeEquipCount(this._dragEquipId) - 1);
|
|
|
|
|
this._dragItem?.setCount(left);
|
|
|
|
|
}
|
|
|
|
|
this._moveGhost(loc);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 武器库拖拽:复制 itemNode1,仅展示 quality + name */
|
|
|
|
|
private _spawnBagDragGhost(loc: Vec2): void {
|
|
|
|
|
this._clearGhost();
|
|
|
|
|
const tpl = this._dragItemTpl;
|
|
|
|
|
if (!tpl?.isValid) return;
|
|
|
|
|
const data = this._dragItem?.data ?? this._ghostEquipData(this._dragEquipId);
|
|
|
|
|
if (!data || data.kind !== 'equip') return;
|
|
|
|
|
|
|
|
|
|
const ghost = instantiate(tpl);
|
|
|
|
|
ghost.parent = this.node;
|
|
|
|
|
ghost.layer = this.node.layer;
|
|
|
|
|
this._setLayerRecursive(ghost, this.node.layer);
|
|
|
|
|
const siblings = this.node.children;
|
|
|
|
|
if (siblings) ghost.setSiblingIndex(siblings.length - 1);
|
|
|
|
|
ghost.active = true;
|
|
|
|
|
this._disableLogicScripts(ghost);
|
|
|
|
|
const comp = ghost.getComponent(shenjiangItem) || ghost.addComponent(shenjiangItem);
|
|
|
|
|
comp.setData(data);
|
|
|
|
|
this._applyBagDragGhostView(ghost);
|
|
|
|
|
RedDotView.setLocal(ghost, false);
|
|
|
|
|
|
|
|
|
|
const ut = ghost.getComponent(UITransform);
|
|
|
|
|
if (ut) ut.hitTest = this._ghostNoHit;
|
|
|
|
|
const uo = ghost.getComponent(UIOpacity) || ghost.addComponent(UIOpacity);
|
|
|
|
|
uo.opacity = GHOST_OPACITY;
|
|
|
|
|
|
|
|
|
|
this._ghost = ghost;
|
|
|
|
|
this._moveGhost(loc);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 拖拽幽灵仅保留品质底与武器名 */
|
|
|
|
|
private _applyBagDragGhostView(ghost: Node): void {
|
|
|
|
|
const suipian = ghost.getChildByName('suipianNode');
|
|
|
|
|
if (suipian) suipian.active = false;
|
|
|
|
|
|
|
|
|
|
const openNode = ghost.getChildByName('openNode');
|
|
|
|
|
if (openNode) {
|
|
|
|
|
openNode.active = true;
|
|
|
|
|
const numBg = openNode.getChildByName('numBg');
|
|
|
|
|
if (numBg) numBg.active = false;
|
|
|
|
|
for (let t = 1; t <= 4; t++) {
|
|
|
|
|
const typeNode = openNode.getChildByName(`type${t}`);
|
|
|
|
|
if (typeNode) typeNode.active = false;
|
|
|
|
|
}
|
|
|
|
|
const nameNode = openNode.getChildByName('name');
|
|
|
|
|
if (nameNode) nameNode.active = true;
|
|
|
|
|
const newWeapon = openNode.getChildByName('newWeapon');
|
|
|
|
|
if (newWeapon) newWeapon.active = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const quality = ghost.getChildByName('quality');
|
|
|
|
|
if (quality) quality.active = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 神将格拖拽:用武器表拼幽灵数据 */
|
|
|
|
|
private _ghostEquipData(equipId: number): IMingjiangkuBagEntry | null {
|
|
|
|
|
const id = Number(equipId) | 0;
|
|
|
|
|
if (id <= 0) return null;
|
|
|
|
|
if (!gg.equip.getEquip(id)) {
|
|
|
|
|
console.log(LOG, '拖拽幽灵武器缺失', id);
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
return { kind: 'equip', equipId: id, itemId: 0, count: 1, need: 0 };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _moveGhost(loc: Vec2): void {
|
|
|
|
|
if (this._ghost?.isValid) {
|
|
|
|
|
this._ghost.setWorldPosition(this._ghostPos.set(loc.x, loc.y, 0));
|
|
|
|
|
}
|
|
|
|
|
if (this._dragging) this._updateAtkHint(loc);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _hitHero(loc: Vec2): Node | null {
|
|
|
|
|
this._hitPos.set(loc.x, loc.y);
|
|
|
|
|
let best: Node | null = null;
|
|
|
|
|
let bestD = Number.POSITIVE_INFINITY;
|
|
|
|
|
for (let i = 0; i < this._heroItems.length; i++) {
|
|
|
|
|
const item = this._heroItems[i];
|
|
|
|
|
if (!item?.isValid || !item.activeInHierarchy) continue;
|
|
|
|
|
const box = this._heroHitBox(item);
|
|
|
|
|
if (!box.contains(this._hitPos)) continue;
|
|
|
|
|
const dx = this._hitPos.x - (box.x + box.width * 0.5);
|
|
|
|
|
const dy = this._hitPos.y - (box.y + box.height * 0.5);
|
|
|
|
|
const d = dx * dx + dy * dy;
|
|
|
|
|
if (d < bestD) {
|
|
|
|
|
bestD = d;
|
|
|
|
|
best = item;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return best;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 按格子 UITransform 切块,避免 Spine 包围盒叠到上一行 */
|
|
|
|
|
private _heroHitBox(item: Node): Rect {
|
|
|
|
|
const ut = item.getComponent(UITransform);
|
|
|
|
|
const p = item.worldPosition;
|
|
|
|
|
if (!ut) {
|
|
|
|
|
console.log(LOG, '神将格缺少 UITransform', item.name);
|
|
|
|
|
return new Rect(p.x, p.y, 0, 0);
|
|
|
|
|
}
|
|
|
|
|
return new Rect(p.x - ut.width * 0.5, p.y - ut.height * 0.5, ut.width, ut.height);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _applyWearHint(equipId: number): void {
|
|
|
|
|
const fromHero = this._dragFromHeroId;
|
|
|
|
|
for (let i = 0; i < this._heroItems.length; i++) {
|
|
|
|
|
const item = this._heroItems[i];
|
|
|
|
|
if (!item?.isValid || !item.activeInHierarchy) continue;
|
|
|
|
|
const heroId = Number(item['heroId']) || 0;
|
|
|
|
|
if (fromHero > 0 && heroId === fromHero) {
|
|
|
|
|
this._setHeroWearMask(item, false);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
const can = this._canPreviewEquipOnHero(heroId, equipId, fromHero);
|
|
|
|
|
this._setHeroWearMask(item, heroId <= 0 || !can);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _clearWearHint(): void {
|
|
|
|
|
for (let i = 0; i < this._heroItems.length; i++) {
|
|
|
|
|
const item = this._heroItems[i];
|
|
|
|
|
if (!item?.isValid) continue;
|
|
|
|
|
this._setHeroWearMask(item, false);
|
|
|
|
|
}
|
|
|
|
|
this._hideAtkHint();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 拖拽悬停神将或武器库卸装区时刷新 ui_atk */
|
|
|
|
|
private _updateAtkHint(loc: Vec2): void {
|
|
|
|
|
if (!this._dragging || this._dragEquipId <= 0) {
|
|
|
|
|
this._hideAtkHint();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const fromHero = this._dragFromHeroId;
|
|
|
|
|
if (fromHero > 0 && this._hitBagArea(loc)) {
|
|
|
|
|
this._showUnequipAtkHint(fromHero);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const heroNode = this._hitHero(loc);
|
|
|
|
|
const heroId = Number(heroNode?.['heroId']) || 0;
|
|
|
|
|
if (!heroNode || heroId <= 0) {
|
|
|
|
|
this._hideAtkHint();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (fromHero > 0 && heroId === fromHero) {
|
|
|
|
|
this._hideAtkHint();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!this._canPreviewEquipOnHero(heroId, this._dragEquipId, fromHero)) {
|
|
|
|
|
this._hideAtkHint();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const curWeaponId = gg.equip.getHeroDisplayWeaponId(heroId);
|
|
|
|
|
const nextWeaponId = this._dragEquipId;
|
|
|
|
|
if (curWeaponId === nextWeaponId) {
|
|
|
|
|
this._hideAtkHint();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const curAtk = this._resolveHeroPanelAttack(heroId, curWeaponId);
|
|
|
|
|
const nextAtk = this._resolveHeroPanelAttack(heroId, nextWeaponId);
|
|
|
|
|
this._showAtkHint(heroNode, curAtk, nextAtk);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 拖拽中是否可在目标神将上预览/放置武器(含已穿戴替换、神将互换)。
|
|
|
|
|
* 已穿戴成品时替换仅校验类型,不要求背包空闲数(与原武器回库等价)。
|
|
|
|
|
*/
|
|
|
|
|
private _canPreviewEquipOnHero(heroId: number, equipId: number, fromHeroId: number): boolean {
|
|
|
|
|
const id = Number(heroId) | 0;
|
|
|
|
|
const eq = Number(equipId) | 0;
|
|
|
|
|
const from = Number(fromHeroId) | 0;
|
|
|
|
|
if (id <= 0 || eq <= 0) return false;
|
|
|
|
|
|
|
|
|
|
if (from > 0) {
|
|
|
|
|
if (from === id) return false;
|
|
|
|
|
if (!gg.equip.canWearType(id, eq)) return false;
|
|
|
|
|
const targetWorn = gg.equip.getHeroEquipId(id);
|
|
|
|
|
if (targetWorn > 0 && !gg.equip.canWearType(from, targetWorn)) return false;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!gg.equip.canWearType(id, eq)) return false;
|
|
|
|
|
if (!gg.equip.isHeroDefaultWeapon(id)) return true;
|
|
|
|
|
return gg.equip.canWear(id, eq);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 神将武器拖入武器库:在源神将格展示卸装后攻击力变化 */
|
|
|
|
|
private _showUnequipAtkHint(fromHeroId: number): void {
|
|
|
|
|
const heroItem = this._heroItemById(fromHeroId);
|
|
|
|
|
if (!heroItem?.isValid) {
|
|
|
|
|
this._hideAtkHint();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const curWeaponId = gg.equip.getHeroDisplayWeaponId(fromHeroId);
|
|
|
|
|
if (curWeaponId <= 0) {
|
|
|
|
|
this._hideAtkHint();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const curAtk = this._resolveHeroPanelAttack(fromHeroId, curWeaponId);
|
|
|
|
|
const nextAtk = this._resolveHeroPanelAttack(fromHeroId, HERO_DEFAULT_EQUIP_ID);
|
|
|
|
|
this._showAtkHint(heroItem, curAtk, nextAtk);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 在神将格上方展示攻击力变化(center 本地 y 偏移) */
|
|
|
|
|
private _showAtkHint(heroItem: Node, curAtk: number, nextAtk: number): void {
|
|
|
|
|
const atkRoot = this.ui_atk;
|
|
|
|
|
const center = this._centerNode;
|
|
|
|
|
if (!atkRoot?.isValid || !center?.isValid || !heroItem?.isValid) return;
|
|
|
|
|
|
|
|
|
|
const up = this._atkUpNode?.isValid ? this._atkUpNode : atkRoot.getChildByName('atkUp');
|
|
|
|
|
const down = this._atkReduceNode?.isValid ? this._atkReduceNode : atkRoot.getChildByName('atkReduce');
|
|
|
|
|
if (up?.isValid) this._atkUpNode = up;
|
|
|
|
|
if (down?.isValid) this._atkReduceNode = down;
|
|
|
|
|
|
|
|
|
|
const centerUt = center.getComponent(UITransform);
|
|
|
|
|
if (!centerUt) return;
|
|
|
|
|
const local = centerUt.convertToNodeSpaceAR(heroItem.worldPosition);
|
|
|
|
|
atkRoot.setPosition(local.x, local.y + ATK_HINT_OFFSET_Y, 0);
|
|
|
|
|
atkRoot.active = true;
|
|
|
|
|
atkRoot.setSiblingIndex(center.children.length - 1);
|
|
|
|
|
|
|
|
|
|
const increased = nextAtk > curAtk;
|
|
|
|
|
const decreased = nextAtk < curAtk;
|
|
|
|
|
if (up?.isValid) {
|
|
|
|
|
up.active = increased || (!increased && !decreased);
|
|
|
|
|
if (up.active) this._fillAtkHintPanel(up, curAtk, nextAtk);
|
|
|
|
|
}
|
|
|
|
|
if (down?.isValid) {
|
|
|
|
|
down.active = decreased;
|
|
|
|
|
if (down.active) this._fillAtkHintPanel(down, curAtk, nextAtk);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 写入 atkUp/atkReduce 内 cur、next 数值 */
|
|
|
|
|
private _fillAtkHintPanel(panel: Node, curAtk: number, nextAtk: number): void {
|
|
|
|
|
const curLb = panel.getChildByName('cur')?.getComponent(Label);
|
|
|
|
|
const nextLb = panel.getChildByName('next')?.getComponent(Label);
|
|
|
|
|
if (curLb) curLb.string = `${curAtk}`;
|
|
|
|
|
if (nextLb) nextLb.string = `${nextAtk}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 隐藏攻击力变化提示 */
|
|
|
|
|
private _hideAtkHint(): void {
|
|
|
|
|
if (this.ui_atk?.isValid) this.ui_atk.active = false;
|
|
|
|
|
if (this._atkUpNode?.isValid) this._atkUpNode.active = false;
|
|
|
|
|
if (this._atkReduceNode?.isValid) this._atkReduceNode.active = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 神将面板攻击力:Hero.atk + 武器/宝石 atk(无等级系数、无专属倍率)。
|
|
|
|
|
* @param weaponEquipId 预览武器;缺省为当前展示武器
|
|
|
|
|
*/
|
|
|
|
|
private _resolveHeroPanelAttack(heroId: number, weaponEquipId?: number): number {
|
|
|
|
|
const id = Number(heroId) | 0;
|
|
|
|
|
if (id <= 0) return 0;
|
|
|
|
|
const hero = gg.data.table.getTableData<ITableHero>(TableNames.Hero, id);
|
|
|
|
|
if (!hero) return 0;
|
|
|
|
|
|
|
|
|
|
let weaponId = weaponEquipId;
|
|
|
|
|
if (weaponId === undefined) {
|
|
|
|
|
weaponId = gg.equip.getHeroDisplayWeaponId(id);
|
|
|
|
|
} else if (!(weaponId > 0)) {
|
|
|
|
|
weaponId = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const weaponRow = weaponId > 0 ? gg.equip.getEquip(weaponId) : null;
|
|
|
|
|
const weaponAtk = Math.floor(Number(weaponRow?.atk) || 0);
|
|
|
|
|
const heroBase = Number(hero.atk) || 0;
|
|
|
|
|
return Math.floor(heroBase + weaponAtk);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 拖拽穿戴提示:mask 显隐表示是否可穿戴 */
|
|
|
|
|
private _setHeroWearMask(item: Node, show: boolean): void {
|
|
|
|
|
const mask = item.getChildByName('mask');
|
|
|
|
|
if (mask) mask.active = show;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 穿戴变化后刷新武器 Spine、名字条并冒出 */
|
|
|
|
|
private _refreshHeroWeaponSkin(heroItem: Node | null | undefined): void {
|
|
|
|
|
if (!heroItem?.isValid) return;
|
|
|
|
|
const heroId = Number(heroItem['heroId']) || 0;
|
|
|
|
|
this._applyHeroWeaponNameBar(heroItem, heroId);
|
|
|
|
|
this._applyHeroWeaponNode(heroItem, heroId);
|
|
|
|
|
this._playHeroWeaponPop(heroItem);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _heroItemById(heroId: number): Node | null {
|
|
|
|
|
const id = Number(heroId) | 0;
|
|
|
|
|
if (id <= 0) return null;
|
|
|
|
|
for (let i = 0; i < this._heroItems.length; i++) {
|
|
|
|
|
if ((Number(this._heroItems[i]?.['heroId']) || 0) === id) return this._heroItems[i];
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 武器库卸装落点:ui_wuqi 固定区域(ui_content 空库时 Layout 高度为 0) */
|
|
|
|
|
private _hitBagArea(loc: Vec2): boolean {
|
|
|
|
|
const n = this.ui_wuqi;
|
|
|
|
|
if (!n?.isValid) {
|
|
|
|
|
console.log(LOG, 'ui_wuqi 缺失');
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
const ut = n.getComponent(UITransform);
|
|
|
|
|
return !!ut?.getBoundingBoxToWorld().contains(this._hitPos.set(loc.x, loc.y));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _clearGhost(): void {
|
|
|
|
|
if (this._ghost?.isValid) {
|
|
|
|
|
this._ghost.removeFromParent();
|
|
|
|
|
this._ghost.destroy();
|
|
|
|
|
}
|
|
|
|
|
this._ghost = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _resetDragFields(): void {
|
|
|
|
|
this._pressing = false;
|
|
|
|
|
this._pressFromHero = false;
|
|
|
|
|
this._dragging = false;
|
|
|
|
|
this._dragEquipId = 0;
|
|
|
|
|
this._dragItem = null;
|
|
|
|
|
this._dragFromHeroId = 0;
|
|
|
|
|
this._tapEquipId = 0;
|
|
|
|
|
this._tapBagKind = 'equip';
|
|
|
|
|
this._clearGhost();
|
|
|
|
|
this._clearWearHint();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _endEquipDrag(): void {
|
|
|
|
|
const needRefresh = this._dragging;
|
|
|
|
|
this._resetDragFields();
|
|
|
|
|
if (needRefresh) this._refreshBagList();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async _refresh(): Promise<void> {
|
|
|
|
|
await gg.equip.ensureTable();
|
|
|
|
|
if (!this.isValid) return;
|
|
|
|
|
gg.equip.composeReadyFragments();
|
|
|
|
|
this._beginFill();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _refreshBagList(): void {
|
|
|
|
|
this._bagList = gg.equip.getBagDisplayList();
|
|
|
|
|
const pageCount = this._bagList.length > 0
|
|
|
|
|
? Math.ceil(this._bagList.length / BAG_PAGE_SIZE)
|
|
|
|
|
: 1;
|
|
|
|
|
this._bagPageCount = pageCount;
|
|
|
|
|
if (this._bagPageIndex >= pageCount) {
|
|
|
|
|
this._bagPageIndex = Math.max(0, pageCount - 1);
|
|
|
|
|
}
|
|
|
|
|
this._renderBagPage();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 渲染当前页武器库格子 */
|
|
|
|
|
private _renderBagPage(): void {
|
|
|
|
|
this._ensureBagItemNodes();
|
|
|
|
|
const start = this._bagPageIndex * BAG_PAGE_SIZE;
|
|
|
|
|
for (let i = 0; i < this._bagItems.length; i++) {
|
|
|
|
|
const item = this._bagItems[i];
|
|
|
|
|
if (!item?.isValid) continue;
|
|
|
|
|
const data = this._bagList[start + i];
|
|
|
|
|
if (!data) {
|
|
|
|
|
item.active = false;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
item.active = true;
|
|
|
|
|
const comp = item.getComponent(shenjiangItem) || item.addComponent(shenjiangItem);
|
|
|
|
|
comp.setData(data);
|
|
|
|
|
}
|
|
|
|
|
this._updateBagPageNav();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 隐藏指定武器格子的 newWeapon 角标 */
|
|
|
|
|
private _hideBagNewWeaponTag(equipId: number): void {
|
|
|
|
|
const id = Math.floor(Number(equipId) || 0);
|
|
|
|
|
if (id <= 0) return;
|
|
|
|
|
for (let i = 0; i < this._bagItems.length; i++) {
|
|
|
|
|
const comp = this._bagItems[i]?.getComponent(shenjiangItem);
|
|
|
|
|
if ((comp?.data?.equipId | 0) !== id) continue;
|
|
|
|
|
comp.hideNewWeaponTag();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 隐藏全部武器库格子 newWeapon 角标 */
|
|
|
|
|
private _hideAllBagNewWeaponTags(): void {
|
|
|
|
|
for (let i = 0; i < this._bagItems.length; i++) {
|
|
|
|
|
this._bagItems[i]?.getComponent(shenjiangItem)?.hideNewWeaponTag();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 保证 ui_content 内有足够格子节点 */
|
|
|
|
|
private _ensureBagItemNodes(): void {
|
|
|
|
|
const root = this.ui_content;
|
|
|
|
|
const tpl = this._bagItemTpl;
|
|
|
|
|
if (!root?.isValid || !tpl?.isValid) {
|
|
|
|
|
console.log(LOG, 'ui_content 或武器格子模板缺失');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!this._bagItemBuilt) {
|
|
|
|
|
this._bagItems = [tpl];
|
|
|
|
|
this._bagItemBuilt = true;
|
|
|
|
|
}
|
|
|
|
|
const layout = root.getComponent(Layout);
|
|
|
|
|
if (layout) layout.enabled = false;
|
|
|
|
|
while (this._bagItems.length < BAG_PAGE_SIZE) {
|
|
|
|
|
const n = instantiate(tpl);
|
|
|
|
|
if (!n?.isValid || !root.isValid) break;
|
|
|
|
|
n.parent = root;
|
|
|
|
|
this._bagItems.push(n);
|
|
|
|
|
}
|
|
|
|
|
if (layout?.isValid) {
|
|
|
|
|
layout.enabled = true;
|
|
|
|
|
layout.updateLayout();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 刷新页码(翻页按钮始终显示,边界由点击逻辑拦截) */
|
|
|
|
|
private _updateBagPageNav(): void {
|
|
|
|
|
if (this.lb_page) {
|
|
|
|
|
this.lb_page.string = `${this._bagPageIndex + 1}/${this._bagPageCount}`;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 打开页:空格一次铺齐,预制体并行预取,分帧 instantiate 挂体 */
|
|
|
|
|
private _beginFill(): void {
|
|
|
|
|
this._stopFillFlow();
|
|
|
|
|
this._fillToken++;
|
|
|
|
|
this._fillAlive = true;
|
|
|
|
|
this._heroPrefabPending = 0;
|
|
|
|
|
this._fillHeroes = gg.equip.getNormalHeroList().slice().sort(this._sortHeroById);
|
|
|
|
|
this._heroCloneIndex = 0;
|
|
|
|
|
this._heroMetaIndex = 0;
|
|
|
|
|
this._bagFillIndex = 0;
|
|
|
|
|
for (let i = 0; i < this._heroItems.length; i++) {
|
|
|
|
|
const item = this._heroItems[i];
|
|
|
|
|
if (!item?.isValid) continue;
|
|
|
|
|
item['weaponPopped'] = false;
|
|
|
|
|
}
|
|
|
|
|
this._bagList = gg.equip.getBagDisplayList();
|
|
|
|
|
const pageCount = this._bagList.length > 0
|
|
|
|
|
? Math.ceil(this._bagList.length / BAG_PAGE_SIZE)
|
|
|
|
|
: 1;
|
|
|
|
|
this._bagPageCount = pageCount;
|
|
|
|
|
if (this._bagPageIndex >= pageCount) {
|
|
|
|
|
this._bagPageIndex = Math.max(0, pageCount - 1);
|
|
|
|
|
}
|
|
|
|
|
this._updateBagPageNav();
|
|
|
|
|
this._prefetchAllHeroPrefabs();
|
|
|
|
|
this._onFillPump();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 分帧泵:格子一次铺齐,缓存命中的神将体立刻挂上 */
|
|
|
|
|
private readonly _onFillPump = (): void => {
|
|
|
|
|
this._pumpScheduled = false;
|
|
|
|
|
if (!this._canFill()) return;
|
|
|
|
|
if (this._heroCloneIndex < this._fillHeroes.length) {
|
|
|
|
|
this._cloneHeroStep();
|
|
|
|
|
}
|
|
|
|
|
if (this._heroMetaIndex < this._heroCloneIndex) {
|
|
|
|
|
this._bindHeroMetaStep();
|
|
|
|
|
}
|
|
|
|
|
this._applyHeroGridLayout();
|
|
|
|
|
this._kickHeroMounts();
|
|
|
|
|
if (this._bagItems.length < BAG_PAGE_SIZE && this._bagFillIndex < BAG_PAGE_SIZE) {
|
|
|
|
|
this._cloneBagStep();
|
|
|
|
|
}
|
|
|
|
|
if (this._bagFillIndex < BAG_PAGE_SIZE) {
|
|
|
|
|
this._fillBagStep();
|
|
|
|
|
}
|
|
|
|
|
if (this._needFillPump()) this._schedulePump();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/** 是否还有打开页填充任务 */
|
|
|
|
|
private _needFillPump(): boolean {
|
|
|
|
|
if (this._heroCloneIndex < this._fillHeroes.length) return true;
|
|
|
|
|
if (this._heroMetaIndex < this._fillHeroes.length) return true;
|
|
|
|
|
if (this._bagFillIndex < BAG_PAGE_SIZE) return true;
|
|
|
|
|
if (this._heroPrefabPending > 0) return true;
|
|
|
|
|
const bundle = assetManager.getBundle(GBundle.Shenjiang);
|
|
|
|
|
for (let i = 0; i < this._fillHeroes.length; i++) {
|
|
|
|
|
const hero = this._fillHeroes[i];
|
|
|
|
|
const item = this._heroItems[i];
|
|
|
|
|
if (!hero || !item?.isValid) continue;
|
|
|
|
|
if ((Number(item['heroFilledId']) || 0) === hero.id) continue;
|
|
|
|
|
const full = this._heroPrefabPath(hero);
|
|
|
|
|
if (full && bundle?.get(full, Prefab)?.isValid) return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 打开页填充是否仍可写节点 */
|
|
|
|
|
private _canFill(): boolean {
|
|
|
|
|
return this._fillAlive && this.isValid && !!this.node?.isValid && this.node.activeInHierarchy;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 预约下一帧继续填充 */
|
|
|
|
|
private _schedulePump(): void {
|
|
|
|
|
if (!this._canFill() || this._pumpScheduled) return;
|
|
|
|
|
if (!this._flow) this._flow = new FrameFlowHost();
|
|
|
|
|
this._pumpScheduled = true;
|
|
|
|
|
this._flow.waitFrames(1, this._onFillPump);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 停掉打开页分帧 */
|
|
|
|
|
private _stopFillFlow(): void {
|
|
|
|
|
this._pumpScheduled = false;
|
|
|
|
|
this._heroPrefabPending = 0;
|
|
|
|
|
this._flow?.dispose();
|
|
|
|
|
this._flow = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 克隆空神将格(模板只作拷贝源,不挂体) */
|
|
|
|
|
private _cloneHeroStep(): void {
|
|
|
|
|
const target = this._fillHeroes.length;
|
|
|
|
|
const tpl = this._heroTpl;
|
|
|
|
|
const parent = this._heroRoot;
|
|
|
|
|
if (!tpl?.isValid || !parent?.isValid) {
|
|
|
|
|
this._heroCloneIndex = target;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const layout = parent.getComponent(Layout);
|
|
|
|
|
if (layout) layout.enabled = false;
|
|
|
|
|
while (this._heroItems.length < target) {
|
|
|
|
|
const n = instantiate(tpl);
|
|
|
|
|
if (!n?.isValid || !parent.isValid) break;
|
|
|
|
|
n.parent = parent;
|
|
|
|
|
n.active = false;
|
|
|
|
|
this._hideHeroWeapon(n);
|
|
|
|
|
this._heroItems.push(n);
|
|
|
|
|
}
|
|
|
|
|
this._heroCloneIndex = Math.min(this._heroItems.length, target);
|
|
|
|
|
for (let i = target; i < this._heroItems.length; i++) {
|
|
|
|
|
const extra = this._heroItems[i];
|
|
|
|
|
if (extra?.isValid) extra.active = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 按预制体 Grid:4 列、横向贴齐、纵向 spacingY */
|
|
|
|
|
private _setupHeroGridLayout(): void {
|
|
|
|
|
const root = this._heroRoot;
|
|
|
|
|
if (!root?.isValid) return;
|
|
|
|
|
const layout = root.getComponent(Layout);
|
|
|
|
|
if (!layout) {
|
|
|
|
|
console.log(LOG, 'shenjiang 缺少 Layout');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
layout.type = Layout.Type.GRID;
|
|
|
|
|
layout.constraint = Layout.Constraint.FIXED_COL;
|
|
|
|
|
layout.constraintNum = 4;
|
|
|
|
|
layout.enabled = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 格子显隐变化后刷新神将网格 */
|
|
|
|
|
private _applyHeroGridLayout(): void {
|
|
|
|
|
const layout = this._heroRoot?.getComponent(Layout);
|
|
|
|
|
if (!layout?.isValid) return;
|
|
|
|
|
layout.enabled = true;
|
|
|
|
|
layout.updateLayout();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 先写 Hero.id 和武器名条,武器 Spine 延后 */
|
|
|
|
|
private _bindHeroMetaStep(): void {
|
|
|
|
|
const end = this._heroCloneIndex;
|
|
|
|
|
for (let i = this._heroMetaIndex; i < end; i++) {
|
|
|
|
|
const item = this._heroItems[i];
|
|
|
|
|
const hero = this._fillHeroes[i];
|
|
|
|
|
if (!item?.isValid || !hero) continue;
|
|
|
|
|
item.active = true;
|
|
|
|
|
this._setHeroWearMask(item, false);
|
|
|
|
|
item['heroId'] = hero.id;
|
|
|
|
|
this._applyHeroWeaponNameBar(item, hero.id);
|
|
|
|
|
this._hideHeroWeapon(item);
|
|
|
|
|
}
|
|
|
|
|
this._heroMetaIndex = end;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 克隆一批武器库格 */
|
|
|
|
|
private _cloneBagStep(): void {
|
|
|
|
|
const root = this.ui_content;
|
|
|
|
|
const tpl = this._bagItemTpl;
|
|
|
|
|
if (!root?.isValid || !tpl?.isValid) {
|
|
|
|
|
console.log(LOG, 'ui_content 或武器格子模板缺失');
|
|
|
|
|
this._bagFillIndex = BAG_PAGE_SIZE;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!this._bagItemBuilt) {
|
|
|
|
|
this._bagItems = [tpl];
|
|
|
|
|
this._bagItemBuilt = true;
|
|
|
|
|
}
|
|
|
|
|
const want = BAG_PAGE_SIZE;
|
|
|
|
|
const layout = root.getComponent(Layout);
|
|
|
|
|
if (layout) layout.enabled = false;
|
|
|
|
|
while (this._bagItems.length < want) {
|
|
|
|
|
const n = instantiate(tpl);
|
|
|
|
|
if (!n?.isValid || !root.isValid) break;
|
|
|
|
|
n.parent = root;
|
|
|
|
|
this._bagItems.push(n);
|
|
|
|
|
}
|
|
|
|
|
if (layout?.isValid) {
|
|
|
|
|
layout.enabled = true;
|
|
|
|
|
layout.updateLayout();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 填充一批当前页武器库格 */
|
|
|
|
|
private _fillBagStep(): void {
|
|
|
|
|
const start = this._bagPageIndex * BAG_PAGE_SIZE;
|
|
|
|
|
const end = BAG_PAGE_SIZE;
|
|
|
|
|
for (let i = this._bagFillIndex; i < end; i++) {
|
|
|
|
|
const item = this._bagItems[i];
|
|
|
|
|
if (!item?.isValid) continue;
|
|
|
|
|
const data = this._bagList[start + i];
|
|
|
|
|
if (!data) {
|
|
|
|
|
item.active = false;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
item.active = true;
|
|
|
|
|
const comp = item.getComponent(shenjiangItem) || item.addComponent(shenjiangItem);
|
|
|
|
|
comp.setData(data);
|
|
|
|
|
}
|
|
|
|
|
this._bagFillIndex = end;
|
|
|
|
|
this._updateBagPageNav();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 缓存里有预制体则立刻挂上 */
|
|
|
|
|
private _kickHeroMounts(): void {
|
|
|
|
|
if (!this._canFill()) return;
|
|
|
|
|
for (let i = 0; i < this._heroMetaIndex; i++) {
|
|
|
|
|
const item = this._heroItems[i];
|
|
|
|
|
const hero = this._fillHeroes[i];
|
|
|
|
|
if (!item?.isValid || !hero) continue;
|
|
|
|
|
this._tryMountHeroFromCache(item, hero);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 从已加载预制体挂神将体;未就绪返回 false。
|
|
|
|
|
* @param item 神将格
|
|
|
|
|
* @param hero 英雄表
|
|
|
|
|
*/
|
|
|
|
|
private _tryMountHeroFromCache(item: Node, hero: ITableHero): boolean {
|
|
|
|
|
const slot = item.getChildByName('hero');
|
|
|
|
|
if (!slot?.isValid || !slot.children) return false;
|
|
|
|
|
const kids = slot.children;
|
|
|
|
|
if ((Number(item['heroFilledId']) || 0) === hero.id && kids && kids.length > 0) {
|
|
|
|
|
if (!item['weaponPopped']) {
|
|
|
|
|
this._applyHeroWeaponNode(item, hero.id);
|
|
|
|
|
this._playHeroWeaponPop(item);
|
|
|
|
|
item['weaponPopped'] = true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
const full = this._heroPrefabPath(hero);
|
|
|
|
|
if (!full) return false;
|
|
|
|
|
const bundle = assetManager.getBundle(GBundle.Shenjiang);
|
|
|
|
|
const prefab = bundle?.get(full, Prefab);
|
|
|
|
|
if (!prefab?.isValid) return false;
|
|
|
|
|
const body = instantiate(prefab);
|
|
|
|
|
if (!body?.isValid || !this._canFill() || !slot.isValid) {
|
|
|
|
|
this._freeDisplay(body);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
this._disableLogicScripts(body);
|
|
|
|
|
this._mountDisplay(slot, body);
|
|
|
|
|
if (!body.isValid || !slot.isValid) return false;
|
|
|
|
|
this._applySpine(body, '', '待机');
|
|
|
|
|
item['heroFilledId'] = hero.id;
|
|
|
|
|
this._applyHeroWeaponNode(item, hero.id);
|
|
|
|
|
this._playHeroWeaponPop(item);
|
|
|
|
|
item['weaponPopped'] = true;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** shenjiang/prefab/{Hero.res} */
|
|
|
|
|
private _heroPrefabPath(hero: ITableHero): string {
|
|
|
|
|
const resName = String(hero.res || hero.name || '').trim();
|
|
|
|
|
if (!resName) {
|
|
|
|
|
console.log(LOG, 'Hero.res 为空', hero.id);
|
|
|
|
|
return '';
|
|
|
|
|
}
|
|
|
|
|
return `${GPath.UIPrefab}${resName}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 打开页前把 shenjiang/prefab 打进缓存,避免打开后再排队 */
|
|
|
|
|
private _warmHeroPrefabs(): void {
|
|
|
|
|
const existed = assetManager.getBundle(GBundle.Shenjiang);
|
|
|
|
|
if (existed) {
|
|
|
|
|
existed.loadDir('prefab', Prefab, this._onWarmPrefabDir);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
assetManager.loadBundle(GBundle.Shenjiang, this._onWarmBundle);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 预热时分包就绪 */
|
|
|
|
|
private readonly _onWarmBundle = (err: Error | null, bundle: AssetManager.Bundle | null): void => {
|
|
|
|
|
if (err || !bundle) {
|
|
|
|
|
if (err) console.error(LOG, 'bundle 加载失败', GBundle.Shenjiang, String(err));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!this.isValid) return;
|
|
|
|
|
bundle.loadDir('prefab', Prefab, this._onWarmPrefabDir);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/** 预热目录结束,若页面已在填则立刻挂体 */
|
|
|
|
|
private readonly _onWarmPrefabDir = (err: Error | null): void => {
|
|
|
|
|
if (err) console.error(LOG, '神将预制体预热失败', String(err));
|
|
|
|
|
if (!this._canFill()) return;
|
|
|
|
|
this._kickHeroMounts();
|
|
|
|
|
if (this._needFillPump()) this._schedulePump();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/** 并行预取全部神将展示预制体,不 instantiate */
|
|
|
|
|
private _prefetchAllHeroPrefabs(): void {
|
|
|
|
|
const existed = assetManager.getBundle(GBundle.Shenjiang);
|
|
|
|
|
if (existed) {
|
|
|
|
|
this._prefetchPrefabsInBundle(existed);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
this._heroPrefabPending++;
|
|
|
|
|
assetManager.loadBundle(GBundle.Shenjiang, this._onShenjiangBundle);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** shenjiang 分包就绪后继续预取预制体 */
|
|
|
|
|
private readonly _onShenjiangBundle = (err: Error | null, bundle: AssetManager.Bundle | null): void => {
|
|
|
|
|
if (this._heroPrefabPending > 0) this._heroPrefabPending--;
|
|
|
|
|
if (err) console.error(LOG, 'bundle 加载失败', GBundle.Shenjiang, String(err));
|
|
|
|
|
if (err || !bundle || !this._canFill()) return;
|
|
|
|
|
this._prefetchPrefabsInBundle(bundle);
|
|
|
|
|
this._kickHeroMounts();
|
|
|
|
|
if (this._needFillPump()) this._schedulePump();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 在已加载分包内预取神将预制体。
|
|
|
|
|
* @param bundle shenjiang
|
|
|
|
|
*/
|
|
|
|
|
private _prefetchPrefabsInBundle(bundle: AssetManager.Bundle): void {
|
|
|
|
|
for (let i = 0; i < this._fillHeroes.length; i++) {
|
|
|
|
|
const full = this._heroPrefabPath(this._fillHeroes[i]);
|
|
|
|
|
if (!full) continue;
|
|
|
|
|
if (bundle.get(full, Prefab)?.isValid) continue;
|
|
|
|
|
this._heroPrefabPending++;
|
|
|
|
|
bundle.load(full, Prefab, this._onPrefetchAsset);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 单个预制体预取结束,立刻尝试挂体 */
|
|
|
|
|
private readonly _onPrefetchAsset = (err: Error | null): void => {
|
|
|
|
|
if (this._heroPrefabPending > 0) this._heroPrefabPending--;
|
|
|
|
|
if (err) console.error(LOG, '预制体预取失败', String(err));
|
|
|
|
|
if (!this._canFill()) return;
|
|
|
|
|
this._kickHeroMounts();
|
|
|
|
|
if (this._needFillPump()) this._schedulePump();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/** 隐藏神将武器,待冒出 */
|
|
|
|
|
private _hideHeroWeapon(item: Node): void {
|
|
|
|
|
const weaponNode = item.getChildByName('weaponNode');
|
|
|
|
|
if (!weaponNode?.isValid) return;
|
|
|
|
|
Tween.stopAllByTarget(weaponNode);
|
|
|
|
|
const uo = weaponNode.getComponent(UIOpacity);
|
|
|
|
|
if (uo) Tween.stopAllByTarget(uo);
|
|
|
|
|
weaponNode.active = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 已展示武器:渐显 + backOut 回弹冒出 */
|
|
|
|
|
private _playHeroWeaponPop(item: Node): void {
|
|
|
|
|
const weaponNode = item.getChildByName('weaponNode');
|
|
|
|
|
if (!weaponNode?.isValid || !weaponNode.active) return;
|
|
|
|
|
Tween.stopAllByTarget(weaponNode);
|
|
|
|
|
const uo = weaponNode.getComponent(UIOpacity) || weaponNode.addComponent(UIOpacity);
|
|
|
|
|
Tween.stopAllByTarget(uo);
|
|
|
|
|
uo.opacity = 0;
|
|
|
|
|
weaponNode.angle = -14;
|
|
|
|
|
weaponNode.setScale(WEAPON_POP_SCALE_FROM, WEAPON_POP_SCALE_FROM, 1);
|
|
|
|
|
tween(uo).to(WEAPON_POP_FADE_SEC, { opacity: 255 }).start();
|
|
|
|
|
tween(weaponNode).to(WEAPON_POP_SCALE_SEC, { scale: new Vec3(1, 1, 1), angle: 0 }, { easing: 'backOut' }).start();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 关闭页时停掉全部武器冒出 */
|
|
|
|
|
private _stopAllWeaponPops(): void {
|
|
|
|
|
for (let i = 0; i < this._heroItems.length; i++) {
|
|
|
|
|
const item = this._heroItems[i];
|
|
|
|
|
if (item?.isValid) this._hideHeroWeapon(item);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 神将网格按 Hero.id 升序 */
|
|
|
|
|
private _sortHeroById(a: ITableHero, b: ITableHero): number {
|
|
|
|
|
return a.id - b.id;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 穿戴武器名条:nameBg=icons/weapon/武器名字底0~4(未穿成品为 0)
|
|
|
|
|
* name=icons/weapon/{武器名};未穿成品显示 icons/weapon/无
|
|
|
|
|
*/
|
|
|
|
|
private _applyHeroWeaponNameBar(item: Node, heroId: number): void {
|
|
|
|
|
const nameBg = item.getChildByName('nameBg');
|
|
|
|
|
if (!nameBg?.isValid) {
|
|
|
|
|
console.log(LOG, 'nameBg 缺失', item.name);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
nameBg.active = true;
|
|
|
|
|
|
|
|
|
|
const worn = gg.equip.getHeroWearEquip(heroId);
|
|
|
|
|
const bgIndex = worn ? this._weaponNameBgIndex(Number(worn.quality), heroId, worn.id) : 0;
|
|
|
|
|
this._setWeaponIconSprite(nameBg, `weapon/武器名字底${bgIndex}`);
|
|
|
|
|
|
|
|
|
|
const nameNode = nameBg.getChildByName('name');
|
|
|
|
|
if (!worn) {
|
|
|
|
|
this._setWeaponIconSprite(nameNode, WEAPON_NAME_EMPTY_SPRITE);
|
|
|
|
|
this._applyHeroWeaponNameScale(nameNode, '');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const weaponName = String(worn.name ?? '').trim();
|
|
|
|
|
if (!weaponName) {
|
|
|
|
|
console.log(LOG, '武器名缺失', heroId, worn.id);
|
|
|
|
|
this._clearWeaponIconSprite(nameNode);
|
|
|
|
|
this._applyHeroWeaponNameScale(nameNode, '');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (nameNode?.isValid) {
|
|
|
|
|
this._setWeaponIconSprite(nameNode, `weapon/${weaponName}`);
|
|
|
|
|
this._applyHeroWeaponNameScale(nameNode, weaponName);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** nameBg 内 name 节点:不少于 5 字缩放 0.8,否则 1 */
|
|
|
|
|
private _applyHeroWeaponNameScale(nameNode: Node | null, weaponName: string): void {
|
|
|
|
|
if (!nameNode?.isValid) return;
|
|
|
|
|
const scale = weaponName.length >= WEAPON_NAME_LONG_LEN
|
|
|
|
|
? HERO_NAMEBG_NAME_SCALE_LONG
|
|
|
|
|
: HERO_NAMEBG_NAME_SCALE_NORMAL;
|
|
|
|
|
nameNode.setScale(scale, scale, 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* weaponNode:按 Weapon.type 显隐 type1~4,Spine 皮肤为当前展示武器 skin
|
|
|
|
|
* 未穿成品时武器 id 为 0(同 getHeroDisplayWeaponId)
|
|
|
|
|
*/
|
|
|
|
|
private _applyHeroWeaponNode(item: Node, heroId: number): void {
|
|
|
|
|
const weaponNode = item.getChildByName('weaponNode');
|
|
|
|
|
if (!weaponNode?.isValid) {
|
|
|
|
|
console.log(LOG, 'weaponNode 缺失', item.name);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const equipId = gg.equip.getHeroDisplayWeaponId(heroId);
|
|
|
|
|
const weapon = equipId > 0 ? gg.equip.getEquip(equipId) : null;
|
|
|
|
|
if (!weapon) {
|
|
|
|
|
weaponNode.active = false;
|
|
|
|
|
item['weaponDisplay'] = null;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const weaponType = Number(weapon.type) | 0;
|
|
|
|
|
const skin = String(weapon.skin ?? '').trim();
|
|
|
|
|
if (weaponType < 1 || weaponType > 4) {
|
|
|
|
|
console.log(LOG, '展示武器 type 无效', heroId, equipId, weaponType);
|
|
|
|
|
weaponNode.active = false;
|
|
|
|
|
item['weaponDisplay'] = null;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!skin) console.log(LOG, '展示武器 skin 缺失', heroId, equipId);
|
|
|
|
|
weaponNode.active = true;
|
|
|
|
|
const uo = weaponNode.getComponent(UIOpacity) || weaponNode.addComponent(UIOpacity);
|
|
|
|
|
uo.opacity = 0;
|
|
|
|
|
weaponNode.setScale(WEAPON_POP_SCALE_FROM, WEAPON_POP_SCALE_FROM, 1);
|
|
|
|
|
const shown = this._applyWeaponSpineGroup(weaponNode, weaponType, skin);
|
|
|
|
|
item['weaponDisplay'] = shown;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 按武器类型显隐 type1~4,并设置 Spine 皮肤;返回当前展示的 type 节点 */
|
|
|
|
|
private _applyWeaponSpineGroup(parent: Node, weaponType: number, skin: string): Node | null {
|
|
|
|
|
let shown: Node | null = null;
|
|
|
|
|
for (let t = 1; t <= 4; t++) {
|
|
|
|
|
const typeNode = parent.getChildByName(`type${t}`);
|
|
|
|
|
if (!typeNode) continue;
|
|
|
|
|
const active = t === weaponType;
|
|
|
|
|
typeNode.active = active;
|
|
|
|
|
if (!active) continue;
|
|
|
|
|
shown = typeNode;
|
|
|
|
|
this._applyWeaponSpine(typeNode, skin);
|
|
|
|
|
}
|
|
|
|
|
if (!shown) console.log(LOG, 'type 节点缺失', parent.name, weaponType);
|
|
|
|
|
return shown;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 设置单个武器 Spine 皮肤与待机 */
|
|
|
|
|
private _applyWeaponSpine(typeNode: Node, skin: string): void {
|
|
|
|
|
const skel = this._findSkeleton(typeNode);
|
|
|
|
|
if (!skel) {
|
|
|
|
|
console.log(LOG, '武器 Spine 缺失', typeNode.name);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
this._applySpine(typeNode, skin, '待机');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 已穿戴成品品质 → 武器名字底 0~4(白/绿/蓝/紫/橙) */
|
|
|
|
|
private _weaponNameBgIndex(quality: number, heroId: number, equipId: number): number {
|
|
|
|
|
const q = Number(quality) | 0;
|
|
|
|
|
if (q < 0 || q > 4) {
|
|
|
|
|
console.log(LOG, '已穿戴武器品质无效', heroId, equipId, q);
|
|
|
|
|
}
|
|
|
|
|
return q;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** icons/weapon 下图 */
|
|
|
|
|
private _setWeaponIconSprite(node: Node | null, path: string, onLoaded?: () => void): void {
|
|
|
|
|
if (!node?.isValid) return;
|
|
|
|
|
if (!path) {
|
|
|
|
|
this._clearWeaponIconSprite(node);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
node.active = true;
|
|
|
|
|
if (!node.getComponent(Sprite)) node.addComponent(Sprite);
|
|
|
|
|
node.getOrAddComponent(SpriteLoad).setSprite(path, GBundle.Items, onLoaded ?? null);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 清除武器名图(未穿戴) */
|
|
|
|
|
private _clearWeaponIconSprite(node: Node | null): void {
|
|
|
|
|
if (!node?.isValid) return;
|
|
|
|
|
node.active = false;
|
|
|
|
|
node.getComponent(SpriteLoad)?.clear();
|
|
|
|
|
const sp = node.getComponent(Sprite);
|
|
|
|
|
if (sp) sp.spriteFrame = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _mountDisplay(slot: Node, child: Node, clear = true): void {
|
|
|
|
|
if (!this._canFill() || !slot?.isValid || !child?.isValid || !slot.children) {
|
|
|
|
|
this._freeDisplay(child);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (clear) this._clearHeroSlot(slot);
|
|
|
|
|
if (!this._canFill() || !slot.isValid || !child.isValid || !slot.children) {
|
|
|
|
|
this._freeDisplay(child);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
child.parent = slot;
|
|
|
|
|
this._setLayerRecursive(child, slot.layer);
|
|
|
|
|
child.setPosition(0, 0, 0);
|
|
|
|
|
child.setScale(1, 1, 1);
|
|
|
|
|
child.angle = 0;
|
|
|
|
|
child.active = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _clearHeroSlot(slot: Node): void {
|
|
|
|
|
if (!slot?.isValid) return;
|
|
|
|
|
const kids = slot.children;
|
|
|
|
|
if (!kids || kids.length <= 0) return;
|
|
|
|
|
const copy: Node[] = [];
|
|
|
|
|
for (let i = 0; i < kids.length; i++) copy.push(kids[i]);
|
|
|
|
|
for (let i = 0; i < copy.length; i++) this._freeDisplay(copy[i]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _freeDisplay(node: Node | null | undefined): void {
|
|
|
|
|
if (!node?.isValid) return;
|
|
|
|
|
if (node.parent) node.removeFromParent();
|
|
|
|
|
if (node.isValid) node.destroy();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _disableLogicScripts(node: Node): void {
|
|
|
|
|
if (!node?.isValid) return;
|
|
|
|
|
const comps = node.getComponents(Component);
|
|
|
|
|
for (let i = 0; i < comps.length; i++) {
|
|
|
|
|
const c = comps[i];
|
|
|
|
|
if (c instanceof UITransform || c instanceof Sprite || c instanceof sp.Skeleton) continue;
|
|
|
|
|
if (c instanceof SpriteLoad || c instanceof UIOpacity) continue;
|
|
|
|
|
c.enabled = false;
|
|
|
|
|
}
|
|
|
|
|
const kids = node.children;
|
|
|
|
|
if (!kids) return;
|
|
|
|
|
for (let i = 0; i < kids.length; i++) {
|
|
|
|
|
this._disableLogicScripts(kids[i]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 拖拽幽灵不参与点击 */
|
|
|
|
|
private _ghostNoHit(): boolean {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _findSkeleton(node: Node | null | undefined): sp.Skeleton | null {
|
|
|
|
|
if (!node?.isValid) return null;
|
|
|
|
|
return node.getComponent(sp.Skeleton) ?? node.getComponentInChildren(sp.Skeleton) ?? null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _applySpine(node: Node, skin: string, anim: string): void {
|
|
|
|
|
const skel = this._findSkeleton(node);
|
|
|
|
|
if (!skel) return;
|
|
|
|
|
if (skel.skeletonData) ensureSpineSkeletonDataRuntime(skel.skeletonData);
|
|
|
|
|
if (skin) {
|
|
|
|
|
try {
|
|
|
|
|
skel.setSkin(skin);
|
|
|
|
|
skel.setSlotsToSetupPose();
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.log(LOG, `setSkin failed "${skin}"`, String(e));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const clip = anim || '待机';
|
|
|
|
|
if (!clip) return;
|
|
|
|
|
try {
|
|
|
|
|
skel.setAnimation(0, clip, true);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.log(LOG, `setAnimation failed "${clip}"`, String(e));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _setLayerRecursive(node: Node, layer: number): void {
|
|
|
|
|
if (!node?.isValid) return;
|
|
|
|
|
node.layer = layer;
|
|
|
|
|
const kids = node.children;
|
|
|
|
|
if (!kids) return;
|
|
|
|
|
for (let i = 0; i < kids.length; i++) {
|
|
|
|
|
const child = kids[i];
|
|
|
|
|
if (child?.isValid) this._setLayerRecursive(child, layer);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|