import { Color, instantiate, Label, Layout, Node, Sprite } from 'cc'; import { SpriteLoad } from 'db://assets/mx/components/SpriteLoad'; import { GBundle, GPath } from '../../game/ConfigRes'; import { BattleBuffCarryStore } from '../../manager/battleBuff/BattleBuffCarryStore'; import { isBattleBuffPassiveBaseThunder, passiveBuffUsesBattleGameCd } from '../../manager/battleBuff/BattleBuffPassiveSkillUtil'; import { DailyShopKind, type DailyShopParsed } from '../../manager/DailyShopTypes'; import { DailyShopLotteryIcon, DailyShopOwnedDesc } from '../PopDailyShopLottery/DailyShopLotteryIcon'; import { AiHeadlessBagHost } from '../../battle/ai/runtime/AiHeadlessBagHost'; import { getBattleBuffBaseThunderHost } from '../../battle/combat/BattleBuffBaseThunderHost'; import type { AiActiveSkillUiSlot } from '../../battle/ai/runtime/AiActiveSkillHost'; import type { AiPassiveCdUiSlot } from '../../battle/ai/runtime/AiPassiveCdHost'; import { AiBattleBridge } from '../../battle/ai/bridge/AiBattleBridge'; import { applySkillCdMaskFillRange, BattleGameDailyShopSkillSlot, setupSkillCdMaskSprite } from './BattleGameDailyShopSkillView'; /** 被动名称描边(与主动同品质色) */ const PASSIVE_NAME_OUTLINE_HEX: Record = { 1: '#386c42', 2: '#285c6d', 3: '#623073', 4: '#734930', }; /** 敌方被动格:仅展示,不触发玩家侧 CD 产字 */ class BattleGameOtherPassiveSlot { private readonly _root: Node; private readonly _quality: Node | null; private readonly _icon: Node | null; private readonly _mask: Node | null; private readonly _maskSprite: Sprite | null; private readonly _name: Label | null; private _buffId = 0; private _usesCd = false; private _cdTotalSec = 0; private _cdLeftSec = 0; private _parsed: DailyShopParsed | null = null; /** @param root skillGrid 实例 */ constructor(root: Node) { this._root = root; this._quality = root.getChildByName('quality'); this._icon = root.getChildByName('icon') ?? this._quality?.getChildByName('icon'); this._mask = root.getChildByName('mask'); this._maskSprite = this._mask?.getComponent(Sprite) ?? null; if (this._maskSprite?.isValid) setupSkillCdMaskSprite(this._maskSprite); this._name = root.getChildByName('name')?.getComponent(Label) ?? null; } /** * 绑定被动展示。 * @param parsed 被动解析 */ bind(parsed: DailyShopParsed | null): void { if (!parsed || parsed.kind !== DailyShopKind.Passive) { this._root.active = false; this._buffId = 0; this._usesCd = false; this._cdTotalSec = 0; this._cdLeftSec = 0; this._parsed = null; return; } const row = parsed.row as ITableBattlebuff; const ft = Math.floor(Number(row?.functiontype) || 0); this._buffId = parsed.id; this._parsed = parsed; this._usesCd = passiveBuffUsesBattleGameCd(ft); this._root.active = true; const quality = Math.max(1, Math.floor(Number(parsed.quality) || 0)); this._setQualitySprite(quality); this._setIconSprite(DailyShopLotteryIcon.itemIconName(parsed)); this._setName(parsed.name, quality); if (this._usesCd) { this._cdTotalSec = Math.max(0, parsed.cd | 0); this._cdLeftSec = this._cdTotalSec; } else { this._cdTotalSec = 0; this._cdLeftSec = 0; } this._refreshCdVisual(); this.syncBaseThunderMask(); } /** 当前 buff id */ get buffId(): number { return this._buffId; } /** 被动图标节点(飞行动画复制影子) */ getIconNode(): Node | null { return this._icon?.isValid ? this._icon : null; } /** 配表 functiontype */ getFunctionType(): number { const row = this._parsed?.row as ITableBattlebuff | undefined; return Math.floor(Number(row?.functiontype) || 0); } /** 根节点 */ get root(): Node { return this._root; } /** 绑定的被动解析 */ getParsed(): DailyShopParsed | null { return this._parsed; } /** * 同步 AI 侧 CD。 * @param cdLeftSec 剩余 * @param cdTotalSec 总长 */ syncCd(cdLeftSec: number, cdTotalSec: number): void { if (!this._usesCd) return; this._cdTotalSec = Math.max(0, cdTotalSec); this._cdLeftSec = Math.max(0, cdLeftSec); this._refreshCdVisual(); this.syncBaseThunderMask(); } /** 九霄神雷次数用尽:直接盖满 mask,不刷新 CD */ syncBaseThunderMask(): void { const row = this._parsed?.row as ITableBattlebuff | undefined; if (!isBattleBuffPassiveBaseThunder(row)) return; if (!getBattleBuffBaseThunderHost().isTriggersExhausted(AiHeadlessBagHost.AI_PLAYER_ID)) return; if (this._mask?.isValid) this._mask.active = true; if (this._maskSprite?.isValid) applySkillCdMaskFillRange(this._maskSprite, 1); } private _setQualitySprite(quality: number): void { if (!this._quality?.isValid) return; const sl = this._quality.getComponent(SpriteLoad); if (!sl) { console.log('[BattleGame] 敌方被动 quality 缺少 SpriteLoad', this._root.name); return; } sl.setSprite(GPath.BattleGamePassiveSkillBg(quality), GBundle.BattleGame); } private _setIconSprite(iconName: string): void { if (!this._icon?.isValid) return; const sl = this._icon.getComponent(SpriteLoad); if (!sl) { console.log('[BattleGame] 敌方被动 icon 缺少 SpriteLoad', this._root.name); return; } sl.setSprite(GPath.ShopSkill(iconName), GBundle.Items); } private _setName(name: string, quality: number): void { if (!this._name?.isValid) return; this._name.string = name; const hex = PASSIVE_NAME_OUTLINE_HEX[quality]; if (!hex) { console.log('[BattleGame] 敌方被动未知品质描边', quality); return; } this._name.enableOutline = true; this._name.outlineColor = Color.fromHEX(new Color(), hex); } private _refreshCdVisual(): void { if (!this._usesCd) { if (this._mask?.isValid) this._mask.active = false; return; } const onCd = this._cdLeftSec > 0; if (this._mask?.isValid) this._mask.active = onCd; if (onCd && this._maskSprite?.isValid && this._cdTotalSec > 0) { applySkillCdMaskFillRange(this._maskSprite, this._cdLeftSec / this._cdTotalSec); } } } /** * 战斗界面敌方 buff:ui_otherskill1List(主动)/ ui_otherskill2List(被动)。 * 仅展示与同步 AI CD,不可拖拽。 */ export class BattleGameOtherSkillView { private readonly _active1: BattleGameDailyShopSkillSlot | null; private readonly _active2: BattleGameDailyShopSkillSlot | null; private readonly _passiveRoot: Node | null; private readonly _passiveTemplate: Node | null; private readonly _passiveSlots: BattleGameOtherPassiveSlot[] = []; private readonly _passiveNodes: Node[] = []; private _activeBuffIds: number[] = []; private _ownedDesc: DailyShopOwnedDesc | null = null; /** * @param activeList ui_otherskill1List * @param passiveList ui_otherskill2List */ constructor(activeList: Node | null | undefined, passiveList: Node | null | undefined) { const skill1 = activeList?.getChildByName('skill1') ?? null; const skill2 = activeList?.getChildByName('skill2') ?? null; this._active1 = skill1?.isValid ? new BattleGameDailyShopSkillSlot(skill1, 0) : null; this._active2 = skill2?.isValid ? new BattleGameDailyShopSkillSlot(skill2, 1) : null; this._passiveRoot = passiveList?.isValid ? passiveList : null; this._passiveTemplate = this._findTemplate(this._passiveRoot); if (this._passiveTemplate?.isValid) { this._passiveTemplate.active = false; } } /** 按 CarryStore(AI) 重建展示;随后 syncFromAi 对齐 CD */ refresh(): void { this._ownedDesc?.hide(); const aiPid = AiHeadlessBagHost.AI_PLAYER_ID; const actives = BattleBuffCarryStore.getActives(aiPid); this._activeBuffIds = []; for (let i = 0; i < actives.length; i++) { this._activeBuffIds.push(actives[i].id); } this._bindActiveSlot(this._active1, actives[0] ?? null); this._bindActiveSlot(this._active2, actives[1] ?? null); this._rebuildPassives(BattleBuffCarryStore.getPassives(aiPid)); this.bindSkillDesc(); this.syncFromAi(); } /** 绑定 ui_otherDesc 描述浮层 */ setOwnedDesc(desc: DailyShopOwnedDesc | null): void { this._ownedDesc = desc; } /** 刷新敌方主动/被动 skill 点击描述 */ bindSkillDesc(): void { const desc = this._ownedDesc; if (!desc) return; for (let slotIndex = 0; slotIndex < 2; slotIndex++) { const slot = slotIndex === 0 ? this._active1 : this._active2; const root = slot?.root; const parsed = slot?.getParsed(); if (!root?.isValid || !root.active || !parsed) { if (root?.isValid) desc.unbindBattleSkillItem(root); continue; } desc.bindBattleSkillItem(root, parsed, DailyShopKind.Active); } for (let i = 0; i < this._passiveSlots.length; i++) { const slot = this._passiveSlots[i]; const root = slot.root; const parsed = slot.getParsed(); if (!root?.isValid || !root.active || !parsed) { if (root?.isValid) desc.unbindBattleSkillItem(root); continue; } desc.bindBattleSkillItem(root, parsed, DailyShopKind.Passive); } } /** 从 AiBattleBridge 同步 CD / 七十二变展示 */ syncFromAi(): void { const bridge = AiBattleBridge.inst; this._syncActives(bridge.snapshotActiveSkillUi()); this._syncPassives(bridge.snapshotPassiveCdUi()); this._syncBaseThunderMasks(); } /** * 主动槽 icon 节点(供施放飞图标复制)。 * @param slotIndex 0/1 */ getActiveIconNode(slotIndex: number): Node | null { const slot = slotIndex === 0 ? this._active1 : this._active2; return slot?.getIconNode() ?? null; } /** * 按 functiontype 取敌方被动 icon 节点。 * @param functionType Battlebuff.functiontype */ getPassiveIconNode(functionType: number): Node | null { const ft = Math.floor(Number(functionType) || 0); if (ft <= 0) return null; for (let i = 0; i < this._passiveSlots.length; i++) { const slot = this._passiveSlots[i]; if (slot.getFunctionType() === ft) { return slot.getIconNode(); } } return null; } /** * 绑定主动格。 * @param slot 槽 * @param parsed buff */ private _bindActiveSlot(slot: BattleGameDailyShopSkillSlot | null, parsed: DailyShopParsed | null): void { if (!slot) return; if (!parsed) { slot.bind(null); return; } slot.bind(parsed); } /** * 同步主动 CD 与展示。 * @param uiSlots AI 快照 */ private _syncActives(uiSlots: AiActiveSkillUiSlot[]): void { for (let i = 0; i < 2; i++) { const slot = i === 0 ? this._active1 : this._active2; if (!slot) continue; const buffId = this._activeBuffIds[i] ?? 0; if (buffId <= 0) continue; const snap = this._findActiveSnap(uiSlots, buffId); if (!snap) continue; if (slot.getBoundBuffId() !== snap.display.id) { slot.bind(snap.display, { cdLeftSec: snap.cdLeftSec, cdTotalSec: snap.cdTotalSec, }); } else { slot.syncCooldown(snap.cdLeftSec, snap.cdTotalSec); } } } /** * 同步被动 CD。 * @param uiSlots AI CD 快照 */ private _syncPassives(uiSlots: AiPassiveCdUiSlot[]): void { for (let i = 0; i < this._passiveSlots.length; i++) { const slot = this._passiveSlots[i]; const snap = this._findPassiveSnap(uiSlots, slot.buffId); if (!snap) continue; slot.syncCd(snap.cdLeftSec, snap.cdTotalSec); } } /** 同步 AI 九霄神雷次数用尽遮罩 */ private _syncBaseThunderMasks(): void { for (let i = 0; i < this._passiveSlots.length; i++) { this._passiveSlots[i].syncBaseThunderMask(); } } /** * @param uiSlots 快照 * @param buffId id */ private _findActiveSnap(uiSlots: AiActiveSkillUiSlot[], buffId: number): AiActiveSkillUiSlot | null { for (let i = 0; i < uiSlots.length; i++) { if (uiSlots[i].buffId === buffId) return uiSlots[i]; } return null; } /** * @param uiSlots 快照 * @param buffId id */ private _findPassiveSnap(uiSlots: AiPassiveCdUiSlot[], buffId: number): AiPassiveCdUiSlot | null { for (let i = 0; i < uiSlots.length; i++) { if (uiSlots[i].buffId === buffId) return uiSlots[i]; } return null; } /** * 重建被动列表。 * @param passives 携带列表 */ private _rebuildPassives(passives: DailyShopParsed[]): void { this._clearPassives(); const root = this._passiveRoot; const template = this._passiveTemplate; if (!root?.isValid || !template?.isValid) return; for (let i = 0; i < passives.length; i++) { const item = instantiate(template); item.active = true; item.setParent(root); const slot = new BattleGameOtherPassiveSlot(item); slot.bind(passives[i]); this._passiveNodes.push(item); this._passiveSlots.push(slot); } root.getComponent(Layout)?.updateLayout(); } /** @param listRoot 被动列表根 */ private _findTemplate(listRoot: Node | null): Node | null { if (!listRoot?.isValid) return null; const named = listRoot.getChildByName('template'); if (named?.isValid) return named; if (listRoot.children.length > 0) return listRoot.children[0]; return null; } private _clearPassives(): void { const desc = this._ownedDesc; for (let i = 0; i < this._passiveNodes.length; i++) { const node = this._passiveNodes[i]; if (desc && node?.isValid) desc.unbindBattleSkillItem(node); if (node?.isValid) node.destroy(); } this._passiveNodes.length = 0; this._passiveSlots.length = 0; } }