MatchMaster/assets/coin_madness/script/CoinMadnessPanel.ts

474 lines
28 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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; }
}