322 lines
14 KiB
TypeScript
322 lines
14 KiB
TypeScript
import { starterPackRemaining } from "../../Script/module/Config/StarterPack";
|
|
|
|
const { ccclass, property } = cc._decorator;
|
|
|
|
/** Presentation for shop0917. Payment and reward delivery stay in shop.ts. */
|
|
@ccclass
|
|
export default class Shop0917View extends cc.Component {
|
|
@property(cc.ScrollView) scrollView: cc.ScrollView = null;
|
|
@property(cc.Node) carousel: cc.Node = null;
|
|
@property([cc.Node]) banners: cc.Node[] = [];
|
|
@property([cc.Node]) dots: cc.Node[] = [];
|
|
@property([cc.Node]) firstPurchase: cc.Node[] = [];
|
|
@property(cc.Node) coin: cc.Node = null;
|
|
@property(cc.Node) health: cc.Node = null;
|
|
@property(cc.Node) monthPrivilege: cc.Node = null;
|
|
@property(cc.Node) monthRemaining: cc.Node = null;
|
|
@property(cc.Node) monthDays: cc.Node = null;
|
|
@property(cc.Sprite) monthButton: cc.Sprite = null;
|
|
@property([cc.SpriteFrame]) countFrames: cc.SpriteFrame[] = [];
|
|
@property([cc.SpriteFrame]) healthFrames: cc.SpriteFrame[] = [];
|
|
@property([cc.SpriteFrame]) timeFrames: cc.SpriteFrame[] = [];
|
|
@property([cc.SpriteFrame]) dayFrames: cc.SpriteFrame[] = [];
|
|
@property(cc.SpriteFrame) colonFrame: cc.SpriteFrame = null;
|
|
@property(cc.SpriteFrame) monthOpen: cc.SpriteFrame = null;
|
|
@property(cc.SpriteFrame) monthRenew: cc.SpriteFrame = null;
|
|
@property(cc.SpriteFrame) dotNormal: cc.SpriteFrame = null;
|
|
@property(cc.SpriteFrame) dotSelected: cc.SpriteFrame = null;
|
|
|
|
private selectedBanner: number = 1;
|
|
private touching: boolean = false;
|
|
private dragged: boolean = false;
|
|
private touchStartX: number = 0;
|
|
private lastTouchX: number = 0;
|
|
private touchId: number = null;
|
|
private dragStartOffset: number = 0;
|
|
private carouselOffset: number = 0;
|
|
private snapping: boolean = false;
|
|
private snapFrom: number = 0;
|
|
private snapTo: number = 0;
|
|
private snapTime: number = 0;
|
|
private snapDuration: number = 0;
|
|
private snapBanner: number = 1;
|
|
private renderedValues = new Map<cc.Node, string>();
|
|
|
|
onLoad(): void {
|
|
this.node.on(cc.Node.EventType.SIZE_CHANGED, this.resize, this);
|
|
this.carousel.on(cc.Node.EventType.TOUCH_START, this.onTouchStart, this, true);
|
|
this.carousel.on(cc.Node.EventType.TOUCH_MOVE, this.onTouchMove, this, true);
|
|
this.carousel.on(cc.Node.EventType.TOUCH_END, this.onTouchEnd, this, true);
|
|
this.carousel.on(cc.Node.EventType.TOUCH_CANCEL, this.onTouchCancel, this, true);
|
|
}
|
|
|
|
onEnable(): void {
|
|
this.restartAutoAdvance();
|
|
this.scheduleOnce(() => {
|
|
this.resize();
|
|
this.scrollView.scrollToTop(0);
|
|
});
|
|
}
|
|
|
|
onDisable(): void {
|
|
this.unschedule(this.advanceBanner);
|
|
this.touching = false;
|
|
this.dragged = false;
|
|
this.touchId = null;
|
|
this.layoutBanners();
|
|
this.setBannerButtonsEnabled(true);
|
|
}
|
|
|
|
private resize(): void {
|
|
const widget = this.scrollView.node.getComponent(cc.Widget);
|
|
widget.updateAlignment();
|
|
this.scrollView.content.parent.getComponent(cc.Widget).updateAlignment();
|
|
this.node.getChildByName("Top").getComponent(cc.Widget).updateAlignment();
|
|
["Background", "Loading", "ConfirmBox"].forEach(name => {
|
|
this.node.getChildByName(name).getComponent(cc.Widget).updateAlignment();
|
|
});
|
|
this.fitSprite(this.node.getChildByName("Background"));
|
|
}
|
|
|
|
fitSprite(layout: cc.Node, frame?: cc.SpriteFrame): void {
|
|
const sprite = layout.getComponentInChildren(cc.Sprite);
|
|
if (frame) sprite.spriteFrame = frame;
|
|
sprite.sizeMode = cc.Sprite.SizeMode.TRIMMED;
|
|
const rect = sprite.spriteFrame.getRect();
|
|
sprite.node.setScale(layout.width / rect.width, layout.height / rect.height);
|
|
}
|
|
|
|
setCoin(value: number): void {
|
|
this.renderNumber(this.coin, String(Math.max(0, Math.floor(Number(value) || 0))), this.countFrames, 37, 190);
|
|
}
|
|
|
|
setHealth(value: number): void {
|
|
this.renderNumber(this.health, String(Math.max(0, Math.floor(Number(value) || 0))), this.healthFrames, 39, 72);
|
|
}
|
|
|
|
setTime(target: cc.Node, value: string): void {
|
|
this.renderNumber(target, value, this.timeFrames, 32, 190);
|
|
}
|
|
|
|
private renderNumber(target: cc.Node, value: string, frames: cc.SpriteFrame[], height: number, maxWidth: number): void {
|
|
if (this.renderedValues.get(target) === value) return;
|
|
this.renderedValues.set(target, value);
|
|
const oldChildren = target.children.slice();
|
|
target.removeAllChildren();
|
|
oldChildren.forEach(child => child.destroy());
|
|
const glyphs = value.split("").map(char => char === ":" ? this.colonFrame : frames[Number(char)]);
|
|
const widths = glyphs.map(frame => {
|
|
const size = frame.getRect();
|
|
return height * size.width / size.height;
|
|
});
|
|
const gap = -1;
|
|
const total = widths.reduce((sum, width) => sum + width, 0) + gap * (widths.length - 1);
|
|
const scale = Math.min(1, maxWidth / total);
|
|
let x = -total / 2;
|
|
glyphs.forEach((frame, index) => {
|
|
const digit = new cc.Node("glyph_" + index);
|
|
const sprite = digit.addComponent(cc.Sprite);
|
|
sprite.spriteFrame = frame;
|
|
sprite.sizeMode = cc.Sprite.SizeMode.TRIMMED;
|
|
const rect = frame.getRect();
|
|
digit.setScale(widths[index] * scale / rect.width, height * scale / rect.height);
|
|
digit.setPosition((x + widths[index] / 2) * scale, 0);
|
|
target.addChild(digit);
|
|
x += widths[index] + gap;
|
|
});
|
|
}
|
|
|
|
refreshFirstPurchase(data: { [key: string]: boolean }): void {
|
|
this.firstPurchase.forEach((node, index) => {
|
|
node.active = !!data && data["gold_" + (index + 1)] === false;
|
|
});
|
|
}
|
|
|
|
refreshMonth(active: boolean, days: number): void {
|
|
this.monthPrivilege.active = !active;
|
|
this.monthRemaining.active = active;
|
|
this.fitSprite(this.monthButton.node.parent, active ? this.monthRenew : this.monthOpen);
|
|
if (active) {
|
|
this.renderNumber(this.monthDays, String(Math.max(0, Math.ceil(Number(days) || 0))), this.dayFrames, 32, 80);
|
|
}
|
|
}
|
|
|
|
refreshBanners(jungleAvailable: boolean): void {
|
|
this.banners[0].active = starterPackRemaining() > 0;
|
|
this.banners[2].active = jungleAvailable;
|
|
if (!this.banners[this.selectedBanner].active) this.selectedBanner = 1;
|
|
this.layoutBanners();
|
|
}
|
|
|
|
private layoutBanners(): void {
|
|
this.snapping = false;
|
|
this.carouselOffset = 0;
|
|
this.dragStartOffset = 0;
|
|
this.touchStartX = this.lastTouchX;
|
|
this.positionBanners();
|
|
this.updateDots();
|
|
}
|
|
|
|
private positionBanners(): void {
|
|
const available = this.banners.filter(banner => banner.active);
|
|
const selected = available.indexOf(this.banners[this.selectedBanner]);
|
|
available.forEach((banner, index) => {
|
|
let x = (index - selected) * 900 + this.carouselOffset;
|
|
if (available.length === 3) {
|
|
// Recycle only outside the mask, so both peeking neighbours move
|
|
// continuously through the first/last-page boundary.
|
|
while (x > 1350) x -= 2700;
|
|
while (x < -1350) x += 2700;
|
|
}
|
|
banner.x = x + 5;
|
|
});
|
|
}
|
|
|
|
private updateDots(): void {
|
|
const available = this.banners.filter(banner => banner.active);
|
|
this.dots.forEach((dot, index) => {
|
|
dot.active = available.length > 1 && this.banners[index].active;
|
|
if (!dot.active) return;
|
|
dot.x = (available.indexOf(this.banners[index]) - (available.length - 1) / 2) * 47;
|
|
const selected = index === this.selectedBanner;
|
|
dot.setContentSize(selected ? 38 : 20, selected ? 38 : 20);
|
|
this.fitSprite(dot, selected ? this.dotSelected : this.dotNormal);
|
|
});
|
|
}
|
|
|
|
selectBanner(_event: cc.Event, value: string): void {
|
|
const index = Number(value);
|
|
if (!this.touching && this.banners[index] && this.banners[index].active) {
|
|
this.snapToBanner(index);
|
|
this.restartAutoAdvance();
|
|
}
|
|
}
|
|
|
|
private advanceBanner(): void {
|
|
if (this.touching || this.snapping) return;
|
|
const available = this.banners.filter(banner => banner.active);
|
|
const current = available.indexOf(this.banners[this.selectedBanner]);
|
|
if (available.length > 1) {
|
|
this.snapToBanner(this.banners.indexOf(available[(current + 1) % available.length]));
|
|
}
|
|
}
|
|
|
|
private restartAutoAdvance(): void {
|
|
this.unschedule(this.advanceBanner);
|
|
this.schedule(this.advanceBanner, 5);
|
|
}
|
|
|
|
private snapToBanner(index: number): void {
|
|
const available = this.banners.filter(banner => banner.active);
|
|
let steps = available.indexOf(this.banners[index]) - available.indexOf(this.banners[this.selectedBanner]);
|
|
if (available.length === 3) {
|
|
if (steps > 1) steps -= 3;
|
|
if (steps < -1) steps += 3;
|
|
}
|
|
this.snapBanner = index;
|
|
this.snapFrom = this.carouselOffset;
|
|
this.snapTo = -steps * 900;
|
|
this.snapTime = 0;
|
|
this.snapDuration = Math.min(.32, Math.max(.16, Math.abs(this.snapTo - this.snapFrom) / 2800));
|
|
this.snapping = true;
|
|
}
|
|
|
|
update(dt: number): void {
|
|
if (!this.snapping) return;
|
|
this.snapTime += dt;
|
|
const t = Math.min(1, this.snapTime / this.snapDuration);
|
|
const eased = 1 - Math.pow(1 - t, 3);
|
|
this.carouselOffset = this.snapFrom + (this.snapTo - this.snapFrom) * eased;
|
|
this.positionBanners();
|
|
if (t === 1) {
|
|
this.selectedBanner = this.snapBanner;
|
|
this.layoutBanners();
|
|
}
|
|
}
|
|
|
|
private onTouchStart(event: cc.Event.EventTouch): void {
|
|
if (this.touching) return;
|
|
this.unschedule(this.advanceBanner);
|
|
this.snapping = false;
|
|
this.touching = true;
|
|
this.dragged = false;
|
|
this.touchId = event.getID();
|
|
this.touchStartX = this.lastTouchX = this.carousel.convertToNodeSpaceAR(event.getLocation()).x;
|
|
this.dragStartOffset = this.carouselOffset;
|
|
}
|
|
|
|
private onTouchMove(event: cc.Event.EventTouch): void {
|
|
if (!this.touching || event.getID() !== this.touchId) return;
|
|
this.lastTouchX = this.carousel.convertToNodeSpaceAR(event.getLocation()).x;
|
|
const distance = this.lastTouchX - this.touchStartX;
|
|
if (Math.abs(distance) > 12) {
|
|
this.dragged = true;
|
|
this.setBannerButtonsEnabled(false);
|
|
}
|
|
if (!this.dragged) return;
|
|
const available = this.banners.filter(banner => banner.active);
|
|
const current = available.indexOf(this.banners[this.selectedBanner]);
|
|
let offset = this.dragStartOffset + distance;
|
|
if (available.length === 1) offset = 0;
|
|
else if (available.length === 2) {
|
|
const min = -(available.length - 1 - current) * 900;
|
|
const max = current * 900;
|
|
if (offset < min) offset = min + (offset - min) * .25;
|
|
if (offset > max) offset = max + (offset - max) * .25;
|
|
}
|
|
this.carouselOffset = Math.max(-900, Math.min(900, offset));
|
|
this.positionBanners();
|
|
}
|
|
|
|
private onTouchEnd(event: cc.Event.EventTouch): void {
|
|
this.onTouchCancel(event);
|
|
}
|
|
|
|
private onTouchCancel(event?: cc.Event.EventTouch): void {
|
|
if (!this.touching || (event && event.getID() !== this.touchId)) return;
|
|
// Creator emits TOUCH_CANCEL when a drag finishes outside its starting
|
|
// button, even though it is still inside the carousel.
|
|
const available = this.banners.filter(banner => banner.active);
|
|
let target = available.indexOf(this.banners[this.selectedBanner]);
|
|
if (event && Math.abs(this.carouselOffset) > 90) target += this.carouselOffset < 0 ? 1 : -1;
|
|
target = available.length === 3 ? (target + 3) % 3 : Math.max(0, Math.min(available.length - 1, target));
|
|
this.snapToBanner(this.banners.indexOf(available[target]));
|
|
this.touching = false;
|
|
this.touchId = null;
|
|
this.restartAutoAdvance();
|
|
// Restore after Button's touch-end dispatch so a swipe cannot purchase.
|
|
this.scheduleOnce(() => {
|
|
if (!this.touching) this.setBannerButtonsEnabled(true);
|
|
});
|
|
}
|
|
|
|
private setBannerButtonsEnabled(enabled: boolean): void {
|
|
this.carousel.getComponentsInChildren(cc.Button).forEach(button => button.interactable = enabled);
|
|
}
|
|
|
|
scrollToCoins(): void {
|
|
this.scrollView.scrollToOffset(cc.v2(0, Math.min(1620, this.scrollView.getMaxScrollOffset().y)), .25);
|
|
}
|
|
|
|
openStarter(event: cc.Event): void {
|
|
const home: any = this.node.parent && this.node.parent.getComponent("JiaZai");
|
|
const game: any = this.node.parent && this.node.parent.getComponent("SceneManager");
|
|
if (home) home.openStarter_pack(event, "shop_page");
|
|
else if (game) game.openStarterPack();
|
|
}
|
|
|
|
openHealth(event: cc.Event): void {
|
|
const home: any = this.node.parent && this.node.parent.getComponent("JiaZai");
|
|
if (home) home.openHeath(event, "shop_page");
|
|
}
|
|
|
|
onDestroy(): void {
|
|
this.node.off(cc.Node.EventType.SIZE_CHANGED, this.resize, this);
|
|
this.carousel.off(cc.Node.EventType.TOUCH_START, this.onTouchStart, this, true);
|
|
this.carousel.off(cc.Node.EventType.TOUCH_MOVE, this.onTouchMove, this, true);
|
|
this.carousel.off(cc.Node.EventType.TOUCH_END, this.onTouchEnd, this, true);
|
|
this.carousel.off(cc.Node.EventType.TOUCH_CANCEL, this.onTouchCancel, this, true);
|
|
this.renderedValues.clear();
|
|
}
|
|
}
|