From 0e321bcea60b440fa271616f3fc01fe0401be180 Mon Sep 17 00:00:00 2001 From: jakciehan Date: Fri, 10 Apr 2026 23:05:26 +0800 Subject: [PATCH] add skin manager --- game.js | 3 + js/base/GameGlobal.js | 1 + js/i18n/en.js | 20 ++- js/i18n/zh.js | 20 ++- js/scenes/GameScene.js | 46 ++++-- js/scenes/MenuScene.js | 44 ++++-- js/scenes/ResultScene.js | 24 +-- js/scenes/SkinScene.js | 299 +++++++++++++++++++++++++++++++++++ js/scenes/TeamResultScene.js | 10 +- 9 files changed, 421 insertions(+), 46 deletions(-) create mode 100644 js/scenes/SkinScene.js diff --git a/game.js b/game.js index edb7d56..e642080 100644 --- a/game.js +++ b/game.js @@ -15,6 +15,7 @@ const CurrencyManager = require('./js/managers/CurrencyManager'); const PaymentManager = require('./js/managers/PaymentManager'); const ComplianceManager = require('./js/managers/ComplianceManager'); const BuffManager = require('./js/managers/BuffManager'); +const SkinManager = require('./js/managers/SkinManager'); const EventBus = require('./js/base/EventBus'); const { SCREEN_WIDTH, @@ -62,12 +63,14 @@ const currencyManager = new CurrencyManager(); const paymentManager = new PaymentManager(); const complianceManager = new ComplianceManager(); const buffManager = new BuffManager(); +const skinManager = new SkinManager(); GameGlobal.adManager = adManager; GameGlobal.shareManager = shareManager; GameGlobal.currencyManager = currencyManager; GameGlobal.paymentManager = paymentManager; GameGlobal.complianceManager = complianceManager; GameGlobal.buffManager = buffManager; +GameGlobal.skinManager = skinManager; // ============================================================ // Game State diff --git a/js/base/GameGlobal.js b/js/base/GameGlobal.js index 9a14dd8..ebe13a9 100644 --- a/js/base/GameGlobal.js +++ b/js/base/GameGlobal.js @@ -164,6 +164,7 @@ const SCENE = { RANKING: 'ranking', SETTINGS: 'settings', SHOP: 'shop', + SKIN: 'skin', BUFF_SELECT: 'buff_select', PVP_ROOM: 'pvp_room', PVP_GAME: 'pvp_game', diff --git a/js/i18n/en.js b/js/i18n/en.js index 758d6e8..fd651bb 100644 --- a/js/i18n/en.js +++ b/js/i18n/en.js @@ -26,6 +26,7 @@ module.exports = { 'menu.pvp': 'PVP', 'menu.team3v3': '3v3 Battle', 'menu.shop': 'Shop', + 'menu.skin': 'Skins', 'menu.ranking': 'Ranking', 'menu.settings': 'Settings', @@ -229,7 +230,7 @@ module.exports = { 'ad.reviveTitle': 'Revive Chance', 'ad.reviveDesc': 'Choose how to revive and continue', 'ad.watchAd': '📺 Watch Ad (Free)', - 'ad.goldRevive': '🪙 Gold Revive (200)', + 'ad.goldRevive': '🪙 Gold Revive', 'ad.giveUp': 'Give Up', 'ad.doubleReward': '🎬 Watch Ad for 2x Reward', 'ad.unavailable': 'Ad temporarily unavailable', @@ -269,4 +270,21 @@ module.exports = { 'dailyGold.remaining': '{remaining}/3', 'dailyGold.exhausted': 'Come back tomorrow', 'dailyGold.reward': '+100 Gold!', + + // ============================================================ + // Skin System + // ============================================================ + 'skin.title': 'Tank Skins', + 'skin.default': 'Classic', + 'skin.arctic': 'Arctic', + 'skin.inferno': 'Inferno', + 'skin.phantom': 'Phantom', + 'skin.jungle': 'Jungle', + 'skin.neon': 'Neon', + 'skin.shadow': 'Shadow', + 'skin.royal': 'Royal', + 'skin.equipped': '✓ Equipped', + 'skin.owned': 'Owned', + 'skin.equipSuccess': '✓ Skin equipped!', + 'skin.purchaseSuccess': '✓ Skin unlocked!', }; diff --git a/js/i18n/zh.js b/js/i18n/zh.js index d912508..fdbb754 100644 --- a/js/i18n/zh.js +++ b/js/i18n/zh.js @@ -26,6 +26,7 @@ module.exports = { 'menu.pvp': '双人对战', 'menu.team3v3': '3v3 对战', 'menu.shop': '商店', + 'menu.skin': '皮肤', 'menu.ranking': '排行榜', 'menu.settings': '设置', @@ -229,7 +230,7 @@ module.exports = { 'ad.reviveTitle': '复活机会', 'ad.reviveDesc': '选择复活方式继续游戏', 'ad.watchAd': '📺 观看广告(免费)', - 'ad.goldRevive': '🪙 金币复活(200)', + 'ad.goldRevive': '🪙 金币复活', 'ad.giveUp': '放弃', 'ad.doubleReward': '🎬 看广告双倍奖励', 'ad.unavailable': '广告暂时不可用', @@ -269,4 +270,21 @@ module.exports = { 'dailyGold.remaining': '{remaining}/3', 'dailyGold.exhausted': '明日再来', 'dailyGold.reward': '+100 金币!', + + // ============================================================ + // Skin System + // ============================================================ + 'skin.title': '坦克皮肤', + 'skin.default': '经典', + 'skin.arctic': '极地', + 'skin.inferno': '烈焰', + 'skin.phantom': '幻影', + 'skin.jungle': '丛林', + 'skin.neon': '霓虹', + 'skin.shadow': '暗影', + 'skin.royal': '皇家', + 'skin.equipped': '✓ 使用中', + 'skin.owned': '已拥有', + 'skin.equipSuccess': '✓ 已装备!', + 'skin.purchaseSuccess': '✓ 已解锁!', }; diff --git a/js/scenes/GameScene.js b/js/scenes/GameScene.js index bd19d72..730cd76 100644 --- a/js/scenes/GameScene.js +++ b/js/scenes/GameScene.js @@ -79,6 +79,7 @@ const GameScene = { // Revive ad state _reviveAdUsed: false, + _reviveCount: 0, // Track revive count for escalating cost _showingReviveDialog: false, _reviveDialogButtons: null, @@ -97,6 +98,7 @@ const GameScene = { this._gameOverDelay = 0; this._cachedBasePos = null; this._reviveAdUsed = false; + this._reviveCount = 0; this._showingReviveDialog = false; this._reviveDialogButtons = null; @@ -138,6 +140,11 @@ const GameScene = { }); this._playerTank.activateShield(3000); // spawn protection + // Apply equipped skin colors to player tank + if (GameGlobal.skinManager) { + this._playerTank._skinColors = GameGlobal.skinManager.getCurrentSkinColors(); + } + // Safety: ensure player spawn area is clear of blocking terrain this._clearSpawnArea(levelData.playerSpawn.col, levelData.playerSpawn.row); @@ -471,14 +478,15 @@ const GameScene = { ctx.fillText(t('ad.watchAd') || '📺 Watch Ad (Free)', btns.watchAd.x + btns.watchAd.w / 2, btns.watchAd.y + btns.watchAd.h / 2); } - // Gold Revive button (orange) + // Gold Revive button (orange) - show escalating cost if (btns.goldRevive) { - const hasGold = GameGlobal.currencyManager && GameGlobal.currencyManager.hasGold(200); + const reviveCost = this._getReviveCost(); + const hasGold = GameGlobal.currencyManager && GameGlobal.currencyManager.hasGold(reviveCost); ctx.fillStyle = hasGold ? '#FF9800' : '#555555'; ctx.fillRect(btns.goldRevive.x, btns.goldRevive.y, btns.goldRevive.w, btns.goldRevive.h); ctx.fillStyle = '#FFFFFF'; ctx.font = 'bold 13px Arial'; - ctx.fillText(t('ad.goldRevive') || '🪙 Gold Revive (200)', btns.goldRevive.x + btns.goldRevive.w / 2, btns.goldRevive.y + btns.goldRevive.h / 2); + ctx.fillText(`🪙 ${t('ad.goldRevive') || 'Gold Revive'} (${reviveCost})`, btns.goldRevive.x + btns.goldRevive.w / 2, btns.goldRevive.y + btns.goldRevive.h / 2); } // Give Up button (gray) @@ -680,13 +688,8 @@ const GameScene = { const hasLives = this._playerTank.die(); if (!hasLives) { - // Check if revive ad is available and not yet used this level - if (!this._reviveAdUsed) { - // Always show revive dialog (with ad and/or gold options) - this._showReviveAdDialog(); - } else { - this._triggerGameOver(); - } + // Always show revive dialog (escalating cost each time) + this._showReviveAdDialog(); } }, @@ -701,6 +704,18 @@ const GameScene = { return GameGlobal.adManager.canShowScene(AdManager.AD_SCENE.REVIVE); }, + /** + * Get the current revive gold cost based on revive count (escalating). + * 1st revive: 200, 2nd: 400, 3rd+: 800 + * @returns {number} + * @private + */ + _getReviveCost() { + if (this._reviveCount === 0) return 200; + if (this._reviveCount === 1) return 400; + return 800; + }, + /** * Show the revive dialog overlay with dual options. * Pauses the game and presents watch-ad / gold-revive / give-up options. @@ -736,17 +751,18 @@ const GameScene = { }, /** - * Handle the player choosing to revive with gold (200 gold). + * Handle the player choosing to revive with gold (escalating cost). * @private */ _onGoldRevive() { + const cost = this._getReviveCost(); const cm = GameGlobal.currencyManager; - if (cm && cm.spendGold(200)) { + if (cm && cm.spendGold(cost)) { this._showingReviveDialog = false; this._reviveDialogButtons = null; - this._reviveAdUsed = true; + this._reviveCount++; this._revivePlayer(); - console.log('[GameScene] Player revived via gold (200)'); + console.log(`[GameScene] Player revived via gold (${cost}), revive #${this._reviveCount}`); } }, @@ -762,7 +778,7 @@ const GameScene = { this._showingReviveDialog = false; this._reviveDialogButtons = null; if (completed) { - this._reviveAdUsed = true; + this._reviveCount++; this._revivePlayer(); } else { this._triggerGameOver(); diff --git a/js/scenes/MenuScene.js b/js/scenes/MenuScene.js index 1847fd1..b031e49 100644 --- a/js/scenes/MenuScene.js +++ b/js/scenes/MenuScene.js @@ -22,8 +22,10 @@ const BTN_GAP = Math.min(8, SCREEN_HEIGHT * 0.015); const BTN_START_Y = SCREEN_HEIGHT * 0.38; const BTN_X = (SCREEN_WIDTH - BTN_WIDTH) / 2; -// Half-width buttons for the utility row (shop, battle pass, ranking, settings) +// Half-width buttons for the utility row const HALF_BTN_WIDTH = (BTN_WIDTH - BTN_GAP) / 2; +// Third-width buttons for 3-column row +const THIRD_BTN_WIDTH = (BTN_WIDTH - BTN_GAP * 2) / 3; // Main game mode buttons (full width, vertical) const MAIN_BUTTONS = [ @@ -33,10 +35,11 @@ const MAIN_BUTTONS = [ { labelKey: 'menu.team3v3', mode: GAME_MODE.TEAM_3V3, scene: SCENE.TEAM_ROOM }, ]; -// Utility buttons: shop, daily gold, ranking, settings (2x2 grid) +// Utility buttons: shop, daily gold, skin, ranking, settings (grid) const UTIL_BUTTONS = [ { labelKey: 'menu.shop', mode: null, scene: SCENE.SHOP }, { labelKey: 'dailyGold.btn', mode: null, scene: 'DAILY_GOLD' }, + { labelKey: 'menu.skin', mode: null, scene: SCENE.SKIN }, { labelKey: 'menu.ranking', mode: null, scene: SCENE.RANKING }, { labelKey: 'menu.settings', mode: null, scene: SCENE.SETTINGS }, ]; @@ -50,18 +53,29 @@ const mainBtnRects = MAIN_BUTTONS.map((btn, i) => ({ ...btn, })); -// Pre-calculate button rects for utility buttons (2x2 grid) +// Pre-calculate button rects for utility buttons (row1: 3 cols, row2: 2 cols centered) const utilStartY = BTN_START_Y + MAIN_BUTTONS.length * (BTN_HEIGHT + BTN_GAP) + BTN_GAP; const utilBtnRects = UTIL_BUTTONS.map((btn, i) => { - const col = i % 2; - const row = Math.floor(i / 2); - return { - x: BTN_X + col * (HALF_BTN_WIDTH + BTN_GAP), - y: utilStartY + row * (BTN_HEIGHT + BTN_GAP), - w: HALF_BTN_WIDTH, - h: BTN_HEIGHT, - ...btn, - }; + if (i < 3) { + // First row: 3 buttons + return { + x: BTN_X + i * (THIRD_BTN_WIDTH + BTN_GAP), + y: utilStartY, + w: THIRD_BTN_WIDTH, + h: BTN_HEIGHT, + ...btn, + }; + } else { + // Second row: 2 buttons centered + const col = i - 3; + return { + x: BTN_X + col * (HALF_BTN_WIDTH + BTN_GAP), + y: utilStartY + (BTN_HEIGHT + BTN_GAP), + w: HALF_BTN_WIDTH, + h: BTN_HEIGHT, + ...btn, + }; + } }); // Combined list for unified iteration @@ -289,6 +303,12 @@ const MenuScene = { sm.register(SCENE.SHOP, ShopScene); } sm.switchTo(SCENE.SHOP); + } else if (btn.scene === SCENE.SKIN) { + if (!sm._scenes.has(SCENE.SKIN)) { + const SkinScene = require('./SkinScene'); + sm.register(SCENE.SKIN, SkinScene); + } + sm.switchTo(SCENE.SKIN); } else if (btn.scene === SCENE.SETTINGS) { if (!sm._scenes.has(SCENE.SETTINGS)) { const SettingsScene = require('./SettingsScene'); diff --git a/js/scenes/ResultScene.js b/js/scenes/ResultScene.js index 0b8b1bf..75a7072 100644 --- a/js/scenes/ResultScene.js +++ b/js/scenes/ResultScene.js @@ -82,27 +82,27 @@ const ResultScene = { */ _calculateGoldReward() { const stats = this._stats; - let gold = 50; // Base reward per requirements + let gold = 30; // Base reward (reduced from 50) - // Bonus per kill type - gold += (stats.kills.normal || 0) * 5; - gold += (stats.kills.fast || 0) * 10; - gold += (stats.kills.armor || 0) * 15; - gold += (stats.kills.boss || 0) * 25; + // Bonus per kill type (reduced) + gold += (stats.kills.normal || 0) * 3; + gold += (stats.kills.fast || 0) * 5; + gold += (stats.kills.armor || 0) * 8; + gold += (stats.kills.boss || 0) * 15; - // Victory bonus + // Victory bonus (reduced from 50) if (this._victory) { - gold += 50; + gold += 30; } - // Time bonus (faster = more gold, max 30 gold for under 60s) + // Time bonus (faster = more gold, max 20 gold for under 60s, reduced from 30) if (this._victory && stats.timeElapsed < 300) { - gold += Math.max(0, Math.floor((300 - stats.timeElapsed) / 10)); + gold += Math.max(0, Math.floor((300 - stats.timeElapsed) / 15)); } - // Base alive bonus + // Base alive bonus (reduced from 20) if (stats.baseAlive) { - gold += 20; + gold += 10; } return gold; diff --git a/js/scenes/SkinScene.js b/js/scenes/SkinScene.js new file mode 100644 index 0000000..543b4ac --- /dev/null +++ b/js/scenes/SkinScene.js @@ -0,0 +1,299 @@ +/** + * SkinScene.js + * Tank skin gallery scene where players can preview, purchase, and equip skins. + */ + +const { + SCREEN_WIDTH, + SCREEN_HEIGHT, + COLORS, + SCENE, +} = require('../base/GameGlobal'); +const { t } = require('../i18n/I18n'); + +// Layout constants +const COLS = 2; +const CARD_GAP = 10; +const CARD_W = Math.min((SCREEN_WIDTH * 0.85 - CARD_GAP) / COLS, 150); +const CARD_H = 100; +const GRID_W = COLS * CARD_W + (COLS - 1) * CARD_GAP; +const GRID_LEFT = (SCREEN_WIDTH - GRID_W) / 2; +const CARDS_START_Y = SCREEN_HEIGHT * 0.18; + +const SkinScene = { + _buttons: {}, + _skinCards: [], + _message: '', + _messageTimer: 0, + _scrollY: 0, + + enter() { + this._message = ''; + this._messageTimer = 0; + this._scrollY = 0; + this._calculateLayout(); + }, + + exit() {}, + + _calculateLayout() { + const sm = GameGlobal.skinManager; + if (!sm) return; + + const skins = sm.getAllSkins(); + this._skinCards = []; + + for (let i = 0; i < skins.length; i++) { + const col = i % COLS; + const row = Math.floor(i / COLS); + const x = GRID_LEFT + col * (CARD_W + CARD_GAP); + const y = CARDS_START_Y + row * (CARD_H + CARD_GAP); + + this._skinCards.push({ + ...skins[i], + rect: { x, y, w: CARD_W, h: CARD_H }, + }); + } + + // Back button + const backW = 100; + const backH = 36; + const lastRow = Math.ceil(skins.length / COLS); + const backY = CARDS_START_Y + lastRow * (CARD_H + CARD_GAP) + 10; + this._buttons = { + back: { x: (SCREEN_WIDTH - backW) / 2, y: backY, 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 22px Arial'; + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + ctx.fillText(t('skin.title') || 'Tank Skins', SCREEN_WIDTH / 2, SCREEN_HEIGHT * 0.06); + + // Gold balance + const gold = GameGlobal.currencyManager ? GameGlobal.currencyManager.getGold() : 0; + ctx.fillStyle = '#FFD700'; + ctx.font = 'bold 14px Arial'; + ctx.fillText(`🪙 ${gold}`, SCREEN_WIDTH / 2, SCREEN_HEIGHT * 0.12); + + const sm = GameGlobal.skinManager; + if (!sm) return; + + const equippedId = sm.getEquippedSkinId(); + + // Render skin cards + for (const card of this._skinCards) { + this._renderSkinCard(ctx, card, sm.isUnlocked(card.id), equippedId === card.id); + } + + // Back button + this._renderButton(ctx, this._buttons.back, t('common.back') || '← Back', '#666666'); + + // Message toast + if (this._message) { + ctx.fillStyle = 'rgba(0,0,0,0.7)'; + const msgW = 200; + const msgH = 30; + const msgX = (SCREEN_WIDTH - msgW) / 2; + const msgY = SCREEN_HEIGHT * 0.92; + ctx.fillRect(msgX, msgY, msgW, msgH); + ctx.fillStyle = '#FFFFFF'; + ctx.font = '13px Arial'; + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + ctx.fillText(this._message, SCREEN_WIDTH / 2, msgY + msgH / 2); + } + }, + + _renderSkinCard(ctx, card, isUnlocked, isEquipped) { + const { rect } = card; + + // Card background + if (isEquipped) { + ctx.fillStyle = 'rgba(76, 175, 80, 0.3)'; + ctx.strokeStyle = '#4CAF50'; + } else if (isUnlocked) { + ctx.fillStyle = 'rgba(255,255,255,0.08)'; + ctx.strokeStyle = '#666666'; + } else { + ctx.fillStyle = 'rgba(255,255,255,0.03)'; + ctx.strokeStyle = '#444444'; + } + ctx.lineWidth = isEquipped ? 2.5 : 1.5; + + // Rounded rect + 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(); + + const cx = rect.x + rect.w / 2; + + // Tank preview (mini tank icon) + this._drawMiniTank(ctx, cx, rect.y + 28, card); + + // Skin name + ctx.fillStyle = '#FFFFFF'; + ctx.font = 'bold 12px Arial'; + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + ctx.fillText(t(card.nameKey) || card.id, cx, rect.y + 58); + + // Status / price + if (isEquipped) { + ctx.fillStyle = '#4CAF50'; + ctx.font = 'bold 11px Arial'; + ctx.fillText(t('skin.equipped') || '✓ Equipped', cx, rect.y + rect.h - 16); + } else if (isUnlocked) { + ctx.fillStyle = '#AAAAAA'; + ctx.font = '11px Arial'; + ctx.fillText(t('skin.owned') || 'Owned', cx, rect.y + rect.h - 16); + } else { + const canAfford = GameGlobal.currencyManager && GameGlobal.currencyManager.hasGold(card.cost); + ctx.fillStyle = canAfford ? '#FFD700' : '#FF4444'; + ctx.font = 'bold 11px Arial'; + ctx.fillText(`🪙 ${card.cost}`, cx, rect.y + rect.h - 16); + } + }, + + /** + * Draw a mini tank preview with skin colors. + */ + _drawMiniTank(ctx, cx, cy, card) { + const size = 14; + const bodyColor = card.colors ? card.colors.body : '#FFD700'; + const turretColor = card.colors ? card.colors.turret : '#B8860B'; + const trackColor = card.colors ? card.colors.track : '#8B6914'; + + ctx.save(); + ctx.translate(cx, cy); + + // Body + ctx.fillStyle = bodyColor; + ctx.fillRect(-size, -size / 2, size * 2, size); + + // Turret (barrel) + ctx.fillStyle = turretColor; + ctx.fillRect(-2, -size / 2 - 10, 4, 10); + + // Center detail + ctx.fillStyle = turretColor; + ctx.fillRect(-size * 0.3, -size * 0.3, size * 0.6, size * 0.6); + + // Tracks + ctx.fillStyle = trackColor; + ctx.fillRect(-size - 3, -size / 2, 3, size); + ctx.fillRect(size, -size / 2, 3, size); + + ctx.restore(); + }, + + _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 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 sm = GameGlobal.skinManager; + if (!sm) return; + + // Check skin cards + for (const card of this._skinCards) { + if (this._hitTest(tx, ty, card.rect)) { + if (sm.isUnlocked(card.id)) { + // Equip + if (sm.getEquippedSkinId() !== card.id) { + const result = sm.equipSkin(card.id); + if (result.success) { + this._showMessage(t('skin.equipSuccess') || '✓ Skin equipped!'); + } + } + } else { + // Purchase + const result = sm.purchaseSkin(card.id); + if (result.success) { + this._showMessage(t('skin.purchaseSuccess') || '✓ Skin unlocked!'); + // Auto-equip after purchase + sm.equipSkin(card.id); + } else if (result.error === 'Insufficient gold') { + this._showMessage(t('currency.insufficient') || 'Insufficient Gold'); + } + } + return; + } + } + + // Back button + if (this._hitTest(tx, ty, this._buttons.back)) { + GameGlobal.sceneManager.switchTo(SCENE.MENU); + return; + } + }, +}; + +module.exports = SkinScene; diff --git a/js/scenes/TeamResultScene.js b/js/scenes/TeamResultScene.js index 77f0f4c..298844b 100644 --- a/js/scenes/TeamResultScene.js +++ b/js/scenes/TeamResultScene.js @@ -151,19 +151,19 @@ const TeamResultScene = { * @private */ _calculateAndAwardGold() { - let gold = 50; // Base reward per requirements + let gold = 30; // Base reward (reduced from 50) // Find local player stats const localPlayer = this._players.find(p => p.isLocal); if (localPlayer) { const stats = this._stats[localPlayer.playerId] || {}; - gold += (stats.kills || 0) * 10; - gold += (stats.assists || 0) * 5; + gold += (stats.kills || 0) * 5; + gold += (stats.assists || 0) * 3; } - // Victory bonus + // Victory bonus (reduced from 50) if (this._didWin) { - gold += 50; + gold += 30; } this._goldReward = gold;