MatchMaster/assets/coin_madness/script/CoinMadnessPanel.ts

474 lines
28 KiB
TypeScript

import CoinMadnessDigits from './CoinMadnessDigits';
import BrownLoadingSpinner from '../../Script/BrownLoadingSpinner';
const { ccclass, property } = cc._decorator;
/** Layout and sprite references live in CoinMadness.prefab. This component only binds state. */
@ccclass
export default class CoinMadnessPanel extends cc.Component {
@property(cc.Node) card: cc.Node = null;
@property(cc.Node) dim: cc.Node = null;
@property(CoinMadnessDigits) countdown: CoinMadnessDigits = null;
@property(cc.SpriteFrame) flyingCoin: cc.SpriteFrame = null;
@property(CoinMadnessDigits) totalDigits: CoinMadnessDigits = null;
@property(cc.Node) lockedOffer: cc.Node = null;
@property(cc.Node) unlockedOffer: cc.Node = null;
@property(cc.Node) unlockedTotalPosition: cc.Node = null;
@property(cc.Node) purchaseButton: cc.Node = null;
@property(cc.Label) purchaseLabel: cc.Label = null;
@property(CoinMadnessDigits) priceDigits: CoinMadnessDigits = null;
@property(cc.Node) currency: cc.Node = null;
@property(cc.Node) content: cc.Node = null;
@property(cc.Node) closeButton: cc.Node = null;
@property(cc.Node) helpButton: cc.Node = null;
@property(cc.Label) message: cc.Label = null;
@property(cc.Node) debugTools: cc.Node = null;
@property(cc.SpriteFrame) lockedFrame: cc.SpriteFrame = null;
@property(cc.SpriteFrame) unlockedFrame: cc.SpriteFrame = null;
@property(CoinMadnessDigits) discountDigits: CoinMadnessDigits = null;
@property({ tooltip: '折扣数字,例如 4 表示4折' }) discount = 4;
private api: any;
private data: any;
private offset = 0;
private busy = false;
private alive = true;
private closing = false;
private purchasing = false;
private cardScale = 1;
private boundRows: cc.Node[] = [];
private rowPitch = 166;
private authoredRowCount = 0;
private authoredContentHeight = 0;
private lockedTotalPosition: cc.Vec3 = null;
private rewardAnimations: cc.Node[] = [];
private rewardRowScales: Map<cc.Node, number> = new Map();
private claimWaiters: Map<number, cc.Node> = new Map();
private displayedClaims: Set<number> = new Set();
private progressKey = '';
private shownWins = 0;
private fromWins = 0;
private targetWins = 0;
private progressElapsed = 0;
private progressDuration = 1;
private progressSteps: { goal: number; start: number; end: number; begin: number; duration: number }[] = [];
private storedWins = -1;
private readyRows: Map<cc.Node, boolean> = new Map();
private pendingReadyRows: Set<cc.Node> = new Set();
private readyScales: Map<cc.Node, cc.Vec2> = new Map();
private focusedProgressIndex = -1;
private progressFollowStopped = false;
initialize(data: any, api: any) {
this.api = api;
const size = cc.view.getVisibleSize(); this.node.setContentSize(size); this.dim.setContentSize(size);
this.card.scale = 0.9 * Math.min((size.width - 96) / this.card.width, (size.height - 96) / this.card.height);
this.cardScale = this.card.scale;
this.authoredRowCount = this.content.children.length;
this.authoredContentHeight = this.content.height;
this.content.parent.on(cc.Node.EventType.TOUCH_START, this.stopProgressFollow, this);
if (this.content.children.length > 1) this.rowPitch = this.content.children[0].y - this.content.children[1].y;
this.closeButton.on(cc.Node.EventType.TOUCH_END, (event: cc.Event.EventTouch) => { event.stopPropagation(); this.closeWithAnimation(); });
this.helpButton.on(cc.Node.EventType.TOUCH_END, (event: cc.Event.EventTouch) => { event.stopPropagation(); if (!this.closing) this.api.openHelp(); });
this.purchaseButton.on(cc.Node.EventType.TOUCH_END, () => {
if (this.busy || this.closing || this.purchasing || !this.data || this.data.state.paidAt
|| (this.api.request && this.data.status !== 'purchasable')
|| Date.now() + this.offset >= this.data.state.config.endsAt) return;
this.purchasing = true;
const scale = this.purchaseButton.scale;
cc.tween(this.purchaseButton).to(.09, { scale: scale * .94 }, { easing: 'quadOut' })
.to(.12, { scale: scale * 1.045 }, { easing: 'sineOut' })
.to(.10, { scale }, { easing: 'sineInOut' })
.call(() => {
this.purchasing = false;
if (this.alive && !this.closing) this.run('purchase', { periodId: this.data.state.config.periodId });
}).start();
});
this.debugTools.active = false;
this.purchaseButton.active = false; this.content.children.forEach(row => row.active = false);
const rules = this.card.getComponentsInChildren(cc.Label).find(label => label.node.name === 'Rules');
if (rules) rules.node.active = false;
this.message.node.active = false;
if (data) this.render(data);
if (!api.initialReady) this.run('read', {});
this.schedule(this.tick, 1);
this.playOpenAnimation();
}
private playOpenAnimation() {
const scale = this.card.scale;
cc.Tween.stopAllByTarget(this.card);
this.card.scale = scale * .86;
cc.tween(this.card).to(.20, { scale: scale * 1.035 }, { easing: 'quadOut' })
.to(.12, { scale }, { easing: 'sineOut' }).start();
}
private closeWithAnimation() {
if (this.closing || !this.alive) return;
this.closing = true;
cc.Tween.stopAllByTarget(this.card);
cc.tween(this.card).to(.15, { scale: this.cardScale * .90 }, { easing: 'sineIn' })
.call(() => { if (this.alive && this.api) this.api.close(); }).start();
}
render(data: any) {
if (!this.alive || this.closing || !data) return;
const unlocking = !!this.data && !this.data.state.paidAt && !!data.state.paidAt;
if (this.data && this.data.state.config.periodId !== data.state.config.periodId) {
this.readyScales.forEach((_, row) => this.stopReadyPulse(row));
this.claimWaiters.forEach((_, stage) => this.stopClaimWaiting(stage));
this.displayedClaims.clear();
}
this.data = data; this.offset = data.serverNow - Date.now();
if (data.state.settled) {
this.api.openSettlement(data);
return;
}
const state = data.state, config = state.config;
this.syncProgress(state);
const expired = Date.now() + this.offset >= config.endsAt;
if (this.api.request && (expired || data.status === 'unavailable' || data.status === 'locked')) {
this.finishExpired();
return;
}
if (!this.lockedTotalPosition) this.lockedTotalPosition = this.totalDigits.node.position.clone();
const unlocked = !!state.paidAt;
this.lockedOffer.active = !unlocked;
this.unlockedOffer.active = unlocked;
this.purchaseButton.active = !unlocked && (!this.api.request || data.status === 'purchasable');
this.totalDigits.node.position = unlocked ? this.unlockedTotalPosition.position : this.lockedTotalPosition;
this.totalDigits.setText(String(config.stages.reduce((sum, s) => sum + s.coins, 0)), 164);
this.purchaseLabel.string = expired ? '已结束' : state.stage >= config.stages.length ? '已全部领取' : state.paidAt ? '已解锁' : '';
const canBuy = !state.paidAt && !expired && (!this.api.request || data.status === 'purchasable');
this.purchaseButton.color = canBuy ? cc.Color.WHITE : new cc.Color(200, 195, 180);
this.priceDigits.node.active = this.currency.active = canBuy;
if (this.discountDigits) this.discountDigits.setText(String(this.discount));
if (Number.isFinite(config.priceCents)) {
const currencyWidth = this.currency.width * Math.abs(this.currency.scaleX), gap = 12;
const width = this.priceDigits.setText(String(Math.floor(config.priceCents / 100)),
Math.max(1, this.purchaseButton.width - 64 - currencyWidth - gap));
// Center the price and currency together instead of keeping “元” at a fixed x.
this.priceDigits.node.x = -(currencyWidth + gap) / 2;
this.currency.x = (width + gap) / 2;
}
// The first complete prefab row is the template for additional configured stages.
while (this.content.children.length < config.stages.length) {
const row = cc.instantiate(this.content.children[0]); this.content.addChild(row);
row.y = this.content.children[0].y - (this.content.children.length - 1) * this.rowPitch;
}
// Preserve the authored scroll area; only extend it for additional server stages.
this.content.height = this.authoredContentHeight
+ Math.max(0, config.stages.length - this.authoredRowCount) * this.rowPitch;
this.content.children.forEach((row, i) => {
row.active = i < config.stages.length; if (!row.active) return;
row.name = 'Stage' + i;
if (this.boundRows.indexOf(row) < 0) {
this.boundRows.push(row);
row.on(cc.Node.EventType.TOUCH_END, (event: cc.Event.EventTouch) => {
const current = this.data.state, target = current.config.stages[i];
if (this.taskProgress(target.wins) < target.wins && current.totalWins >= target.wins) return;
if (this.pendingReadyRows.has(row)) return;
if (this.claimWaiters.has(i) || this.displayedClaims.has(i)) return;
if (!this.closing && !this.busy && !current.claimed[i] && !current.paidAt) {
event.stopPropagation(); this.showLockedToast(); return;
}
if (!this.closing && !this.busy && !current.claimed[i] && current.totalWins < target.wins) {
event.stopPropagation(); this.api.toast('请通过更多的关卡以解锁金币奖励!'); return;
}
if (!this.closing && !current.claimed[i] && current.totalWins >= target.wins && !this.busy && Date.now() + this.offset < current.config.endsAt) {
event.stopPropagation();
this.run('claim', { periodId: current.config.periodId, stage: i });
}
});
}
const stage = config.stages[i], complete = this.displayedClaims.has(i)
|| (!this.claimWaiters.has(i) && !!state.claimed[i]);
const child = (name: string) => row.getChildByName(name);
const lock = child('RewardLock');
if (unlocking && !complete) {
const position = lock.position.clone(), scaleX = lock.scaleX, scaleY = lock.scaleY, angle = lock.angle;
lock.active = true; lock.opacity = 255;
lock.getComponent(cc.Sprite).spriteFrame = this.unlockedFrame;
cc.Tween.stopAllByTarget(lock);
cc.tween(lock).to(.16, { scaleX: scaleX * 1.25, scaleY: scaleY * 1.25, angle: angle - 12 }).to(.16, { angle: angle + 8 })
.to(.25, { y: position.y + 30, opacity: 0 }).call(() => {
if (!cc.isValid(lock)) return;
lock.active = false; lock.position = position; lock.scaleX = scaleX; lock.scaleY = scaleY; lock.angle = angle; lock.opacity = 255;
}).start();
} else if (!unlocked) {
lock.active = !complete; lock.opacity = 255;
lock.getComponent(cc.Sprite).spriteFrame = this.lockedFrame;
} else if (!lock.getNumberOfRunningActions()) lock.active = false;
this.paintProgress(row, i, expired);
child('Reward').getComponent(CoinMadnessDigits).setText(String(stage.coins), 170);
['Task', 'CompletedTask'].forEach(name => child(name).getChildByName('Target').getComponent(CoinMadnessDigits).setText(String(stage.wins)));
});
this.focusGrowingTask();
this.tick();
}
private focusGrowingTask() {
if (this.progressFollowStopped) return;
const state = this.data.state;
const index = state.config.stages.findIndex((stage, i) => !state.claimed[i]
&& Math.min(this.targetWins, stage.wins) > this.taskProgress(stage.wins));
const row = this.content.children[index], viewport = this.content.parent;
const scroll = viewport.getComponent(cc.ScrollView);
if (!scroll) return;
const first = this.focusedProgressIndex < 0;
// Rewards already available at the saved display progress take priority on opening.
const firstOldReward = first ? state.config.stages.findIndex((stage, i) => !state.claimed[i]
&& !this.displayedClaims.has(i) && stage.wins <= this.shownWins) : -1;
if (firstOldReward >= 0) {
const offset = this.content.children[0].y - this.content.children[firstOldReward].y;
this.followOffset = Math.max(0, Math.min(offset, this.content.height - viewport.height));
this.focusedProgressIndex = firstOldReward;
scroll.stopAutoScroll();
scroll.scrollToOffset(cc.v2(0, this.followOffset), 0);
this.progressFollowStopped = true;
return;
}
// Keep the last available reward visible at the first row's authored position.
let lastClaimable = -1;
state.config.stages.forEach((stage, i) => {
if (!state.claimed[i] && !this.displayedClaims.has(i) && stage.wins <= this.targetWins) lastClaimable = i;
});
const topRow = this.content.children[0];
const followLimit = lastClaimable >= 0
? Math.max(0, topRow.y - this.content.children[lastClaimable].y) : 0;
// After replaying new progress, settle on the last available reward.
const finished = this.targetWins <= this.shownWins || !row;
const offset = finished ? followLimit : -row.y - viewport.height / 2;
const max = Math.min(followLimit, Math.max(0, this.content.height - viewport.height));
const destination = Math.max(0, Math.min(max, offset));
if (this.followOffset === destination) return;
this.followOffset = destination;
this.focusedProgressIndex = finished ? state.config.stages.length : index;
scroll.stopAutoScroll();
scroll.scrollToOffset(cc.v2(0, destination), first ? 0 : .18);
}
private followOffset = -1;
private stopProgressFollow() {
this.progressFollowStopped = true;
const scroll = this.content.parent.getComponent(cc.ScrollView);
if (scroll) scroll.stopAutoScroll();
}
private syncProgress(state: any) {
const key = 'goldMiner:shownProgress:' + (this.api.progressScope || 'preview') + ':' + state.config.periodId;
const target = Math.max(0, Number(state.totalWins) || 0);
if (key !== this.progressKey) {
this.progressKey = key;
this.focusedProgressIndex = -1;
this.followOffset = -1;
this.readyRows.clear();
const raw = cc.sys.localStorage.getItem(key), saved = Number(raw);
this.shownWins = raw !== null && raw !== '' && Number.isFinite(saved) && saved >= 0 ? Math.min(saved, target) : target;
this.targetWins = -1;
this.storedWins = -1;
this.progressSteps = [];
}
if (target === this.targetWins) return;
const previous = this.progressSteps.map(step => ({goal: step.goal, value: this.taskProgress(step.goal)}));
this.shownWins = Math.min(this.shownWins, target);
this.fromWins = this.shownWins; this.targetWins = target; this.progressElapsed = 0;
this.progressSteps = [];
this.progressDuration = 0;
state.config.stages.forEach((stage, i) => {
const old = previous.find(step => step.goal === stage.wins);
const end = Math.min(target, stage.wins);
const start = Math.min(old ? old.value : this.fromWins, end);
const duration = state.claimed[i] || stage.wins <= 0 ? 0 : .64 * Math.max(0, end - start) / stage.wins;
this.progressSteps.push({goal: stage.wins, start: state.claimed[i] ? end : start,
end, begin: this.progressDuration, duration});
this.progressDuration += duration;
});
this.progressDuration = Math.max(.001, this.progressDuration);
if (this.storedWins < 0) this.saveShownProgress();
}
private saveShownProgress() {
const value = Math.floor(this.shownWins);
if (value === this.storedWins) return;
cc.sys.localStorage.setItem(this.progressKey, String(value)); this.storedWins = value;
}
update(dt: number) {
if (!this.alive || this.closing || !this.data || this.shownWins >= this.targetWins) return;
this.progressElapsed += dt;
// Hold the last displayed progress for 0.4 seconds before increasing it.
const t = Math.min(1, Math.max(0, (this.progressElapsed - .4) / this.progressDuration));
this.shownWins = t === 1 ? this.targetWins : this.fromWins + (this.targetWins - this.fromWins) * t;
// Persist the shared baseline only after every task has shown its new progress.
if (t === 1) this.saveShownProgress();
const expired = Date.now() + this.offset >= this.data.state.config.endsAt;
this.focusGrowingTask();
this.content.children.forEach((row, i) => { if (row.active) this.paintProgress(row, i, expired); });
}
private paintProgress(row: cc.Node, i: number, expired: boolean) {
const state = this.data.state, stage = state.config.stages[i];
const complete = this.displayedClaims.has(i) || (!this.claimWaiters.has(i) && !!state.claimed[i]);
const earned = !expired && !complete && this.taskProgress(stage.wins) >= stage.wins;
const newlyEarned = earned && this.readyRows.get(row) === false && !this.claimWaiters.has(i);
this.readyRows.set(row, earned);
if (!earned) this.stopReadyPulse(row);
if (newlyEarned) this.playReadyPulse(row, () => this.paintProgress(row, i, expired));
const showReady = earned && !this.pendingReadyRows.has(row);
const child = (name: string) => row.getChildByName(name);
child('Normal').active = !showReady; child('Ready').active = showReady;
child('Check').active = complete; child('Coins').active = child('Reward').active = !complete;
const claim = child('Claim'); claim.active = showReady && !this.claimWaiters.has(i);
child('Task').active = !complete && !showReady; child('CompletedTask').active = complete;
const progress = child('Progress'); progress.active = !complete && !showReady;
const amount = this.taskProgress(stage.wins);
const fill = progress.getChildByName('Fill'); fill.active = amount > 0;
fill.getComponent(cc.Sprite).fillRange = stage.wins > 0 ? Math.min(1, amount / stage.wins) : 1;
progress.getChildByName('Expired').active = expired;
const digits = progress.getChildByName('Digits'); digits.active = !expired;
digits.getComponent(CoinMadnessDigits).setText(Math.floor(amount) + '/' + stage.wins);
}
private taskProgress(goal: number): number {
const step = this.progressSteps.find(item => item.goal === goal);
if (!step) return Math.min(this.shownWins, goal);
if (!step.duration) return step.end;
const t = Math.max(0, Math.min(1, (this.progressElapsed - .4 - step.begin) / step.duration));
return step.start + (step.end - step.start) * t;
}
private rowVisible(row: cc.Node): boolean {
const viewport = this.content.parent;
const p = viewport.convertToNodeSpaceAR(row.convertToWorldSpaceAR(cc.Vec2.ZERO));
const half = row.height * Math.abs(row.scaleY) * 1.08 / 2;
return p.y - half >= -viewport.height * viewport.anchorY
&& p.y + half <= viewport.height * (1 - viewport.anchorY);
}
private stopReadyPulse(row: cc.Node) {
this.pendingReadyRows.delete(row);
const base = this.readyScales.get(row);
if (!base) return;
cc.Tween.stopAllByTarget(row);
if (cc.isValid(row)) { row.scaleX = base.x; row.scaleY = base.y; }
this.readyScales.delete(row);
}
private playReadyPulse(row: cc.Node, reveal: () => void) {
this.stopReadyPulse(row);
if (!this.rowVisible(row)) return;
const base = cc.v2(row.scaleX, row.scaleY);
this.readyScales.set(row, base);
this.pendingReadyRows.add(row);
const size = (factor: number) => ({ scaleX: base.x * factor, scaleY: base.y * factor });
cc.tween(row).delay(.16).to(.12, size(1.08), { easing: 'sineOut' })
.to(.096, size(.1), { easing: 'sineInOut' })
.call(() => { this.pendingReadyRows.delete(row); reveal(); })
.to(.064, size(1.08), { easing: 'quadOut' })
.to(.112, size(1), { easing: 'sineInOut' })
.call(() => { this.readyScales.delete(row); }).start();
}
private showLockedToast() {
this.api.toast('购买礼包以解锁奖励');
}
private startClaimWaiting(stage: number) {
const row = this.content.children[stage];
if (!row || this.claimWaiters.has(stage)) return;
const claim = row.getChildByName('Claim');
const spinner = new cc.Node('ClaimWaiting'); row.addChild(spinner);
spinner.setPosition(claim.position);
spinner.addComponent(BrownLoadingSpinner);
this.claimWaiters.set(stage, spinner);
claim.active = false;
}
private stopClaimWaiting(stage: number) {
const spinner = this.claimWaiters.get(stage);
if (!spinner) return;
cc.Tween.stopAllByTarget(spinner);
if (cc.isValid(spinner)) { spinner.active = false; spinner.destroy(); }
this.claimWaiters.delete(stage);
}
private clearRewardAnimation(layer: cc.Node) {
if (!layer) return;
(layer.children || []).forEach(node => cc.Tween.stopAllByTarget(node));
cc.Tween.stopAllByTarget(layer);
if (cc.isValid(layer)) layer.destroy();
const index = this.rewardAnimations.indexOf(layer);
if (index >= 0) this.rewardAnimations[index] = null;
}
rewardSaved(stageIndex: number) {
this.stopClaimWaiting(stageIndex);
if (this.alive && !this.closing) {
this.displayedClaims.add(stageIndex);
this.render(this.data);
this.showRewardAnimation(stageIndex);
}
else if (this.api) this.api.rewardArrived();
}
private showRewardAnimation(stageIndex: number) {
const row = this.content.children[stageIndex];
if (!row) { this.api.rewardArrived(); return; }
this.stopReadyPulse(row);
const baseScale = this.rewardRowScales.has(row) ? this.rewardRowScales.get(row) : row.scale;
this.rewardRowScales.set(row, baseScale);
cc.Tween.stopAllByTarget(row);
row.scale = baseScale;
cc.tween(row).to(.12, { scale: baseScale * 1.035 }, { easing: 'quadOut' })
.to(.10, { scale: baseScale * .985 }, { easing: 'sineInOut' })
.to(.12, { scale: baseScale }, { easing: 'sineOut' })
.call(() => { this.rewardRowScales.delete(row); }).start();
const source = row.getChildByName('Coins');
const origin = this.node.convertToNodeSpaceAR(source.convertToWorldSpaceAR(cc.Vec2.ZERO));
let slot = this.rewardAnimations.indexOf(null);
if (slot < 0) slot = this.rewardAnimations.length;
const coinTarget = this.api.coinTarget && this.api.coinTarget();
const destination = coinTarget && cc.isValid(coinTarget) && coinTarget.activeInHierarchy
? this.node.convertToNodeSpaceAR(coinTarget.convertToWorldSpaceAR(cc.Vec2.ZERO))
: cc.v2(0, this.node.height / 2 - 100);
const layer = new cc.Node('RewardClaimAnimation');
this.rewardAnimations[slot] = layer;
this.node.addChild(layer); layer.zIndex = cc.macro.MAX_ZINDEX;
for (let i = 0; i < 16; i++) {
const coin = new cc.Node('FlyingCoin' + i); layer.addChild(coin);
coin.addComponent(cc.Sprite).spriteFrame = this.flyingCoin;
coin.setPosition(origin.x + (Math.random() - .5) * 120 + 50, origin.y + (Math.random() - .5) * 70 + 140); coin.scale = 0; coin.opacity = 255;
cc.tween(coin).delay(i * .025)
.to(.06, { scale: .75 }, { easing: 'backOut' })
.to(.175, { x: coin.x + (Math.random() - .5) * 180, y: coin.y + 80 + Math.random() * 80 }, { easing: 'sineOut' })
.to(.325, { x: destination.x, y: destination.y, scale: .35 }, { easing: 'sineIn' })
.call(() => { if (cc.isValid(coin)) coin.destroy(); }).start();
}
cc.tween(layer).delay(.935).call(() => {
if (this.alive && this.api) this.api.rewardArrived();
}).delay(.24).call(() => {
this.clearRewardAnimation(layer);
}).start();
}
private finishExpired() {
if (this.closing || !this.alive) return;
this.closing = true;
// Defer until initialization/render completes before replacing or releasing the prefab.
this.scheduleOnce(() => { if (this.alive && this.api) this.api.expired(); }, 0);
}
private tick() {
if (!this.data) return;
const remaining = Math.max(0, Math.ceil(((this.data.state.config.endsAt || 0) - Date.now() - this.offset) / 1000));
const pad = (value: number) => value < 10 ? '0' + value : String(value);
this.countdown.setText(pad(Math.floor(remaining / 3600)) + ':' + pad(Math.floor(remaining / 60) % 60) + ':' + pad(remaining % 60));
if (this.api.request && !remaining) { this.finishExpired(); return; }
}
private async run(action: string, body: any) {
if (this.busy || this.closing) return;
if (action === 'claim' && (this.claimWaiters.has(body.stage) || this.displayedClaims.has(body.stage))) return;
if (action !== 'claim') this.busy = true;
this.message.string = '请求中…';
if (action === 'claim') this.startClaimWaiting(body.stage);
{
let result;
try { result = await this.api.request(action, body); }
catch (error) {
result = { code: 0, msg: error.message || '请求失败,请点击重试' };
// Normal payment results are shown by the service; unexpected request failures still need visible feedback.
if (action === 'purchase') this.api.toast(result.msg);
}
if (!this.alive) return;
if (action !== 'claim') this.busy = false;
if (action === 'claim') this.stopClaimWaiting(body.stage);
if (result.code !== 1 && action !== 'purchase') this.api.toast(result.msg || '请求失败,请稍后重试');
this.message.string = result.msg || (result.data && result.data.status === 'locked' ? '尚未达到活动开放条件'
: result.data && result.data.status === 'unavailable' ? '活动已结束或尚未开放'
: result.data && result.data.status === 'purchase_disabled' ? '本期暂停购买,通关进度仍会累计' : '点击此处刷新活动');
if (result.data || action === 'claim') this.render(result.data || this.data);
return;
}
}
onDestroy() { this.alive = false; if (this.content && this.content.parent) this.content.parent.off(cc.Node.EventType.TOUCH_START, this.stopProgressFollow, this); this.readyScales.forEach((_, row) => this.stopReadyPulse(row)); this.claimWaiters.forEach((_, stage) => this.stopClaimWaiting(stage)); this.displayedClaims.clear(); cc.Tween.stopAllByTarget(this.purchaseButton); cc.Tween.stopAllByTarget(this.card); this.rewardRowScales.forEach((scale, row) => cc.Tween.stopAllByTarget(row)); this.rewardRowScales.clear(); this.rewardAnimations.slice().forEach(layer => this.clearRewardAnimation(layer)); this.rewardAnimations = []; this.unscheduleAllCallbacks(); this.boundRows = []; this.api = null; this.data = null; }
}