253 lines
7.6 KiB
JavaScript
253 lines
7.6 KiB
JavaScript
/**
|
|
* BuffSelectScene.js
|
|
* Pre-game buff selection screen.
|
|
* Allows players to purchase Shield (100g) and/or Double Fire (150g)
|
|
* before entering a game level.
|
|
*/
|
|
|
|
const {
|
|
SCREEN_WIDTH,
|
|
SCREEN_HEIGHT,
|
|
COLORS,
|
|
SCENE,
|
|
} = require('../base/GameGlobal');
|
|
const { t } = require('../i18n/I18n');
|
|
const BuffManager = require('../managers/BuffManager');
|
|
|
|
const BUFF_TYPE = BuffManager.BUFF_TYPE;
|
|
const BUFF_COST = BuffManager.BUFF_COST;
|
|
|
|
// Layout constants
|
|
const CARD_W = Math.min(SCREEN_WIDTH * 0.38, 160);
|
|
const CARD_H = Math.min(SCREEN_HEIGHT * 0.3, 140);
|
|
const CARD_GAP = 20;
|
|
const CARD_Y = SCREEN_HEIGHT * 0.3;
|
|
|
|
const BuffSelectScene = {
|
|
_gameParams: null, // params to pass to GameScene
|
|
_buttons: {},
|
|
_buffManager: null,
|
|
|
|
enter(params) {
|
|
this._gameParams = params || {};
|
|
this._buffManager = GameGlobal.buffManager;
|
|
this._buttons = {};
|
|
|
|
// Clear any previous buffs
|
|
if (this._buffManager) {
|
|
this._buffManager.clearBuffs();
|
|
}
|
|
|
|
this._calculateLayout();
|
|
},
|
|
|
|
exit() {},
|
|
|
|
_calculateLayout() {
|
|
const cx = SCREEN_WIDTH / 2;
|
|
|
|
// Two buff cards side by side
|
|
const card1X = cx - CARD_W - CARD_GAP / 2;
|
|
const card2X = cx + CARD_GAP / 2;
|
|
|
|
this._buttons = {
|
|
shield: { x: card1X, y: CARD_Y, w: CARD_W, h: CARD_H, type: BUFF_TYPE.SHIELD },
|
|
doubleFire: { x: card2X, y: CARD_Y, w: CARD_W, h: CARD_H, type: BUFF_TYPE.DOUBLE_FIRE },
|
|
};
|
|
|
|
// Start/Skip button
|
|
const btnW = Math.min(SCREEN_WIDTH * 0.5, 200);
|
|
const btnH = 42;
|
|
const btnY = CARD_Y + CARD_H + 30;
|
|
this._buttons.start = { x: cx - btnW / 2, y: btnY, w: btnW, h: btnH };
|
|
this._buttons.skip = { x: cx - btnW / 2, y: btnY + btnH + 12, w: btnW, h: btnH };
|
|
},
|
|
|
|
update(dt) {},
|
|
|
|
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('buff.title') || 'Pre-Game Buffs', SCREEN_WIDTH / 2, SCREEN_HEIGHT * 0.12);
|
|
|
|
// 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.2);
|
|
|
|
// Render buff cards
|
|
this._renderBuffCard(ctx, this._buttons.shield,
|
|
t('buff.shield') || '🛡️ Shield',
|
|
t('buff.shieldDesc') || 'Start with a shield layer',
|
|
BUFF_COST[BUFF_TYPE.SHIELD],
|
|
BUFF_TYPE.SHIELD
|
|
);
|
|
|
|
this._renderBuffCard(ctx, this._buttons.doubleFire,
|
|
t('buff.doubleFire') || '🔥 Double Fire',
|
|
t('buff.doubleFireDesc') || '2x bullet power for 10s',
|
|
BUFF_COST[BUFF_TYPE.DOUBLE_FIRE],
|
|
BUFF_TYPE.DOUBLE_FIRE
|
|
);
|
|
|
|
// Start button (if any buff purchased)
|
|
const hasBuffs = this._buffManager && this._buffManager.getActiveBuffs().length > 0;
|
|
if (hasBuffs) {
|
|
this._renderButton(ctx, this._buttons.start, t('buff.start') || 'Start Game', '#4CAF50');
|
|
}
|
|
|
|
// Skip button
|
|
this._renderButton(ctx, this._buttons.skip, t('buff.skip') || 'Skip →', '#666666');
|
|
},
|
|
|
|
_renderBuffCard(ctx, rect, title, desc, cost, buffType) {
|
|
if (!rect) return;
|
|
|
|
const purchased = this._buffManager && this._buffManager.hasBuff(buffType);
|
|
const canAfford = GameGlobal.currencyManager && GameGlobal.currencyManager.hasGold(cost);
|
|
|
|
// Card background
|
|
ctx.fillStyle = purchased ? 'rgba(76, 175, 80, 0.3)' : 'rgba(255,255,255,0.05)';
|
|
ctx.strokeStyle = purchased ? '#4CAF50' : '#444444';
|
|
ctx.lineWidth = 2;
|
|
|
|
// Rounded rect
|
|
const r = 10;
|
|
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();
|
|
|
|
const cx = rect.x + rect.w / 2;
|
|
|
|
// Title
|
|
ctx.fillStyle = '#FFFFFF';
|
|
ctx.font = 'bold 16px Arial';
|
|
ctx.textAlign = 'center';
|
|
ctx.fillText(title, cx, rect.y + 30);
|
|
|
|
// Description
|
|
ctx.fillStyle = '#AAAAAA';
|
|
ctx.font = '11px Arial';
|
|
ctx.fillText(desc, cx, rect.y + 55);
|
|
|
|
// Cost or status
|
|
if (purchased) {
|
|
ctx.fillStyle = '#4CAF50';
|
|
ctx.font = 'bold 14px Arial';
|
|
ctx.fillText(t('buff.purchased') || '✓ Purchased', cx, rect.y + rect.h - 25);
|
|
} else {
|
|
ctx.fillStyle = canAfford ? '#FFD700' : '#FF4444';
|
|
ctx.font = 'bold 14px Arial';
|
|
ctx.fillText(`🪙 ${cost}`, cx, rect.y + rect.h - 25);
|
|
}
|
|
},
|
|
|
|
_renderButton(ctx, rect, label, color) {
|
|
if (!rect) return;
|
|
|
|
ctx.fillStyle = color;
|
|
ctx.strokeStyle = '#333333';
|
|
ctx.lineWidth = 1;
|
|
|
|
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.stroke();
|
|
|
|
ctx.fillStyle = '#FFFFFF';
|
|
ctx.font = 'bold 16px 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;
|
|
},
|
|
|
|
_startGame() {
|
|
const sm = GameGlobal.sceneManager;
|
|
if (!sm._scenes.has(SCENE.GAME)) {
|
|
const GameScene = require('./GameScene');
|
|
sm.register(SCENE.GAME, GameScene);
|
|
}
|
|
// ★ DEBUG: Force level 20 (Boss Battle) for quick verification of Boss tank gap-fix
|
|
// const params = Object.assign({}, this._gameParams, { level: 20 });
|
|
const params = Object.assign({}, this._gameParams);
|
|
sm.switchTo(SCENE.GAME, params);
|
|
},
|
|
|
|
handleTouch(eventType, e) {
|
|
if (eventType !== 'touchstart') return;
|
|
|
|
const touch = e.touches[0];
|
|
const tx = touch.clientX;
|
|
const ty = touch.clientY;
|
|
|
|
// Shield card
|
|
if (this._hitTest(tx, ty, this._buttons.shield)) {
|
|
if (this._buffManager && !this._buffManager.hasBuff(BUFF_TYPE.SHIELD)) {
|
|
const result = this._buffManager.purchaseBuff(BUFF_TYPE.SHIELD);
|
|
if (!result.success) {
|
|
console.log(`[BuffSelectScene] Shield purchase failed: ${result.error}`);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Double Fire card
|
|
if (this._hitTest(tx, ty, this._buttons.doubleFire)) {
|
|
if (this._buffManager && !this._buffManager.hasBuff(BUFF_TYPE.DOUBLE_FIRE)) {
|
|
const result = this._buffManager.purchaseBuff(BUFF_TYPE.DOUBLE_FIRE);
|
|
if (!result.success) {
|
|
console.log(`[BuffSelectScene] Double Fire purchase failed: ${result.error}`);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Start button
|
|
if (this._hitTest(tx, ty, this._buttons.start)) {
|
|
this._startGame();
|
|
return;
|
|
}
|
|
|
|
// Skip button
|
|
if (this._hitTest(tx, ty, this._buttons.skip)) {
|
|
this._startGame();
|
|
return;
|
|
}
|
|
},
|
|
};
|
|
|
|
module.exports = BuffSelectScene;
|