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.
329 lines
12 KiB
329 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[] = [];
|
|
|
|
/** @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;
|
|
}
|
|
}
|
|
|
|
/** @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 */
|
|
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;
|
|
}
|
|
}
|
|
|