277 lines
11 KiB
TypeScript
277 lines
11 KiB
TypeScript
import { SevenDayActivityInfo, SevenDayReward, SevenDayRewardItem } from "./SevenDayGiftApi";
|
|
|
|
const { ccclass, property } = cc._decorator;
|
|
const TOTAL_DAYS = 7;
|
|
|
|
type ClaimHandler = (callback: (success: boolean, message?: string) => void) => 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;
|
|
|
|
private info: SevenDayActivityInfo = null;
|
|
private cards: cc.Node[] = [];
|
|
private claimHandler: ClaimHandler = null;
|
|
private claiming = false;
|
|
|
|
onLoad() {
|
|
if (!this.cacheView()) {
|
|
this.node.active = false;
|
|
return;
|
|
}
|
|
this.bindEvents();
|
|
}
|
|
|
|
public setData(info: SevenDayActivityInfo, claimHandler: ClaimHandler) {
|
|
this.info = info;
|
|
this.claimHandler = claimHandler;
|
|
this.refreshView();
|
|
}
|
|
|
|
public show() {
|
|
if (!this.info) return;
|
|
this.node.active = true;
|
|
this.refreshView();
|
|
this.node.stopAllActions();
|
|
this.node.opacity = 0;
|
|
this.node.scale = 0.94;
|
|
cc.tween(this.node).to(0.16, { opacity: 255, scale: 1 }).start();
|
|
}
|
|
|
|
public close(event?: cc.Event) {
|
|
if (event) event.stopPropagation();
|
|
this.node.stopAllActions();
|
|
cc.tween(this.node)
|
|
.to(0.12, { opacity: 0, scale: 0.96 })
|
|
.call(() => this.node.active = false)
|
|
.start();
|
|
}
|
|
|
|
private cacheView(): boolean {
|
|
const panel = this.node.getChildByName("panel");
|
|
const cards = panel && panel.getChildByName("cards");
|
|
const message = panel && panel.getChildByName("message");
|
|
if (!panel || !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() {
|
|
const panel = this.node.getChildByName("panel");
|
|
panel.getChildByName("close").on(cc.Node.EventType.TOUCH_END, this.close, this);
|
|
this.node.getChildByName("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);
|
|
|
|
const statusLabel = card.getChildByName("statusLabel").getComponent(cc.Label);
|
|
statusLabel.node.active = false;
|
|
statusLabel.string = claimed ? "已领取" : available ? "点击领取" : "未解锁";
|
|
statusLabel.node.color = available ? cc.color(255, 245, 110) : cc.Color.WHITE;
|
|
|
|
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.claiming = true;
|
|
this.claimHandler(() => {
|
|
this.claiming = false;
|
|
});
|
|
}
|
|
|
|
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");
|
|
const originalLabel = card.getChildByName("rewardLabel");
|
|
if (originalIcon) originalIcon.active = false;
|
|
if (originalLabel) originalLabel.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);
|
|
});
|
|
|
|
// 数量独立放在最上层,避免被后一个重叠图标遮住。
|
|
items.forEach((item, index) => {
|
|
const amountNode = new cc.Node("amount");
|
|
const amountWidth = item.type === "infinite_health" ? (count === 1 ? 112 : 88) : 72;
|
|
amountNode.setContentSize(amountWidth, 34);
|
|
amountNode.setPosition(
|
|
startX + step * index + iconSize * 0.2,
|
|
centerY - iconSize * 0.34
|
|
);
|
|
amountNode.zIndex = 100 + index;
|
|
container.addChild(amountNode);
|
|
|
|
const amountLabel = amountNode.addComponent(cc.Label);
|
|
amountLabel.string = this.formatRewardAmount(item);
|
|
amountLabel.fontSize = 22;
|
|
amountLabel.lineHeight = 28;
|
|
amountLabel.horizontalAlign = cc.Label.HorizontalAlign.CENTER;
|
|
amountLabel.verticalAlign = cc.Label.VerticalAlign.CENTER;
|
|
amountLabel.overflow = cc.Label.Overflow.SHRINK;
|
|
amountNode.color = cc.color(59, 42, 35);
|
|
|
|
const outline = amountNode.addComponent(cc.LabelOutline);
|
|
outline.color = cc.Color.WHITE;
|
|
outline.width = 4;
|
|
});
|
|
}
|
|
|
|
private formatRewardAmount(item: SevenDayRewardItem): string {
|
|
const rewardAmount = item.count !== undefined ? item.count : item.amount;
|
|
const amount = Math.max(0, Number(rewardAmount) || 0);
|
|
if (item.type === "infinite_health") {
|
|
if (amount % 3600 === 0) return "x" + (amount / 3600) + "小时";
|
|
if (amount % 60 === 0) return "x" + (amount / 60) + "分钟";
|
|
return "x" + amount + "秒";
|
|
}
|
|
return "x" + amount;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|