first commit
This commit is contained in:
@@ -0,0 +1,279 @@
|
||||
/**
|
||||
* ShopScene.js
|
||||
* Simplified shop scene for monetization-lite.
|
||||
* Shows 3 products: Ad-Free (¥18), Gold Pack (¥6), Newcomer Pack (¥1).
|
||||
*/
|
||||
|
||||
const {
|
||||
SCREEN_WIDTH,
|
||||
SCREEN_HEIGHT,
|
||||
COLORS,
|
||||
SCENE,
|
||||
} = require('../base/GameGlobal');
|
||||
const { t } = require('../i18n/I18n');
|
||||
|
||||
// Layout
|
||||
const CARD_W = Math.min(SCREEN_WIDTH * 0.85, 320);
|
||||
const CARD_H = 70;
|
||||
const CARD_GAP = 12;
|
||||
const CARD_X = (SCREEN_WIDTH - CARD_W) / 2;
|
||||
const CARDS_START_Y = SCREEN_HEIGHT * 0.25;
|
||||
|
||||
const ShopScene = {
|
||||
_buttons: {},
|
||||
_message: '',
|
||||
_messageTimer: 0,
|
||||
|
||||
enter() {
|
||||
this._buttons = {};
|
||||
this._message = '';
|
||||
this._messageTimer = 0;
|
||||
this._calculateLayout();
|
||||
},
|
||||
|
||||
exit() {},
|
||||
|
||||
_calculateLayout() {
|
||||
let y = CARDS_START_Y;
|
||||
|
||||
// Ad-Free card
|
||||
this._buttons.adFree = { x: CARD_X, y, w: CARD_W, h: CARD_H };
|
||||
y += CARD_H + CARD_GAP;
|
||||
|
||||
// Gold Pack card
|
||||
this._buttons.goldPack = { x: CARD_X, y, w: CARD_W, h: CARD_H };
|
||||
y += CARD_H + CARD_GAP;
|
||||
|
||||
// Newcomer Pack card (only if available)
|
||||
this._buttons.newcomerPack = { x: CARD_X, y, w: CARD_W, h: CARD_H };
|
||||
y += CARD_H + CARD_GAP + 10;
|
||||
|
||||
// Back button
|
||||
const backW = 100;
|
||||
const backH = 36;
|
||||
this._buttons.back = { x: (SCREEN_WIDTH - backW) / 2, y, w: backW, h: backH };
|
||||
},
|
||||
|
||||
update(dt) {
|
||||
if (this._messageTimer > 0) {
|
||||
this._messageTimer -= dt;
|
||||
if (this._messageTimer <= 0) {
|
||||
this._message = '';
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
render(ctx) {
|
||||
// Background
|
||||
ctx.fillStyle = COLORS.MENU_BG;
|
||||
ctx.fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
|
||||
|
||||
// Title
|
||||
ctx.fillStyle = COLORS.MENU_TITLE;
|
||||
ctx.font = 'bold 24px Arial';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.fillText(t('shop.title') || 'Shop', SCREEN_WIDTH / 2, SCREEN_HEIGHT * 0.08);
|
||||
|
||||
// Gold balance
|
||||
const gold = GameGlobal.currencyManager ? GameGlobal.currencyManager.getGold() : 0;
|
||||
ctx.fillStyle = '#FFD700';
|
||||
ctx.font = 'bold 16px Arial';
|
||||
ctx.fillText(`🪙 ${gold}`, SCREEN_WIDTH / 2, SCREEN_HEIGHT * 0.16);
|
||||
|
||||
const pm = GameGlobal.paymentManager;
|
||||
|
||||
// Ad-Free card
|
||||
const adFreePurchased = pm && pm.isAdFreePurchased();
|
||||
this._renderProductCard(ctx, this._buttons.adFree,
|
||||
t('shop.adFree') || 'Remove Ads',
|
||||
t('shop.adFreeDesc') || 'Permanently remove interstitial ads',
|
||||
adFreePurchased ? (t('shop.adFreeOwned') || 'Owned') : '¥18',
|
||||
adFreePurchased
|
||||
);
|
||||
|
||||
// Gold Pack card
|
||||
this._renderProductCard(ctx, this._buttons.goldPack,
|
||||
t('shop.goldPack') || 'Gold Pack',
|
||||
t('shop.goldPackDesc') || '1000 Gold',
|
||||
'¥6',
|
||||
false
|
||||
);
|
||||
|
||||
// Newcomer Pack card
|
||||
const newcomerAvailable = pm && pm.isNewcomerPackAvailable();
|
||||
if (newcomerAvailable) {
|
||||
const remainingMs = pm.getNewcomerPackRemainingMs();
|
||||
const hours = Math.floor(remainingMs / (60 * 60 * 1000));
|
||||
const mins = Math.floor((remainingMs % (60 * 60 * 1000)) / (60 * 1000));
|
||||
const timeStr = `${hours}h ${mins}m`;
|
||||
|
||||
this._renderProductCard(ctx, this._buttons.newcomerPack,
|
||||
t('shop.newcomerPack') || 'Newcomer Pack',
|
||||
`${t('shop.newcomerPackDesc') || '500 Gold'} (⏰ ${timeStr})`,
|
||||
'¥1',
|
||||
false,
|
||||
'#FF9800' // highlight color for limited time
|
||||
);
|
||||
} else {
|
||||
// Show expired/purchased state
|
||||
this._renderProductCard(ctx, this._buttons.newcomerPack,
|
||||
t('shop.newcomerPack') || 'Newcomer Pack',
|
||||
t('shop.newcomerExpired') || 'Expired',
|
||||
'--',
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
// Back button
|
||||
this._renderButton(ctx, this._buttons.back, t('common.back') || '← Back', '#666666');
|
||||
|
||||
// Toast message
|
||||
if (this._message) {
|
||||
ctx.fillStyle = 'rgba(0,0,0,0.7)';
|
||||
const msgW = 200;
|
||||
const msgH = 36;
|
||||
ctx.fillRect(SCREEN_WIDTH / 2 - msgW / 2, SCREEN_HEIGHT * 0.92 - msgH / 2, msgW, msgH);
|
||||
ctx.fillStyle = '#FFFFFF';
|
||||
ctx.font = '14px Arial';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillText(this._message, SCREEN_WIDTH / 2, SCREEN_HEIGHT * 0.92);
|
||||
}
|
||||
},
|
||||
|
||||
_renderProductCard(ctx, rect, title, desc, priceLabel, disabled, highlightColor) {
|
||||
if (!rect) return;
|
||||
|
||||
// Card background
|
||||
ctx.fillStyle = disabled ? 'rgba(255,255,255,0.02)' : 'rgba(255,255,255,0.06)';
|
||||
ctx.strokeStyle = highlightColor || (disabled ? '#333333' : '#555555');
|
||||
ctx.lineWidth = highlightColor ? 2 : 1;
|
||||
|
||||
const r = 8;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(rect.x + r, rect.y);
|
||||
ctx.lineTo(rect.x + rect.w - r, rect.y);
|
||||
ctx.arcTo(rect.x + rect.w, rect.y, rect.x + rect.w, rect.y + r, r);
|
||||
ctx.lineTo(rect.x + rect.w, rect.y + rect.h - r);
|
||||
ctx.arcTo(rect.x + rect.w, rect.y + rect.h, rect.x + rect.w - r, rect.y + rect.h, r);
|
||||
ctx.lineTo(rect.x + r, rect.y + rect.h);
|
||||
ctx.arcTo(rect.x, rect.y + rect.h, rect.x, rect.y + rect.h - r, r);
|
||||
ctx.lineTo(rect.x, rect.y + r);
|
||||
ctx.arcTo(rect.x, rect.y, rect.x + r, rect.y, r);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
|
||||
// Title (left aligned)
|
||||
ctx.fillStyle = disabled ? '#666666' : '#FFFFFF';
|
||||
ctx.font = 'bold 15px Arial';
|
||||
ctx.textAlign = 'left';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.fillText(title, rect.x + 15, rect.y + rect.h * 0.35);
|
||||
|
||||
// Description
|
||||
ctx.fillStyle = disabled ? '#444444' : '#AAAAAA';
|
||||
ctx.font = '11px Arial';
|
||||
ctx.fillText(desc, rect.x + 15, rect.y + rect.h * 0.65);
|
||||
|
||||
// Price (right aligned)
|
||||
ctx.fillStyle = disabled ? '#444444' : (highlightColor || '#FFD700');
|
||||
ctx.font = 'bold 16px Arial';
|
||||
ctx.textAlign = 'right';
|
||||
ctx.fillText(priceLabel, rect.x + rect.w - 15, rect.y + rect.h / 2);
|
||||
},
|
||||
|
||||
_renderButton(ctx, rect, label, color) {
|
||||
if (!rect) return;
|
||||
|
||||
ctx.fillStyle = color;
|
||||
const r = 6;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(rect.x + r, rect.y);
|
||||
ctx.lineTo(rect.x + rect.w - r, rect.y);
|
||||
ctx.arcTo(rect.x + rect.w, rect.y, rect.x + rect.w, rect.y + r, r);
|
||||
ctx.lineTo(rect.x + rect.w, rect.y + rect.h - r);
|
||||
ctx.arcTo(rect.x + rect.w, rect.y + rect.h, rect.x + rect.w - r, rect.y + rect.h, r);
|
||||
ctx.lineTo(rect.x + r, rect.y + rect.h);
|
||||
ctx.arcTo(rect.x, rect.y + rect.h, rect.x, rect.y + rect.h - r, r);
|
||||
ctx.lineTo(rect.x, rect.y + r);
|
||||
ctx.arcTo(rect.x, rect.y, rect.x + r, rect.y, r);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
|
||||
ctx.fillStyle = '#FFFFFF';
|
||||
ctx.font = 'bold 14px Arial';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.fillText(label, rect.x + rect.w / 2, rect.y + rect.h / 2);
|
||||
},
|
||||
|
||||
_hitTest(tx, ty, rect) {
|
||||
if (!rect) return false;
|
||||
return tx >= rect.x && tx <= rect.x + rect.w && ty >= rect.y && ty <= rect.y + rect.h;
|
||||
},
|
||||
|
||||
_showMessage(msg) {
|
||||
this._message = msg;
|
||||
this._messageTimer = 2;
|
||||
},
|
||||
|
||||
handleTouch(eventType, e) {
|
||||
if (eventType !== 'touchstart') return;
|
||||
|
||||
const touch = e.touches[0];
|
||||
const tx = touch.clientX;
|
||||
const ty = touch.clientY;
|
||||
const pm = GameGlobal.paymentManager;
|
||||
|
||||
// Ad-Free
|
||||
if (this._hitTest(tx, ty, this._buttons.adFree)) {
|
||||
if (pm && !pm.isAdFreePurchased()) {
|
||||
pm.purchaseAdFree((result) => {
|
||||
if (result.success) {
|
||||
this._showMessage('✅ Ad-Free activated!');
|
||||
} else {
|
||||
this._showMessage(result.error || 'Purchase failed');
|
||||
}
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Gold Pack
|
||||
if (this._hitTest(tx, ty, this._buttons.goldPack)) {
|
||||
if (pm) {
|
||||
pm.purchaseGoldPack((result) => {
|
||||
if (result.success) {
|
||||
this._showMessage('✅ +1000 Gold!');
|
||||
} else {
|
||||
this._showMessage(result.error || 'Purchase failed');
|
||||
}
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Newcomer Pack
|
||||
if (this._hitTest(tx, ty, this._buttons.newcomerPack)) {
|
||||
if (pm && pm.isNewcomerPackAvailable()) {
|
||||
pm.purchaseNewcomerPack((result) => {
|
||||
if (result.success) {
|
||||
this._showMessage('✅ +500 Gold!');
|
||||
} else {
|
||||
this._showMessage(result.error || 'Purchase failed');
|
||||
}
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Back
|
||||
if (this._hitTest(tx, ty, this._buttons.back)) {
|
||||
GameGlobal.sceneManager.switchTo(SCENE.MENU);
|
||||
return;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = ShopScene;
|
||||
Reference in New Issue
Block a user