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/BattleGame/BattleGameOtherSkillView.ts

406 lines
15 KiB

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<number, string> = {
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;
}
}