import { _decorator, Button, Color, Component, Label, Node, sp, Sprite } from 'cc'; import { SpriteLoad } from 'db://assets/mx/components/SpriteLoad'; import { ensureSpineSkeletonDataRuntime } from 'db://assets/mx/module/lang/spineRuntimePatch'; import { SpineBatch } from 'db://assets/resources/multSpines/MultSpine'; import { GBundle } from '../../game/ConfigRes'; import type { IMingjiangkuBagEntry } from '../../manager/EquipDataManager'; import { RedDotView } from 'db://assets/mx/module/redDot/RedDotView'; const { ccclass, property } = _decorator; const LOG = '[shenjiangItem]'; const SPINE_DIM = new Color(0x99, 0x99, 0x99, 255); const SPINE_NORMAL = new Color(255, 255, 255, 255); const WEAPON_SPINE_ANIM = '待机'; /** MultSpine 置灰:类型 1 + 插值 0.999(见 MultSpine.ts / Mult-spine.effect) */ const SPINE_GRAY_SHADER = 1.999; /** 武器名不少于该字数时缩小 name 图(含 5 字) */ const WEAPON_NAME_LONG_LEN = 5; /** openNode.name:未超长=1,超长=0.8 */ const OPEN_NAME_SCALE_NORMAL = 1; const OPEN_NAME_SCALE_LONG = 0.8; /** suipianNode.name:未超长=0.7,超长=0.56 */ const SUIPIAN_NAME_SCALE_NORMAL = 0.7; const SUIPIAN_NAME_SCALE_LONG = 0.56; type SkelShader = sp.Skeleton & { shaderVar?: number }; /** * 神将武器库格子 * quality:icons/weapon/武器底0~4 * openNode/suipianNode:name=icons/weapon/{name};type1~4 按 Weapon.type 显隐,Spine 皮肤为 Weapon.skin */ @ccclass('shenjiangItem') export class shenjiangItem extends Component { /** 碎片节点(含 type1~4、lb_suipian) */ @property(Node) suipianNode: Node = null; /** 当前格子数据 */ private _data: IMingjiangkuBagEntry | null = null; /** 当前格子数据 */ get data(): IMingjiangkuBagEntry | null { return this._data; } /** 成品拖拽图标节点(openNode 下当前 type 的 Spine) */ getDragIconNode(): Node | null { const openNode = this.node.getChildByName('openNode'); if (!openNode?.activeInHierarchy) return null; const weapon = this._data?.equipId ? gg.equip.getEquip(this._data.equipId) : null; const type = Number(weapon?.type) | 0; if (type >= 1 && type <= 4) { return openNode.getChildByName(`type${type}`) ?? null; } return null; } /** 绑定格子数据并刷新展示 */ setData(data: IMingjiangkuBagEntry): void { this._data = data; if (!this.suipianNode) this.suipianNode = this.node.getChildByName('suipianNode'); const openNode = this.node.getChildByName('openNode'); const qualityNode = this.node.getChildByName('quality'); const isFrag = data.kind === 'fragment'; const weapon = data.equipId > 0 ? gg.equip.getEquip(data.equipId) : null; if (!weapon) console.log(LOG, '武器表缺失', data.equipId); const weaponType = Number(weapon?.type) | 0; const skin = String(weapon?.skin ?? '').trim(); const weaponName = String(weapon?.name ?? '').trim(); if (weaponType < 1 || weaponType > 4) console.warn(LOG, 'type 无效', data.equipId, weaponType); if (!skin) console.warn(LOG, 'skin 缺失', data.equipId); if (!weaponName) console.warn(LOG, 'name 缺失', data.equipId); if (openNode) openNode.active = !isFrag; if (this.suipianNode) this.suipianNode.active = isFrag; const bgIndex = this._equipQualityBgIndex(Number(weapon?.quality), data.equipId); this._setSprite(qualityNode, `weapon/武器底${bgIndex}`); const longName = weaponName.length >= WEAPON_NAME_LONG_LEN; const openScale = longName ? OPEN_NAME_SCALE_LONG : OPEN_NAME_SCALE_NORMAL; const suipianScale = longName ? SUIPIAN_NAME_SCALE_LONG : SUIPIAN_NAME_SCALE_NORMAL; if (!isFrag && openNode) { this._setWeaponNameNode(openNode, weaponName, openScale); this._applyWeaponSpineGroup(openNode, weaponType, skin, false); this._refreshNewWeaponTag(openNode, data.equipId, !isFrag); const lbNum = openNode.getChildByName('numBg')?.getChildByName('lb_weaponNum')?.getComponent(Label); if (lbNum) lbNum.string = `${Math.max(0, data.count)}`; } if (isFrag && this.suipianNode) { this._setWeaponNameNode(this.suipianNode, weaponName, suipianScale); this._applyWeaponSpineGroup(this.suipianNode, weaponType, skin, true); const lbFrag = this.suipianNode.getChildByName('lb_suipian')?.getComponent(Label); const need = Number(data.need) > 0 ? Number(data.need) : gg.equip.getComposeNeed(data.equipId); if (need <= 0) console.log(LOG, '合成需求缺失', data.equipId); if (lbFrag) lbFrag.string = `${data.count}/${Math.max(0, need)}`; } const btns = this.node.getComponentsInChildren(Button); for (let i = 0; i < btns.length; i++) { btns[i].interactable = false; btns[i].enabled = false; } RedDotView.setLocal(this.node, false); } /** 隐藏 openNode 新武器角标 */ hideNewWeaponTag(): void { const openNode = this.node.getChildByName('openNode'); this._refreshNewWeaponTag(openNode, 0, false); } /** openNode/newWeapon:新成品武器角标 */ private _refreshNewWeaponTag(openNode: Node | null, equipId: number, show: boolean): void { const tag = openNode?.getChildByName('newWeapon'); if (!tag?.isValid) return; tag.active = show && gg.equip.isNewWeaponBadgeVisible(equipId); } /** 成品可用数量(库存-已穿戴) */ setCount(count: number): void { if (this._data) this._data.count = Math.max(0, count | 0); if (this._data?.kind === 'fragment') return; const lbNum = this.node.getChildByName('openNode')?.getChildByName('numBg')?.getChildByName('lb_weaponNum')?.getComponent(Label); if (lbNum) lbNum.string = `${this._data?.count ?? 0}`; } /** 设置武器名图并缩放 name 节点(贴图异步加载完成后会再设一次缩放) */ private _setWeaponNameNode(parent: Node | null, weaponName: string, scale: number): void { const nameNode = parent?.getChildByName('name'); if (!nameNode?.isValid) return; const applyScale = (): void => { nameNode.setScale(scale, scale, 1); }; const path = weaponName ? `weapon/${weaponName}` : ''; if (!path) { nameNode.active = false; applyScale(); return; } nameNode.active = true; if (!nameNode.getComponent(Sprite)) nameNode.addComponent(Sprite); nameNode.getOrAddComponent(SpriteLoad).setSprite(path, GBundle.Items, () => { applyScale(); }); applyScale(); } /** 按武器类型显隐 type1~4,并设置 Spine 皮肤 */ private _applyWeaponSpineGroup(parent: Node, weaponType: number, skin: string, dimmed: boolean): void { let matched = false; for (let t = 1; t <= 4; t++) { const typeNode = parent.getChildByName(`type${t}`); if (!typeNode) continue; const show = t === weaponType; typeNode.active = show; if (!show) continue; matched = true; this._applyWeaponSpine(typeNode, skin, dimmed); } if (!matched && weaponType >= 1 && weaponType <= 4) { console.log(LOG, 'type 节点缺失', parent.name, weaponType); } } /** 设置单个武器 Spine 皮肤与待机 */ private _applyWeaponSpine(typeNode: Node, skin: string, dimmed: boolean): void { const skel = typeNode.getComponent(sp.Skeleton) ?? typeNode.getComponentInChildren(sp.Skeleton); if (!skel) { console.log(LOG, 'Spine 缺失', typeNode.name); return; } if (skel.skeletonData) ensureSpineSkeletonDataRuntime(skel.skeletonData); if (skin) { try { skel.setSkin(skin); skel.setSlotsToSetupPose(); } catch (e) { console.log(LOG, 'setSkin 失败', skin, String(e)); } } try { skel.setAnimation(0, WEAPON_SPINE_ANIM, true); } catch (e) { console.log(LOG, 'setAnimation 失败', WEAPON_SPINE_ANIM, String(e)); } this._setSpineDimmed(skel, dimmed); } /** * Spine 置灰:MultSpine 合批走 shaderVar(1.999),否则走 skel.color(同 PanelShenjiang 神将提示) */ private _setSpineDimmed(skel: sp.Skeleton, dimmed: boolean): void { const s = skel as SkelShader; if (SpineBatch.active() && typeof s.shaderVar === 'number') { s.shaderVar = dimmed ? SPINE_GRAY_SHADER : 0; skel.color = SPINE_NORMAL; return; } s.shaderVar = 0; skel.color = dimmed ? SPINE_DIM : SPINE_NORMAL; } /** 成品品质 → 武器底 0~4(白/绿/蓝/紫/橙) */ private _equipQualityBgIndex(quality: number, equipId: number): number { const q = Number(quality) | 0; if (q < 0 || q > 4) { console.log(LOG, '品质无效', equipId, q); } return q; } private _setSprite(node: Node | null, path: string): void { if (!node?.isValid) return; if (!path) { node.active = false; return; } node.active = true; if (!node.getComponent(Sprite)) node.addComponent(Sprite); node.getOrAddComponent(SpriteLoad).setSprite(path, GBundle.Items); } }