import JungleAnimator from "./JungleAnimator"; import { createJungleMockConfig } from "./JungleConfig"; import JungleProgress from "./JungleProgress"; import JungleRewardService from "./JungleRewardService"; import { JungleActivityConfig, JungleRenderState, JungleTierConfig, JungleViewAssets, } from "./JungleTypes"; import JungleView from "./JungleView"; const { ccclass, property } = cc._decorator; /** * Jungle Treasure 流程控制器。 * 只管理活动状态和模块协作;UI、动画、配置、存档与发奖均由独立模块负责。 */ @ccclass export default class JungleManager extends cc.Component { @property(cc.SpriteFrame) cardFrame: cc.SpriteFrame = null; @property(cc.SpriteFrame) trackFrame: cc.SpriteFrame = null; @property(cc.SpriteFrame) titleFrame: cc.SpriteFrame = null; @property(cc.SpriteFrame) buttonFrame: cc.SpriteFrame = null; @property(cc.SpriteFrame) lockFrame: cc.SpriteFrame = null; @property(cc.SpriteFrame) timerFrame: cc.SpriteFrame = null; @property(cc.SpriteFrame) closeFrame: cc.SpriteFrame = null; @property(cc.SpriteFrame) coinFrame: cc.SpriteFrame = null; @property(cc.SpriteFrame) freezeFrame: cc.SpriteFrame = null; @property(cc.SpriteFrame) hammerFrame: cc.SpriteFrame = null; @property(cc.SpriteFrame) magicWandFrame: cc.SpriteFrame = null; @property(cc.SpriteFrame) infiniteHealthFrame: cc.SpriteFrame = null; @property({ tooltip: "服务器支付尚未接入时,点击付费档位自动模拟支付成功" }) useMockPayment: boolean = true; @property({ tooltip: "只预览领取和轨道动画,不发道具、不支付、不保存进度" }) previewAnimationOnly: boolean = true; private activityConfig: JungleActivityConfig = null; private currentTierIndex: number = 0; private cards: cc.Node[] = []; private slotPositions: cc.Vec2[] = []; private isMoving: boolean = false; private isPurchasing: boolean = false; private view: JungleView = null; private animator: JungleAnimator = null; private progress: JungleProgress = null; private rewardService: JungleRewardService = null; protected onLoad(): void { this.progress = new JungleProgress(); this.rewardService = new JungleRewardService(); this.activityConfig = createJungleMockConfig(); this.currentTierIndex = this.progress.read(this.activityConfig, this.previewAnimationOnly); this.view = new JungleView( this.node, this.getViewAssets(), this, this.closeActivity, this.onCardClicked, ); const refs = this.view.build(); this.cards = refs.cards; this.slotPositions = refs.slotPositions; this.animator = new JungleAnimator(this, refs.contentNode, this.cardFrame); this.renderAllCards(); this.updateTimer(); this.schedule(this.updateTimer, 1); } protected onDestroy(): void { this.unschedule(this.updateTimer); } /** 后续服务器配置到达后调用此方法即可替换模拟表。 */ public setServerConfig(config: JungleActivityConfig, claimedTierIndex?: number): void { if (!config || !config.tiers || config.tiers.length === 0) { cc.warn("[JungleTreasure] ignored empty server config"); return; } this.activityConfig = config; this.currentTierIndex = typeof claimedTierIndex === "number" ? Math.max(0, Math.min(claimedTierIndex, config.tiers.length)) : this.progress.read(config, this.previewAnimationOnly); this.renderAllCards(); this.updateTimer(); } /** 正式支付层完成后回调。支付失败或取消不会移动轨道。 */ public completePurchase(success: boolean): void { if (!this.isPurchasing) { return; } this.isPurchasing = false; if (success) { this.completeCurrentTier(); } else { this.renderAllCards(); } } public openActivity(): void { this.node.active = true; this.renderAllCards(); this.updateTimer(); } public closeActivity(): void { if (this.isMoving || this.isPurchasing) { return; } this.node.emit("jungle-close"); this.node.active = false; } /** 仅用于当前模拟配置的联调。 */ public resetMockProgress(): void { if (!this.activityConfig) { return; } this.progress.reset(this.activityConfig); this.currentTierIndex = 0; this.renderAllCards(); } public getCurrentTierIndex(): number { return this.currentTierIndex; } private onCardClicked(buttonComponent: cc.Button): void { if (this.isMoving || this.isPurchasing || !this.activityConfig) { return; } const button = buttonComponent && buttonComponent.node; if (!button) { return; } const card = button.parent; if (card !== this.cards[0]) { this.animator.playLockedFeedback(card); return; } const tier = this.activityConfig.tiers[this.currentTierIndex]; if (!tier || Date.now() >= this.activityConfig.endAt) { return; } if (this.previewAnimationOnly) { this.completeCurrentTier(); } else if (tier.price > 0) { this.requestPurchase(tier); } else { this.completeCurrentTier(); } } private requestPurchase(tier: JungleTierConfig): void { this.isPurchasing = true; this.renderAllCards(); if (this.useMockPayment) { this.scheduleOnce(() => this.completePurchase(true), 0.45); return; } this.node.emit("jungle-purchase-request", { tierId: tier.id, price: tier.price, productId: tier.productId || "", rewards: tier.rewards, }); } private completeCurrentTier(): void { const tier = this.activityConfig.tiers[this.currentTierIndex]; if (!tier || this.isMoving) { return; } this.isMoving = true; if (!this.previewAnimationOnly) { this.rewardService.grant(this.node, tier.rewards); } const leavingCard = this.cards[0]; this.animator.playClaimEffect( leavingCard, () => this.animator.playCardExit(leavingCard, () => this.shiftCards()), ); } private shiftCards(): void { const oldCards = this.cards.slice(); this.currentTierIndex++; if (!this.previewAnimationOnly) { this.progress.save(this.activityConfig, this.currentTierIndex); } const recycled = oldCards[0]; recycled.stopAllActions(); recycled.active = false; recycled.scale = 1; recycled.opacity = 0; recycled.setAnchorPoint(0.5, 0.5); this.animator.moveCardsWithOverlap(oldCards, this.slotPositions, 1, () => { this.cards.splice( 0, this.cards.length, oldCards[1], oldCards[2], oldCards[3], oldCards[4], oldCards[5], recycled, ); this.showNewSixthCard(recycled); }); } private showNewSixthCard(card: cc.Node): void { const sixthTierIndex = this.currentTierIndex + 5; const targetCenter = this.slotPositions[5]; this.view.renderCard(card, this.activityConfig, this.getRenderState(), sixthTierIndex, 5); if (sixthTierIndex >= this.activityConfig.tiers.length) { card.setAnchorPoint(0.5, 0.5); card.setPosition(targetCenter); this.finishShift(); return; } this.animator.showNewCard(card, targetCenter, () => this.finishShift()); } private finishShift(): void { this.isMoving = false; this.renderAllCards(); if (this.currentTierIndex >= this.activityConfig.tiers.length) { this.node.emit("jungle-complete"); } } private renderAllCards(): void { if (this.view) { this.view.renderAllCards(this.activityConfig, this.getRenderState()); } } private updateTimer(): void { if (!this.view || !this.activityConfig) { return; } if (this.view.updateTimer(this.activityConfig) === 0) { this.renderAllCards(); this.node.emit("jungle-expired"); } } private getRenderState(): JungleRenderState { return { currentTierIndex: this.currentTierIndex, isMoving: this.isMoving, isPurchasing: this.isPurchasing, }; } private getViewAssets(): JungleViewAssets { return { cardFrame: this.cardFrame, trackFrame: this.trackFrame, titleFrame: this.titleFrame, buttonFrame: this.buttonFrame, lockFrame: this.lockFrame, timerFrame: this.timerFrame, closeFrame: this.closeFrame, coinFrame: this.coinFrame, freezeFrame: this.freezeFrame, hammerFrame: this.hammerFrame, magicWandFrame: this.magicWandFrame, infiniteHealthFrame: this.infiniteHealthFrame, }; } }