MatchMaster/assets/seven_day_gift/SevenDayGift.ts

282 lines
11 KiB
TypeScript

import { SevenDayActivityInfo, SevenDayReward, SevenDayRewardItem } from "./SevenDayGiftApi";
import SevenDayGiftRuntime from "./SevenDayGiftRuntime";
import ActivityPopupAnimator, { ActivityPopupAnimationType } from "../Script/ActivityPopupAnimator";
const { ccclass, property } = cc._decorator;
const TOTAL_DAYS = 7;
type ClaimHandler = (callback: (success: boolean) => void) => void;
type CloseHandler = () => void;
@ccclass
export default class SevenDayGift extends cc.Component {
@property(cc.SpriteFrame) cardBlue: cc.SpriteFrame = null;
@property(cc.SpriteFrame) cardYellow: cc.SpriteFrame = null;
@property(cc.SpriteFrame) cardSpecial: cc.SpriteFrame = null;
@property(cc.SpriteFrame) claimedOverlay: cc.SpriteFrame = null;
@property(cc.SpriteFrame) claimedBadge: cc.SpriteFrame = null;
@property(cc.SpriteFrame) catArt: cc.SpriteFrame = null;
@property(cc.SpriteFrame) catLocked: cc.SpriteFrame = null;
@property(cc.SpriteFrame) coinIcon: cc.SpriteFrame = null;
@property(cc.SpriteFrame) hammerIcon: cc.SpriteFrame = null;
@property(cc.SpriteFrame) freezeIcon: cc.SpriteFrame = null;
@property(cc.SpriteFrame) magicWandIcon: cc.SpriteFrame = null;
@property(cc.SpriteFrame) infiniteHealthIcon: cc.SpriteFrame = null;
@property({
type: cc.Enum(ActivityPopupAnimationType),
displayName: "弹窗动画",
tooltip: "活动页面打开和关闭时使用的动画"
})
popupAnimation: ActivityPopupAnimationType = ActivityPopupAnimationType.Q弹弹出;
private info: SevenDayActivityInfo = null;
private cards: cc.Node[] = [];
private claimHandler: ClaimHandler = null;
private claiming = false;
private panel: cc.Node = null;
private mask: cc.Node = null;
private closeNode: cc.Node = null;
private closeEnabled = true;
private closeHandler: CloseHandler = null;
private runtime: SevenDayGiftRuntime = null;
onLoad() {
if (!this.cacheView()) {
this.node.active = false;
return;
}
this.bindEvents();
this.runtime = new SevenDayGiftRuntime(this);
}
onDestroy() {
if (this.runtime) this.runtime.destroy();
this.runtime = null;
}
public initializeRuntime(info: SevenDayActivityInfo, closeHandler: CloseHandler) {
if (this.runtime) this.runtime.initialize(info, closeHandler);
}
public updateRuntimeInfo(info: SevenDayActivityInfo) {
if (this.runtime) this.runtime.updateInfo(info);
}
public setData(info: SevenDayActivityInfo, claimHandler: ClaimHandler) {
this.info = info;
this.claimHandler = claimHandler;
this.refreshView();
}
public setCloseHandler(closeHandler: CloseHandler) {
this.closeHandler = closeHandler;
}
public show(autoClaim: boolean = false) {
if (!this.info) return;
const shouldAutoClaim = autoClaim && !!this.info.canClaim && !this.info.todayClaimed;
this.setCloseEnabled(!shouldAutoClaim);
this.node.active = true;
this.refreshView();
this.node.opacity = 255;
this.node.scale = 1;
ActivityPopupAnimator.open(this.panel, this.mask, this.popupAnimation, () => {
if (shouldAutoClaim) this.requestCurrentReward();
});
}
public close(event?: cc.Event) {
if (event) event.stopPropagation();
if (!this.closeEnabled || this.claiming) return;
ActivityPopupAnimator.close(this.panel, this.mask, this.popupAnimation, () => {
this.node.active = false;
if (this.closeHandler) this.closeHandler();
});
}
private cacheView(): boolean {
this.panel = this.node.getChildByName("panel");
this.mask = this.node.getChildByName("mask");
this.closeNode = this.panel && this.panel.getChildByName("close");
const cards = this.panel && this.panel.getChildByName("cards");
const message = this.panel && this.panel.getChildByName("message");
if (!this.panel || !this.mask || !this.closeNode || !cards) {
cc.error("[SevenDayGift] prefab structure is incomplete");
return false;
}
if (message) message.active = false;
for (let day = 1; day <= TOTAL_DAYS; day++) {
const card = cards.getChildByName("giftDay" + day);
if (!card) {
cc.error("[SevenDayGift] missing card for day " + day);
return false;
}
this.cards.push(card);
}
return true;
}
private bindEvents() {
this.closeNode.on(cc.Node.EventType.TOUCH_END, this.close, this);
this.mask.on(cc.Node.EventType.TOUCH_END, this.close, this);
this.cards.forEach((card: cc.Node, index: number) => {
card.on(cc.Node.EventType.TOUCH_END, (event: cc.Event) => {
event.stopPropagation();
this.onCardClicked(index + 1);
}, this);
});
}
private refreshView() {
if (!this.info || this.cards.length !== TOTAL_DAYS) return;
for (let day = 1; day <= TOTAL_DAYS; day++) {
this.refreshCard(this.cards[day - 1], day, this.findReward(day));
}
}
private refreshCard(card: cc.Node, day: number, reward: SevenDayReward) {
const claimed = !!(reward && reward.claimed);
const available = !!this.info.canClaim && day === Number(this.info.nextRewardDay);
const special = day === TOTAL_DAYS;
const cardFrame = claimed && !special
? this.claimedOverlay
: special ? this.cardSpecial : available ? this.cardYellow : this.cardBlue;
this.setSprite(card, cardFrame);
// White is the neutral tint: keep the supplied art's original colors.
card.color = cc.Color.WHITE;
card.opacity = 255;
const dayLabel = card.getChildByName("dayLabel").getComponent(cc.Label);
dayLabel.string = day === TOTAL_DAYS ? "第7天" : "第" + day + "天";
dayLabel.fontSize = 32;
dayLabel.lineHeight = 38;
dayLabel.node.color = cc.Color.WHITE;
const dayOutline = dayLabel.node.getComponent(cc.LabelOutline);
if (dayOutline) {
dayOutline.color = special
? cc.color(238, 91, 126)
: available ? cc.color(242, 151, 0) : cc.color(0, 170, 182);
}
const items = reward && Array.isArray(reward.items) ? reward.items : [];
this.refreshRewardItems(card, items, special, available || claimed);
let badge = card.getChildByName("claimedBadge");
if (!badge) {
badge = new cc.Node("claimedBadge");
badge.addComponent(cc.Sprite);
card.addChild(badge);
badge.zIndex = 10;
}
badge.setContentSize(118, 99);
badge.setPosition(0, 0);
badge.active = claimed;
this.setSprite(badge, this.claimedBadge);
card.stopAllActions();
card.scale = 1;
if (available) {
cc.tween(card).repeatForever(
cc.tween().to(0.65, { scale: 1.04 }).to(0.65, { scale: 1 })
).start();
}
}
private onCardClicked(day: number) {
const reward = this.findReward(day);
if (reward && reward.claimed) {
return;
}
if (!this.info.canClaim || day !== Number(this.info.nextRewardDay)) {
return;
}
if (this.claiming || !this.claimHandler) return;
this.requestCurrentReward();
}
private requestCurrentReward() {
if (this.claiming || !this.claimHandler || !this.info || !this.info.canClaim) return;
this.claiming = true;
this.claimHandler((success: boolean) => {
this.claiming = false;
this.setCloseEnabled(true);
if (!success) cc.warn("[SevenDayGift] 自动领取未完成,可点击当天奖励重试");
});
}
private setCloseEnabled(enabled: boolean) {
this.closeEnabled = enabled;
if (this.closeNode) this.closeNode.active = enabled;
}
private findReward(day: number): SevenDayReward {
const rewards = Array.isArray(this.info.rewards) ? this.info.rewards : [];
for (let i = 0; i < rewards.length; i++) {
if (Number(rewards[i].day) === day) return rewards[i];
}
return null;
}
private refreshRewardItems(card: cc.Node, items: SevenDayRewardItem[], special: boolean, unlocked: boolean) {
const originalIcon = card.getChildByName("rewardIcon");
if (originalIcon) originalIcon.active = false;
let container = card.getChildByName("rewardItems");
if (!container) {
container = new cc.Node("rewardItems");
container.zIndex = 2;
card.addChild(container);
}
container.children.slice().forEach(child => {
child.removeFromParent();
child.destroy();
});
container.active = items.length > 0;
if (!items.length) return;
const count = items.length;
const availableWidth = special ? 220 : 184;
// 多奖励保持与单奖励相同尺寸,通过横向重叠收进卡片内部。
const iconSize = special ? 128 : 104;
const step = count === 1 ? 0 : Math.min(iconSize * 0.76, (availableWidth - iconSize) / (count - 1));
const totalWidth = iconSize + step * (count - 1);
const startX = -totalWidth / 2 + iconSize / 2;
const centerY = special ? 8 : 0;
container.setContentSize(availableWidth, special ? 210 : 130);
container.setPosition(0, special ? -8 : -4);
items.forEach((item, index) => {
const itemNode = new cc.Node("rewardItem" + index);
itemNode.setContentSize(iconSize, iconSize);
itemNode.setPosition(startX + step * index, centerY);
itemNode.zIndex = index;
container.addChild(itemNode);
const sprite = itemNode.addComponent(cc.Sprite);
sprite.sizeMode = cc.Sprite.SizeMode.CUSTOM;
sprite.spriteFrame = this.getRewardIcon(item, unlocked);
});
}
private getRewardIcon(item: SevenDayRewardItem, unlocked: boolean): cc.SpriteFrame {
switch (item.type) {
case "coin": return this.coinIcon;
case "hammer": return this.hammerIcon;
case "freeze": return this.freezeIcon;
case "magic_wand": return this.magicWandIcon;
case "infinite_health": return this.infiniteHealthIcon;
case "cat_skin": return unlocked ? this.catArt : this.catLocked;
default: return this.coinIcon;
}
}
private setSprite(node: cc.Node, frame: cc.SpriteFrame) {
if (!node || !frame) return;
const sprite = node.getComponent(cc.Sprite);
if (sprite) sprite.spriteFrame = frame;
}
}