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/BattleGamePassiveSkillView.ts

349 lines
12 KiB

import { instantiate, Layout, Node, Sprite, Vec3 } from 'cc';
import { SpriteLoad } from 'db://assets/mx/components/SpriteLoad';
import { GBundle, GPath } from '../../game/ConfigRes';
import { DailyShopDataManager } from '../../manager/DailyShopDataManager';
import { DailyShopKind, DailyShopParsed } from '../../manager/DailyShopTypes';
import { DailyShopLotteryIcon, DailyShopOwnedDesc } from '../PopDailyShopLottery/DailyShopLotteryIcon';
import { isBattleBuffPassiveBaseThunder, passiveBuffUsesBattleGameCd } from '../../manager/battleBuff/BattleBuffPassiveSkillUtil';
import { getBattleBuffBaseThunderHost } from '../../battle/combat/BattleBuffBaseThunderHost';
import type { ProjectBattleHandle } from '../../battle/runtime/BattleRuntime';
import type { CocosBagBoardView } from '../../battle/view/CocosBagBoardView';
import { BattleGamePassiveSkillRuntime } from './BattleGamePassiveSkillRuntime';
import type { BattleGamePassiveSkillHost } from './BattleGamePassiveSkillHost';
import { applySkillCdMaskFillRange, setupSkillCdMaskSprite } from './BattleGameDailyShopSkillView';
/** 单个被动 skill 格:品质 / 图标 / CD 遮罩 */
class BattleGamePassiveSkillSlot {
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 _cdTotalSec = 0;
private _cdLeftSec = 0;
private _usesCd = false;
private _parsed: DailyShopParsed | null = null;
private _spawnInflight = false;
/** 本轮 CD 已触发过(防止 cd=0 时每帧重复产字) */
private _cdTriggerConsumed = false;
/** ui_selfskill2List 槽位下标 */
private readonly _slotIndex: number;
/** @param root skillGrid2 实例根节点 */
/** @param slotIndex 槽位下标 */
constructor(root: Node, slotIndex: number) {
this._root = root;
this._slotIndex = slotIndex;
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);
}
/** 绑定被动 buff */
bind(parsed: DailyShopParsed | null): void {
if (!parsed || parsed.kind !== DailyShopKind.Passive) {
this._root.active = false;
this._parsed = null;
this._cdTotalSec = 0;
this._cdLeftSec = 0;
this._usesCd = false;
this._spawnInflight = false;
this._cdTriggerConsumed = false;
return;
}
const row = parsed.row as ITableBattlebuff;
const ft = Number(row?.functiontype) || 0;
this._usesCd = passiveBuffUsesBattleGameCd(ft);
this._parsed = parsed;
this._spawnInflight = false;
this._cdTriggerConsumed = false;
this._root.active = true;
const quality = Math.max(1, Math.floor(Number(parsed.quality) || 0));
this._setQualitySprite(quality);
this._setIconSprite(DailyShopLotteryIcon.itemIconName(parsed));
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();
}
/** 帧更新 CD */
tick(dt: number, battle: ProjectBattleHandle | null, host: BattleGamePassiveSkillHost | null): void {
this._syncBaseThunderMask();
if (!this._usesCd || this._spawnInflight) return;
if (this._cdLeftSec > 0) {
this._cdLeftSec = Math.max(0, this._cdLeftSec - dt);
this._refreshCdVisual();
return;
}
if (this._cdTriggerConsumed) return;
const row = this._parsed?.row as ITableBattlebuff | undefined;
if (!row) return;
const ft = Math.floor(Number(row.functiontype) || 0);
if ((ft === 3 || ft === 9) && (!host?.bagView || !host.battle?.bagSession)) {
return;
}
this._cdTriggerConsumed = true;
const handled = BattleGamePassiveSkillRuntime.onPassiveCdReady(row, battle, host, this._slotIndex);
if (!handled) {
this.restartCd();
}
}
/** ui_selfskill2List 槽位下标 */
get slotIndex(): number {
return this._slotIndex;
}
/** 被动图标世界坐标 */
getIconWorldPosition(): Readonly<Vec3> | null {
const icon = this._icon;
if (!icon?.isValid) return null;
return icon.worldPosition.clone();
}
/** 被动图标节点(飞行动画复制影子) */
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);
}
/** 产字成功后重启 CD */
restartCd(): void {
if (!this._usesCd) return;
this._cdLeftSec = this._cdTotalSec;
this._cdTriggerConsumed = false;
this._refreshCdVisual();
}
/** 异步产字进行中 */
setSpawnInflight(inflight: boolean): void {
this._spawnInflight = inflight;
}
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 _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);
}
}
/** 九霄神雷次数用尽:直接盖满 mask,不刷新 CD */
private _syncBaseThunderMask(): void {
const row = this._parsed?.row as ITableBattlebuff | undefined;
if (!isBattleBuffPassiveBaseThunder(row)) return;
if (!getBattleBuffBaseThunderHost().isTriggersExhausted(0)) return;
if (this._mask?.isValid) this._mask.active = true;
if (this._maskSprite?.isValid) applySkillCdMaskFillRange(this._maskSprite, 1);
}
}
/**
* 战斗界面 ui_selfskill2List:按当前携带被动数量动态展示 skillGrid2。
*/
export class BattleGamePassiveSkillView implements BattleGamePassiveSkillHost {
private readonly _listRoot: Node | null;
private readonly _template: Node | null;
private _battle: ProjectBattleHandle | null = null;
private _bagView: CocosBagBoardView | null = null;
private _ownedDesc: DailyShopOwnedDesc | null = null;
private readonly _slots: BattleGamePassiveSkillSlot[] = [];
private readonly _itemNodes: Node[] = [];
/** 金矿飞币:图标世界坐标 → ui_money */
private _goldMineCoinFly: ((fromWorld: Readonly<Vec3>) => void) | null = null;
/** @param listRoot ui_selfskill2List 节点 */
constructor(listRoot: Node | null | undefined) {
this._listRoot = listRoot?.isValid ? listRoot : null;
this._template = this._findTemplate(this._listRoot);
if (this._template?.isValid) {
this._template.active = false;
}
}
/**
* 绑定金矿产币飞币表现。
* @param handler 起点世界坐标回调;null 清除
*/
setGoldMineCoinFlyHandler(handler: ((fromWorld: Readonly<Vec3>) => void) | null): void {
this._goldMineCoinFly = handler;
}
/** @inheritdoc */
get battle(): ProjectBattleHandle | null {
return this._battle;
}
/** @inheritdoc */
get bagView(): CocosBagBoardView | null {
return this._bagView;
}
bindBattle(battle: ProjectBattleHandle | null): void {
this._battle = battle;
}
bindBagView(bagView: CocosBagBoardView | null): void {
this._bagView = bagView;
}
/** 绑定战斗 skill 描述浮层 */
setOwnedDesc(desc: DailyShopOwnedDesc | null): void {
this._ownedDesc = desc;
}
/** @inheritdoc */
getIconWorldPosition(slotIndex: number): Readonly<Vec3> | null {
const slot = this._slots[slotIndex];
return slot?.getIconWorldPosition() ?? 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._slots.length; i++) {
const slot = this._slots[i];
if (slot.getFunctionType() === ft) {
return slot.getIconNode();
}
}
return null;
}
/** @inheritdoc */
notifyGoldMineCoinFly(slotIndex: number): void {
const from = this.getIconWorldPosition(slotIndex);
if (!from) {
console.log('[PassiveSkill] 金矿飞币无图标坐标', slotIndex);
return;
}
this._goldMineCoinFly?.(from);
}
/** @inheritdoc */
restartPassiveCd(slotIndex: number): void {
this._slots[slotIndex]?.restartCd();
}
/** @inheritdoc */
setPassiveSpawnInflight(slotIndex: number, inflight: boolean): void {
this._slots[slotIndex]?.setSpawnInflight(inflight);
}
/** 按被动槽刷新展示 */
refresh(): void {
this._ownedDesc?.hide();
this._clearItems();
const root = this._listRoot;
const template = this._template;
if (!root?.isValid || !template?.isValid) return;
const carried = this._collectCarriedPassive();
for (let i = 0; i < carried.length; i++) {
const item = instantiate(template);
item.active = true;
item.setParent(root);
const slot = new BattleGamePassiveSkillSlot(item, i);
slot.bind(carried[i]);
this._ownedDesc?.bindBattleSkillItem(item, carried[i], DailyShopKind.Passive);
this._itemNodes.push(item);
this._slots.push(slot);
}
root.getComponent(Layout)?.updateLayout();
}
/** 帧更新 CD */
tick(dt: number): void {
const battle = this._battle;
for (let i = 0; i < this._slots.length; i++) {
this._slots[i].tick(dt, battle, this);
}
}
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 _collectCarriedPassive(): DailyShopParsed[] {
const out: DailyShopParsed[] = [];
const ids = DailyShopDataManager.getPassiveSlotIds();
for (let i = 0; i < ids.length; i++) {
const id = Number(ids[i]) || 0;
if (id <= 0) continue;
const parsed = DailyShopDataManager.getParsed(id);
if (!parsed) {
console.log('[BattleGame] 被动槽 buff 解析失败', id);
continue;
}
if (parsed.kind !== DailyShopKind.Passive) {
console.log('[BattleGame] 被动槽非被动 buff', id, parsed.kind);
continue;
}
out.push(parsed);
}
return out;
}
private _clearItems(): void {
for (let i = 0; i < this._itemNodes.length; i++) {
const node = this._itemNodes[i];
if (node?.isValid) node.destroy();
}
this._itemNodes.length = 0;
this._slots.length = 0;
}
}