/** * MenuScene.js * Main menu scene — military-tech themed UI with game title and mode selection. * Rendered entirely with Canvas API (no DOM). */ const { SCREEN_WIDTH, SCREEN_HEIGHT, COLORS, SCENE, GAME_MODE, } = require('../base/GameGlobal'); const { t } = require('../i18n/I18n'); // ============================================================ // Style — Military-tech theme matching reference design // ============================================================ const MC = { BG_TOP: '#0a0e1a', BG_BOT: '#111827', ACCENT: '#e94560', GOLD: '#FFD700', GOLD_LIGHT: '#FFF3A8', GOLD_DIM: '#B8860B', BTN_BG: '#162844', BTN_BG_HIGHLIGHT: '#1a3555', BG_GRAD_START: '#142236', BG_GRAD_END: '#0c1320', BTN_BORDER_GLOW: 'rgba(70,140,220,0.4)', BTN_BORDER: 'rgba(50,100,170,0.5)', BTN_HOVER: '#244a75', BTN_TEXT_PRIMARY: '#FFFFFF', BTN_TEXT_SECONDARY: '#88AACC', TITLE: '#FFD700', SUBTITLE_BG: 'rgba(25,55,95,0.85)', SUBTITLE_BORDER: '#3a68a0', SUBTITLE_TEXT: '#7CB9E8', FOOTER: '#445566', ICON_GOLD: '#FFD700', ICON_BLUE: '#4A90D9', HOT_BADGE: '#e94560', UTIL_GREEN: '#1a4a2e', }; // ============================================================ // Layout — Left-Right Split (Reference Design) // // ┌─ Left Panel (~54%) ─┬─ Right Panel (~43%) ─┐ // │ Tank visual area │ 5 mode buttons │ // │ + Title + Subtitle │ (vertical stack) │ // │ + Promo text │ │ // ├──────────────────────┴───────────────────────┤ // │ Bottom Utility Bar (4 columns) │ // └───────────────────────────────────────────────┘ // ============================================================ const PAD = Math.max(12, SCREEN_WIDTH * 0.03); const TOP_BAR_H = 48; const SPLIT_X = SCREEN_WIDTH * 0.55; const RIGHT_PAD = Math.max(14, PAD * 2); // --- Right Panel: Mode Buttons (vertical) --- const MODE_INNER_PAD = PAD; // inner left padding inside right panel const MODE_BTN_W = SCREEN_WIDTH - SPLIT_X - RIGHT_PAD - MODE_INNER_PAD; const MODE_BTN_H = Math.min(66, SCREEN_HEIGHT * 0.095); const MODE_GAP = Math.max(10, SCREEN_HEIGHT * 0.015); const MODE_START_Y = TOP_BAR_H + SCREEN_HEIGHT * 0.035; // --- Bottom Utility Bar --- const UTIL_BAR_Y = SCREEN_HEIGHT * 0.805; const UTIL_BAR_H = Math.min(58, SCREEN_HEIGHT * 0.088); const UTIL_EDGE_PAD = Math.max(24, SCREEN_WIDTH * 0.058); const UTIL_GAP = Math.max(14, SCREEN_WIDTH * 0.025); const UTIL_COL_W = (SCREEN_WIDTH - UTIL_EDGE_PAD * 2 - UTIL_GAP * 3) / 4; // ============================================================ // Button Definitions // ============================================================ const MAIN_BUTTONS = [ { labelKey: 'menu.classic', subKey: 'menu.classic.sub', icon: 'trophy', mode: GAME_MODE.CLASSIC, scene: SCENE.GAME }, { labelKey: 'menu.endless', subKey: 'menu.endless.sub', icon: 'target', mode: GAME_MODE.ENDLESS, scene: SCENE.GAME }, { labelKey: 'menu.pvp', subKey: 'menu.pvp.sub', icon: 'swords', mode: GAME_MODE.PVP, scene: SCENE.PVP_ROOM }, { labelKey: 'menu.team2v2', subKey: 'menu.team2v2.sub', icon: 'swords', mode: GAME_MODE.TEAM_2V2, scene: SCENE.TEAM_2V2_ROOM }, { labelKey: 'menu.team3v3', subKey: 'menu.team3v3.sub', icon: 'shield', mode: GAME_MODE.TEAM_3V3, scene: SCENE.TEAM_ROOM, hot: true }, ]; const UTIL_BUTTONS = [ { labelKey: 'dailyGold.btn', subKey: 'dailyGold.desc', icon: 'coins', mode: null, scene: 'DAILY_GOLD' }, { labelKey: 'menu.skin', subKey: 'menu.skin.sub', icon: 'skin', mode: null, scene: SCENE.SKIN }, { labelKey: 'menu.ranking', subKey: 'menu.ranking.sub', icon: 'ranking', mode: null, scene: SCENE.RANKING }, { labelKey: 'menu.settings', subKey: 'menu.settings.sub', icon: 'settings', mode: null, scene: SCENE.SETTINGS }, ]; // Pre-calculate mode button rects (right panel, vertical stack) const mainBtnRects = MAIN_BUTTONS.map((btn, i) => ({ x: SPLIT_X + MODE_INNER_PAD, y: MODE_START_Y + i * (MODE_BTN_H + MODE_GAP), w: MODE_BTN_W, h: MODE_BTN_H, ...btn, })); // Pre-calculate utility button rects (bottom row) const utilBtnRects = UTIL_BUTTONS.map((btn, i) => ({ x: UTIL_EDGE_PAD + i * (UTIL_COL_W + UTIL_GAP), y: UTIL_BAR_Y, w: UTIL_COL_W, h: UTIL_BAR_H, ...btn, })); const buttonRects = [...mainBtnRects, ...utilBtnRects]; // ============================================================ // Menu Scene // ============================================================ const MenuScene = { _pressedIndex: -1, _tankAnim: 0, // Background image _bgImage: null, enter() { this._pressedIndex = -1; this._tankAnim = 0; this._avatarImg = null; this._bgImage = null; // Load background image this._loadBgImage(); // Load avatar image if profile has one const profile = GameGlobal.playerProfile; if (profile && profile.avatarUrl) { this._loadAvatarImage(profile.avatarUrl); } // Listen for profile updates (avatar may arrive after initial render) this._profileHandler = (data) => { if (data && data.avatarUrl && !this._avatarImg) { this._loadAvatarImage(data.avatarUrl); } }; const bus = GameGlobal.eventBus; if (bus && typeof bus.on === 'function') { bus.on('profile:updated', this._profileHandler); } // Kick off nickname acquisition as early as possible so that later // network messages (CREATE_TEAM, SOLO_MATCH, ...) can carry it. this._initPlayerProfile(); if (GameGlobal._pendingTeamId) { const teamId = GameGlobal._pendingTeamId; const teamMode = GameGlobal._pendingTeamMode || null; GameGlobal._pendingTeamId = null; GameGlobal._pendingTeamMode = null; const is2v2 = teamMode === '2v2'; const targetScene = is2v2 ? SCENE.TEAM_2V2_ROOM : SCENE.TEAM_ROOM; const sceneName = is2v2 ? 'Team2v2RoomScene' : 'TeamRoomScene'; console.log(`[MenuScene] Found pendingTeamId: ${teamId}, mode: ${teamMode || '3v3'}, will auto-navigate to ${sceneName}`); setTimeout(() => { console.log(`[MenuScene] Auto-navigating to ${sceneName} with teamId: ${teamId}`); const sm = GameGlobal.sceneManager; if (!sm._scenes.has(targetScene)) { const SceneModule = is2v2 ? require('./Team2v2RoomScene') : require('./TeamRoomScene'); sm.register(targetScene, SceneModule); } sm.switchTo(targetScene, { teamId }); }, 100); } if (GameGlobal._pendingRoomId) { const roomId = GameGlobal._pendingRoomId; GameGlobal._pendingRoomId = null; console.log(`[MenuScene] Found pendingRoomId: ${roomId}, will auto-navigate to RoomScene`); setTimeout(() => { console.log(`[MenuScene] Auto-navigating to RoomScene with roomId: ${roomId}`); const sm = GameGlobal.sceneManager; if (!sm._scenes.has(SCENE.PVP_ROOM)) { const RoomScene = require('./RoomScene'); sm.register(SCENE.PVP_ROOM, RoomScene); } sm.switchTo(SCENE.PVP_ROOM, { roomId }); }, 100); } }, exit() { this._pressedIndex = -1; // Destroy UserInfoButton overlay when leaving the menu const profile = GameGlobal.playerProfile; if (profile && typeof profile.destroyUserInfoButton === 'function') { profile.destroyUserInfoButton(); } // Remove profile update listener const bus = GameGlobal.eventBus; if (bus && typeof bus.off === 'function' && this._profileHandler) { bus.off('profile:updated', this._profileHandler); } this._profileHandler = null; this._avatarImg = null; }, update(dt) { this._tankAnim += dt; }, render(ctx) { // ---- Background (image or fallback gradient) ---- if (this._bgImage) { // Cover-fit: scale image to fill entire screen, center-crop if aspect differs const imgW = this._bgImage.width; const imgH = this._bgImage.height; const imgRatio = imgW / imgH; const screenRatio = SCREEN_WIDTH / SCREEN_HEIGHT; var drawW, drawH, dx, dy; if (imgRatio > screenRatio) { drawH = SCREEN_HEIGHT; drawW = SCREEN_HEIGHT * imgRatio; dx = (SCREEN_WIDTH - drawW) / 2; dy = 0; } else { drawW = SCREEN_WIDTH; drawH = SCREEN_WIDTH / imgRatio; dx = 0; dy = (SCREEN_HEIGHT - drawH) / 2; } ctx.drawImage(this._bgImage, dx, dy, drawW, drawH); } else { const bgGrad = ctx.createLinearGradient(0, 0, 0, SCREEN_HEIGHT); bgGrad.addColorStop(0, MC.BG_TOP); bgGrad.addColorStop(1, MC.BG_BOT); ctx.fillStyle = bgGrad; ctx.fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); // Scan-lines ctx.globalAlpha = 0.02; ctx.fillStyle = '#FFFFFF'; for (let sy = 0; sy < SCREEN_HEIGHT; sy += 4) { ctx.fillRect(0, sy, SCREEN_WIDTH, 1); } ctx.globalAlpha = 1; } // Top accent bar const accentGrad = ctx.createLinearGradient(0, 0, SCREEN_WIDTH, 0); accentGrad.addColorStop(0, 'transparent'); accentGrad.addColorStop(0.3, MC.ACCENT); accentGrad.addColorStop(0.7, MC.ACCENT); accentGrad.addColorStop(1, 'transparent'); ctx.fillStyle = accentGrad; ctx.fillRect(0, 0, SCREEN_WIDTH, 3); // ============================================================ // LEFT PANEL — Branding area (~54% width) // - Tank icon (animated) // - Title + subtitle tag // - Decorative treasure icon // - Promotional text // ============================================================ const leftCenterX = SPLIT_X * 0.5; const leftCenterY = SCREEN_HEIGHT * 0.48; // ---- Animated Tank Icon (larger, as visual anchor) ---- ctx.save(); ctx.translate(leftCenterX * 0.65, leftCenterY - 10); const tankScale = 1.6; ctx.scale(tankScale, tankScale); ctx.translate(-leftCenterX * 0.65 / tankScale, (-leftCenterY + 10) / tankScale); this._drawTankIcon(ctx, leftCenterX * 0.65, (leftCenterY - 10) / tankScale); ctx.restore(); // ---- Title (positioned in upper-right of left panel) ---- const titleX = SPLIT_X * 0.52; const titleY = TOP_BAR_H + SCREEN_HEIGHT * 0.04; ctx.save(); ctx.shadowColor = MC.GOLD; ctx.shadowBlur = 22; ctx.fillStyle = MC.TITLE; const titleSize = Math.max(Math.min(SPLIT_X * 0.13, 38), 26); ctx.font = `bold ${titleSize}px Arial`; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText(t('menu.title'), titleX, titleY); // Subtitle tag below title ctx.shadowBlur = 0; const subText = t('menu.subtitle'); const sFont = '11px Arial'; ctx.font = sFont; const stw = ctx.measureText(subText).width; const stagW = Math.max(stw + 20, 120); const stagH = 24; const stagX = titleX - stagW / 2; const stagY = titleY + titleSize * 0.58; ctx.fillStyle = MC.SUBTITLE_BG; ctx.strokeStyle = MC.SUBTITLE_BORDER; ctx.lineWidth = 1.2; this._roundRect(ctx, stagX, stagY, stagW, stagH, stagH / 2); ctx.fill(); ctx.stroke(); ctx.fillStyle = MC.SUBTITLE_TEXT; ctx.font = sFont; ctx.fillText(subText, titleX, stagY + stagH / 2); ctx.restore(); // ---- Promotional Text (bottom-left) ---- ctx.save(); const promoY = UTIL_BAR_Y - 38; const promoX = PAD * 3.2; // "3v3 团队竞技" style text ctx.fillStyle = '#4A9EFF'; ctx.font = `bold ${Math.max(16, SCREEN_WIDTH * 0.042)}px Arial`; ctx.textAlign = 'left'; ctx.textBaseline = 'bottom'; // Add subtle glow ctx.shadowColor = '#4A9EFF'; ctx.shadowBlur = 8; const promoLine1 = '3v3 团队竞技'; ctx.fillText(promoLine1, promoX, promoY); // "随时随地开一局!" style text ctx.shadowBlur = 0; ctx.fillStyle = '#FFD54F'; ctx.font = `bold ${Math.max(14, SCREEN_WIDTH * 0.037)}px Arial`; const promoLine2 = '随时随地开一局!'; ctx.fillText(promoLine2, promoX, promoY + 32); ctx.restore(); // ============================================================ // RIGHT PANEL — Mode buttons (vertical stack) // ============================================================ for (let i = 0; i < mainBtnRects.length; i++) { const btn = mainBtnRects[i]; const isPressed = this._pressedIndex === i; this._drawModeButton( ctx, btn, t(btn.labelKey), isPressed, null, t(btn.subKey || ''), btn.icon, !!btn.hot ); } // ============================================================ // TOP BAR — Avatar + Gold pill // ============================================================ // Player Avatar & Nickname (top-left) const profile = GameGlobal.playerProfile; const avatarSize = 28; const avatarX = PAD; const avatarY = 8; const avatarR = avatarSize / 2; ctx.save(); ctx.beginPath(); ctx.arc(avatarX + avatarR, avatarY + avatarR, avatarR, 0, Math.PI * 2); ctx.closePath(); ctx.fillStyle = 'rgba(30,48,84,0.75)'; ctx.fill(); ctx.strokeStyle = 'rgba(255,215,0,0.45)'; ctx.lineWidth = 1.2; ctx.stroke(); if (profile && profile.avatarUrl && this._avatarImg && this._avatarImg.complete) { ctx.clip(); ctx.drawImage(this._avatarImg, avatarX, avatarY, avatarSize, avatarSize); } else { ctx.fillStyle = 'rgba(255,215,0,0.5)'; ctx.beginPath(); ctx.arc(avatarX + avatarR, avatarY + avatarR - 2, avatarR * 0.35, 0, Math.PI * 2); ctx.fill(); ctx.beginPath(); ctx.ellipse(avatarX + avatarR, avatarY + avatarR + avatarR * 0.55, avatarR * 0.55, avatarR * 0.3, 0, Math.PI, 0); ctx.fill(); } ctx.restore(); // Nickname next to avatar const displayName = profile ? profile.getDisplayName() : 'Tanker'; ctx.font = 'bold 11px Arial'; ctx.fillStyle = MC.GOLD; ctx.textAlign = 'left'; ctx.textBaseline = 'middle'; ctx.fillText(displayName, avatarX + avatarSize + 6, avatarY + avatarR - 5); if (profile && !profile.granted) { ctx.font = '9px Arial'; ctx.fillStyle = MC.FOOTER; ctx.fillText(t('menu.tapToAuth') || 'Tap to authorize', avatarX + avatarSize + 6, avatarY + avatarR + 8); } // Gold Balance pill (to the right of nickname) const gold = GameGlobal.currencyManager ? GameGlobal.currencyManager.getGold() : 0; const goldText = `🪙 ${gold}`; ctx.font = 'bold 12px Arial'; const gtw = ctx.measureText(goldText).width; const pillW = gtw + 14; const pillH = 22; const pillX = avatarX + avatarSize + 6; // right under nickname const pillY = avatarY + avatarR + 12; ctx.fillStyle = 'rgba(255,215,0,0.08)'; ctx.strokeStyle = 'rgba(255,215,0,0.35)'; ctx.lineWidth = 1; this._roundRect(ctx, pillX, pillY, pillW, pillH, pillH / 2); ctx.fill(); ctx.stroke(); ctx.fillStyle = MC.GOLD; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText(goldText, pillX + pillW / 2, pillY + pillH / 2); // ============================================================ // BOTTOM UTILITY BAR — 4 columns // ============================================================ for (let i = 0; i < utilBtnRects.length; i++) { const btn = utilBtnRects[i]; const globalIdx = mainBtnRects.length + i; const isPressed = this._pressedIndex === globalIdx; let label = t(btn.labelKey); let subtitle = t(btn.subKey || ''); let customBg = null; let iconOverride = btn.icon; if (btn.scene === 'DAILY_GOLD') { const remaining = GameGlobal.adManager ? GameGlobal.adManager.getDailyGoldRemaining() : 0; if (remaining > 0) { label = `${t('dailyGold.btn')} ${remaining}/3`; subtitle = t('dailyGold.desc') || ''; customBg = MC.UTIL_GREEN; iconOverride = 'coins'; } else { label = t('dailyGold.exhausted') || '...'; subtitle = ''; customBg = '#222'; iconOverride = 'coins'; } } this._drawUtilButton(ctx, btn, label, subtitle, isPressed, customBg, iconOverride); } // ---- Footer version ---- ctx.fillStyle = MC.FOOTER; ctx.font = `${Math.max(9, SCREEN_WIDTH * 0.022)}px Arial`; ctx.textAlign = 'center'; ctx.fillText('v1.0.0', SCREEN_WIDTH / 2, SCREEN_HEIGHT - 10); }, // ---- Mode Button (Right panel: icon + two-line text + optional hot badge) ---- _drawModeButton(ctx, btn, label, isPressed, font, subtitle, iconType, isHot) { const r = 10; ctx.save(); ctx.globalAlpha = 0.85; const iconSz = Math.max(26, Math.min(btn.h * 0.48, 34)); const pad = Math.max(10, iconSz * 0.38); const txtOff = iconSz + pad + 6; const iconX = btn.x + pad; const iconY = btn.y + (btn.h - iconSz) / 2; const textX = btn.x + txtOff; // Shadow ctx.fillStyle = 'rgba(0,0,0,0.35)'; this._roundRect(ctx, btn.x + 2, btn.y + 3, btn.w, btn.h, r); ctx.fill(); // Body gradient — slightly lighter for mode buttons const bodyGrad = ctx.createLinearGradient(btn.x, btn.y, btn.x, btn.y + btn.h); if (isPressed) { bodyGrad.addColorStop(0, '#284a70'); bodyGrad.addColorStop(1, '#1c3055'); } else if (isHot) { bodyGrad.addColorStop(0, '#1a4080'); bodyGrad.addColorStop(1, '#142d55'); } else { bodyGrad.addColorStop(0, MC.BTN_BG_HIGHLIGHT); bodyGrad.addColorStop(1, MC.BTN_BG); } ctx.fillStyle = bodyGrad; // Border glow if (isHot) { ctx.strokeStyle = 'rgba(100,180,255,0.6)'; ctx.lineWidth = 1.5; } else { ctx.strokeStyle = isPressed ? MC.GOLD : MC.BTN_BORDER_GLOW; ctx.lineWidth = isPressed ? 2 : 1.2; } this._roundRect(ctx, btn.x, btn.y, btn.w, btn.h, r); ctx.fill(); ctx.stroke(); // Inner top highlight if (!isPressed) { ctx.save(); ctx.clip(); const hl = ctx.createLinearGradient(btn.x, btn.y, btn.x, btn.y + btn.h * 0.32); hl.addColorStop(0, isHot ? 'rgba(100,180,255,0.12)' : 'rgba(255,255,255,0.08)'); hl.addColorStop(1, 'rgba(255,255,255,0)'); ctx.fillStyle = hl; ctx.fillRect(btn.x, btn.y, btn.w, btn.h * 0.32); ctx.restore(); } // Icon if (iconType) { this._drawButtonIcon(ctx, iconX, iconY, iconSz, iconType, isPressed); } // Two-line text — larger fonts with clear vertical separation const pFont = font || `bold ${Math.max(15, btn.h * 0.32)}px Arial`; const sFont = `${Math.max(11, btn.h * 0.24)}px Arial`; if (subtitle) { ctx.fillStyle = isPressed ? MC.GOLD_LIGHT : MC.BTN_TEXT_PRIMARY; ctx.font = pFont; ctx.textAlign = 'left'; ctx.textBaseline = 'middle'; ctx.fillText(label, textX, btn.y + btn.h * 0.33); ctx.fillStyle = isPressed ? 'rgba(200,220,255,0.85)' : MC.BTN_TEXT_SECONDARY; ctx.font = sFont; ctx.fillText(subtitle, textX, btn.y + btn.h * 0.73); } else { ctx.fillStyle = isPressed ? MC.TITLE : MC.BTN_TEXT_PRIMARY; ctx.font = pFont; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText(label, btn.x + btn.w / 2, btn.y + btn.h / 2); } // Hot badge ("火爆" style corner badge) if (isHot && !isPressed) { const badgeW = 34; const badgeH = 18; const bx = btn.x + btn.w - badgeW - 2; const by = btn.y + 2; ctx.save(); ctx.fillStyle = MC.HOT_BADGE; this._roundRect(ctx, bx, by, badgeW, badgeH, 4); ctx.fill(); ctx.fillStyle = '#FFFFFF'; ctx.font = 'bold 10px Arial'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText('HOT', bx + badgeW / 2, by + badgeH / 2); ctx.restore(); } ctx.restore(); }, /** * Draw decorative treasure/chest icon in left panel. */ _drawTreasureIcon(ctx, cx, cy) { const pulse = 0.5 + 0.5 * Math.sin(this._tankAnim * 1.8); const size = 36; ctx.save(); // Glow halo ctx.globalAlpha = 0.12 + pulse * 0.1; ctx.fillStyle = MC.GOLD; ctx.beginPath(); ctx.arc(cx, cy, size * 0.7, 0, Math.PI * 2); ctx.fill(); ctx.globalAlpha = 1; // Chest body (rounded rectangle with metallic look) const bw = size * 0.85; const bh = size * 0.65; const br = 5; // Shadow ctx.fillStyle = 'rgba(0,0,0,0.25)'; this._roundRect(ctx, cx - bw / 2 + 2, cy - bh / 2 + 3, bw, bh, br); ctx.fill(); // Body gradient const chestGrad = ctx.createLinearGradient(cx - bw / 2, cy - bh / 2, cx - bw / 2, cy + bh / 2); chestGrad.addColorStop(0, '#FFD54F'); chestGrad.addColorStop(0.3, MC.GOLD); chestGrad.addColorStop(0.7, '#DAA520'); chestGrad.addColorStop(1, '#B8860B'); ctx.save(); ctx.shadowColor = MC.GOLD; ctx.shadowBlur = 12 + pulse * 5; ctx.fillStyle = chestGrad; this._roundRect(ctx, cx - bw / 2, cy - bh / 2, bw, bh, br); ctx.fill(); ctx.restore(); // Edge outline ctx.strokeStyle = 'rgba(255,235,150,0.6)'; ctx.lineWidth = 1; this._roundRect(ctx, cx - bw / 2, cy - bh / 2, bw, bh, br); ctx.stroke(); // Center lock/keyhole area const lockR = size * 0.14; ctx.fillStyle = '#7A5A0A'; ctx.beginPath(); ctx.arc(cx, cy, lockR, 0, Math.PI * 2); ctx.fill(); // Keyhole inner ctx.fillStyle = '#FFF3A8'; ctx.beginPath(); ctx.arc(cx, cy - 1, lockR * 0.45, 0, Math.PI * 2); ctx.fill(); ctx.fillRect(cx - 1.5, cy, 3, lockR * 0.6); // Horizontal lid line ctx.strokeStyle = 'rgba(120,80,0,0.5)'; ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(cx - bw * 0.38, cy - 2); ctx.lineTo(cx - lockR * 1.3, cy - 2); ctx.moveTo(cx + lockR * 1.3, cy - 2); ctx.lineTo(cx + bw * 0.38, cy - 2); ctx.stroke(); // Decorative rivets ctx.fillStyle = '#B8860B'; for (const [rx, ry] of [[-bw * 0.32, -bh * 0.25], [bw * 0.32, -bh * 0.25], [-bw * 0.32, bh * 0.25], [bw * 0.32, bh * 0.25]]) { ctx.beginPath(); ctx.arc(cx + rx, cy + ry, 2.2, 0, Math.PI * 2); ctx.fill(); } ctx.restore(); }, /** * Draw button icons — simplified vector-style icons for each button type. * @param {CanvasRenderingContext2D} ctx * @param {number} x - left position * @param {number} y - top position * @param {number} size - icon size (square) * @param {string} type - icon identifier * @param {boolean} isActive - pressed/highlighted state */ _drawButtonIcon(ctx, x, y, size, type, isActive) { const cx = x + size / 2; const cy = y + size / 2; const s = size / 2.2; // drawing scale ctx.save(); // Icon glow when active if (isActive) { ctx.shadowColor = MC.GOLD; ctx.shadowBlur = 8; } const baseColor = isActive ? MC.GOLD_LIGHT : MC.ICON_GOLD; const dimColor = isActive ? '#E0B020' : MC.GOLD_DIM; switch (type) { case 'trophy': { // Trophy cup ctx.fillStyle = baseColor; ctx.beginPath(); ctx.moveTo(cx - s * 0.5, cy - s * 0.7); ctx.lineTo(cx + s * 0.5, cy - s * 0.7); ctx.lineTo(cx + s * 0.35, cy - s * 0.1); ctx.lineTo(cx - s * 0.35, cy - s * 0.1); ctx.closePath(); ctx.fill(); // Cup base ctx.fillRect(cx - s * 0.25, cy - s * 0.1, s * 0.5, s * 0.35); ctx.fillRect(cx - s * 0.45, cy + s * 0.2, s * 0.9, s * 0.15); // Handles ctx.strokeStyle = baseColor; ctx.lineWidth = 2; ctx.beginPath(); ctx.arc(cx - s * 0.5, cy - s * 0.3, s * 0.25, Math.PI * 0.6, Math.PI * 1.4); ctx.stroke(); ctx.beginPath(); ctx.arc(cx + s * 0.5, cy - s * 0.3, s * 0.25, -Math.PI * 0.4, Math.PI * 0.4); ctx.stroke(); break; } case 'target': { // Crosshair target ctx.strokeStyle = baseColor; ctx.lineWidth = 2.5; // Outer circle ctx.beginPath(); ctx.arc(cx, cy, s * 0.7, 0, Math.PI * 2); ctx.stroke(); // Inner circle ctx.beginPath(); ctx.arc(cx, cy, s * 0.28, 0, Math.PI * 2); ctx.stroke(); // Crosshairs ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(cx - s * 0.95, cy); ctx.lineTo(cx - s * 0.45, cy); ctx.moveTo(cx + s * 0.45, cy); ctx.lineTo(cx + s * 0.95, cy); ctx.moveTo(cx, cy - s * 0.95); ctx.lineTo(cx, cy - s * 0.45); ctx.moveTo(cx, cy + s * 0.45); ctx.lineTo(cx, cy + s * 0.95); ctx.stroke(); // Center dot ctx.fillStyle = dimColor; ctx.beginPath(); ctx.arc(cx, cy, s * 0.12, 0, Math.PI * 2); ctx.fill(); break; } case 'users': { // Two user figures ctx.fillStyle = baseColor; // Left person (head + body) ctx.beginPath(); ctx.arc(cx - s * 0.32, cy - s * 0.2, s * 0.28, 0, Math.PI * 2); ctx.fill(); ctx.beginPath(); ctx.ellipse(cx - s * 0.32, cy + s * 0.42, s * 0.34, s * 0.22, 0, Math.PI, 0); ctx.fill(); // Right person (slightly behind) ctx.fillStyle = dimColor; ctx.globalAlpha = 0.85; ctx.beginPath(); ctx.arc(cx + s * 0.32, cy - s * 0.12, s * 0.26, 0, Math.PI * 2); ctx.fill(); ctx.beginPath(); ctx.ellipse(cx + s * 0.32, cy + s * 0.48, s * 0.30, s * 0.20, 0, Math.PI, 0); ctx.fill(); break; } case 'swords': { // Two crossed swords ctx.save(); ctx.translate(cx, cy); ctx.rotate(Math.PI / 4); // Sword 1 ctx.fillStyle = baseColor; ctx.fillRect(-s * 0.08, -s * 0.85, s * 0.16, s * 1.4); // Guard ctx.fillRect(-s * 0.38, -s * 0.05, s * 0.76, s * 0.14); // Sword 2 (rotated) ctx.rotate(Math.PI / 2); ctx.fillStyle = dimColor; ctx.fillRect(-s * 0.08, -s * 0.85, s * 0.16, s * 1.4); ctx.fillRect(-s * 0.38, -s * 0.05, s * 0.76, s * 0.14); ctx.restore(); break; } case 'coins': { // Stack of coins ctx.fillStyle = baseColor; // Bottom coin (offset) ctx.beginPath(); ctx.ellipse(cx + s * 0.15, cy + s * 0.18, s * 0.55, s * 0.22, 0, 0, Math.PI * 2); ctx.fill(); // Top coin ctx.fillStyle = MC.GOLD_LIGHT; ctx.beginPath(); ctx.ellipse(cx, cy, s * 0.55, s * 0.22, 0, 0, Math.PI * 2); ctx.fill(); // Coin detail ($ symbol hint) ctx.fillStyle = dimColor; ctx.font = `bold ${Math.floor(s)}px Arial`; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText('$', cx, cy + 1); break; } case 'skin': { // Tank/paintbrush icon ctx.fillStyle = baseColor; // Tank body this._roundRect(ctx, cx - s * 0.55, cy - s * 0.15, s * 1.1, s * 0.55, 3); ctx.fill(); // Barrel ctx.fillRect(cx + s * 0.2, cy - s * 0.65, s * 0.18, s * 0.52); // Wheels hint ctx.fillStyle = dimColor; ctx.beginPath(); ctx.arc(cx - s * 0.3, cy + s * 0.4, s * 0.15, 0, Math.PI * 2); ctx.fill(); ctx.beginPath(); ctx.arc(cx + s * 0.3, cy + s * 0.4, s * 0.15, 0, Math.PI * 2); ctx.fill(); // Star sparkle (skin customization hint) ctx.fillStyle = MC.GOLD_LIGHT; for (let i = 0; i < 3; i++) { const angle = (i / 3) * Math.PI * 2 - Math.PI / 2; const sx = cx + Math.cos(angle) * s * 0.7; const sy = cy + Math.sin(angle) * s * 0.7 - s * 0.2; ctx.beginPath(); ctx.arc(sx, sy, s * 0.08, 0, Math.PI * 2); ctx.fill(); } break; } case 'ranking': { // Podium/ranking icon ctx.fillStyle = baseColor; // 2nd place (left, shorter) ctx.fillRect(cx - s * 0.55, cy, s * 0.4, s * 0.6); ctx.fillStyle = '#C0C0C0'; ctx.fillRect(cx - s * 0.15, cy - s * 0.3, s * 0.4, s * 0.9); // 1st place (center, tallest) ctx.fillStyle = baseColor; ctx.fillRect(cx + s * 0.25, cy + s * 0.15, s * 0.4, s * 0.45); // Numbers ctx.fillStyle = '#333'; ctx.font = `bold ${Math.floor(s * 0.3)}px Arial`; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText('2', cx - s * 0.35, cy + s * 0.3); ctx.fillText('1', cx + s * 0.05, cy); ctx.fillText('3', cx + s * 0.45, cy + s * 0.4); break; } case 'settings': { // Gear/cog icon ctx.strokeStyle = baseColor; ctx.lineWidth = 2.5; ctx.beginPath(); ctx.arc(cx, cy, s * 0.35, 0, Math.PI * 2); ctx.stroke(); // Gear teeth ctx.fillStyle = baseColor; const teeth = 8; for (let i = 0; i < teeth; i++) { const angle = (i / teeth) * Math.PI * 2; const tx = cx + Math.cos(angle) * s * 0.5; const ty = cy + Math.sin(angle) * s * 0.5; ctx.beginPath(); ctx.arc(tx, ty, s * 0.1, 0, Math.PI * 2); ctx.fill(); } // Center hole ctx.fillStyle = dimColor; ctx.beginPath(); ctx.arc(cx, cy, s * 0.15, 0, Math.PI * 2); ctx.fill(); break; } case 'shield': { // Shield icon (for 3v3 team mode) ctx.fillStyle = baseColor; ctx.beginPath(); ctx.moveTo(cx, cy - s * 0.65); ctx.lineTo(cx + s * 0.55, cy - s * 0.35); ctx.lineTo(cx + s * 0.5, cy + s * 0.45); ctx.lineTo(cx, cy + s * 0.6); ctx.lineTo(cx - s * 0.5, cy + s * 0.45); ctx.lineTo(cx - s * 0.55, cy - s * 0.35); ctx.closePath(); ctx.fill(); // Inner detail ctx.strokeStyle = dimColor; ctx.lineWidth = 1; const shieldInner = 0.7; ctx.beginPath(); ctx.moveTo(cx, cy - s * 0.65 * shieldInner); ctx.lineTo(cx + s * 0.55 * shieldInner, cy - s * 0.35 * shieldInner); ctx.lineTo(cx + s * 0.5 * shieldInner, cy + s * 0.45 * shieldInner); ctx.lineTo(cx, cy + s * 0.6 * shieldInner); ctx.lineTo(cx - s * 0.5 * shieldInner, cy + s * 0.45 * shieldInner); ctx.lineTo(cx - s * 0.55 * shieldInner, cy - s * 0.35 * shieldInner); ctx.closePath(); ctx.stroke(); // "3V3" text inside ctx.fillStyle = dimColor; ctx.font = `bold ${Math.max(6, s * 0.32)}px Arial`; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText('3v3', cx, cy + s * 0.08); break; } default: // Fallback circle icon ctx.fillStyle = baseColor; ctx.beginPath(); ctx.arc(cx, cy, s * 0.5, 0, Math.PI * 2); ctx.fill(); } ctx.restore(); }, /** * Utility bar button — horizontal layout (icon on left, text on right). * Used for the 4-column bottom utility bar. */ _drawUtilButton(ctx, btn, label, subtitle, isPressed, customBg, iconType) { const r = 8; ctx.save(); ctx.globalAlpha = 0.85; const iconSz = Math.min(24, btn.h * 0.48); const innerPad = Math.max(8, btn.w * 0.06); // Shadow ctx.fillStyle = 'rgba(0,0,0,0.25)'; this._roundRect(ctx, btn.x + 1, btn.y + 2, btn.w, btn.h, r); ctx.fill(); // Body gradient const bgGrad = ctx.createLinearGradient(btn.x, btn.y, btn.x, btn.y + btn.h); if (isPressed) { bgGrad.addColorStop(0, '#243a5c'); bgGrad.addColorStop(1, '#1a2d4a'); } else if (customBg) { bgGrad.addColorStop(0, customBg); bgGrad.addColorStop(1, this._darkenColor(customBg, 12)); } else { bgGrad.addColorStop(0, '#182844'); bgGrad.addColorStop(1, '#121d33'); } ctx.fillStyle = bgGrad; ctx.strokeStyle = isPressed ? MC.GOLD : 'rgba(50,90,140,0.5)'; ctx.lineWidth = 1; this._roundRect(ctx, btn.x, btn.y, btn.w, btn.h, r); ctx.fill(); ctx.stroke(); // Icon on the left side if (iconType) { const ix = btn.x + innerPad; const iy = btn.y + (btn.h - iconSz) / 2; this._drawButtonIcon(ctx, ix, iy, iconSz, iconType, isPressed); } // Text to the right of icon — match right-panel mode button font size const txtX = btn.x + innerPad + iconSz + 6; const maxTxtW = btn.x + btn.w - innerPad - txtX; // Match mode button primary text size: bold max(15, h*0.32) const txtFont = `bold ${Math.max(14, btn.h * 0.30)}px Arial`; ctx.fillStyle = isPressed ? MC.GOLD_LIGHT : MC.BTN_TEXT_PRIMARY; ctx.font = txtFont; ctx.textAlign = 'left'; ctx.textBaseline = 'middle'; // Truncate long text to fit remaining width let displayLabel = label; while (ctx.measureText(displayLabel).width > maxTxtW && displayLabel.length > 3) { displayLabel = displayLabel.slice(0, -2) + '…'; } ctx.fillText(displayLabel, txtX, btn.y + btn.h / 2); ctx.restore(); }, /** Darken a hex color by a percentage */ _darkenColor(hex, percent) { const num = parseInt(hex.replace('#', ''), 16); const amt = Math.round(2.55 * percent); const R = Math.max((num >> 16) - amt, 0); const G = Math.max(((num >> 8) & 0x00FF) - amt, 0); const B = Math.max((num & 0x0000FF) - amt, 0); return '#' + (0x1000000 + R * 0x10000 + G * 0x100 + B).toString(16).slice(1); }, // ---- Tank Icon ---- _drawTankIcon(ctx, cx, cy) { const bounce = Math.sin(this._tankAnim * 3) * 1.5; const pulse = 0.5 + 0.5 * Math.sin(this._tankAnim * 2); const s = 13; // body half-size (compact: 26×26) ctx.save(); ctx.translate(cx, cy + bounce); // ── 1. OUTER GLOW HALO (breathing golden aura) ── ctx.save(); ctx.globalAlpha = 0.15 + pulse * 0.12; ctx.fillStyle = MC.GOLD; ctx.beginPath(); ctx.arc(0, 0, s * 1.55, 0, Math.PI * 2); ctx.fill(); ctx.restore(); // ── 2. GROUND SHADOW (soft ellipse underneath) ── ctx.save(); ctx.globalAlpha = 0.35; ctx.fillStyle = '#000'; ctx.beginPath(); ctx.ellipse(0, s + 6, s * 1.1, 3, 0, 0, Math.PI * 2); ctx.fill(); ctx.restore(); // ── 3. TRACKS (left & right, with segment pattern) ── const trackW = 5; const trackX = s; // Left track ctx.fillStyle = '#4A3508'; this._roundRect(ctx, -trackX - trackW, -s, trackW, s * 2, 1.5); ctx.fill(); // Right track this._roundRect(ctx, trackX, -s, trackW, s * 2, 1.5); ctx.fill(); // Track top highlight ctx.fillStyle = 'rgba(255, 220, 120, 0.35)'; ctx.fillRect(-trackX - trackW + 0.8, -s + 0.8, trackW - 1.6, 1); ctx.fillRect(trackX + 0.8, -s + 0.8, trackW - 1.6, 1); // Track segment lines (metallic plate pattern) ctx.strokeStyle = 'rgba(0, 0, 0, 0.55)'; ctx.lineWidth = 0.8; for (let ty = -s + 4; ty < s - 1; ty += 4) { ctx.beginPath(); ctx.moveTo(-trackX - trackW, ty); ctx.lineTo(-trackX, ty); ctx.stroke(); ctx.beginPath(); ctx.moveTo(trackX, ty); ctx.lineTo(trackX + trackW, ty); ctx.stroke(); } // ── 4. BODY (square, with vertical metallic gradient) ── ctx.save(); ctx.shadowColor = MC.GOLD; ctx.shadowBlur = 12; const bodyGrad = ctx.createLinearGradient(0, -s, 0, s); bodyGrad.addColorStop(0, '#FFF3A8'); // top highlight bodyGrad.addColorStop(0.3, MC.GOLD); // main gold bodyGrad.addColorStop(0.75, '#C89A1C'); bodyGrad.addColorStop(1, '#7A5A0A'); // bottom shadow ctx.fillStyle = bodyGrad; this._roundRect(ctx, -s, -s, s * 2, s * 2, 3); ctx.fill(); ctx.restore(); // ── 5. BODY EDGE OUTLINE ── ctx.strokeStyle = 'rgba(255, 235, 150, 0.7)'; ctx.lineWidth = 1; this._roundRect(ctx, -s, -s, s * 2, s * 2, 3); ctx.stroke(); // ── 6. TOP HIGHLIGHT STRIP (bright metallic sheen) ── ctx.fillStyle = 'rgba(255, 255, 255, 0.35)'; this._roundRect(ctx, -s + 2, -s + 2, s * 2 - 4, 3, 1.5); ctx.fill(); // ── 7. ARMOR PLATE DETAIL (X-cross rivets in 4 corners + center crosshair) ── const rivetOffset = s * 0.6; ctx.fillStyle = '#6B4A08'; for (const [rx, ry] of [[-rivetOffset, -rivetOffset], [rivetOffset, -rivetOffset], [-rivetOffset, rivetOffset], [rivetOffset, rivetOffset]]) { ctx.beginPath(); ctx.arc(rx, ry, 1.4, 0, Math.PI * 2); ctx.fill(); // Tiny rivet highlight ctx.fillStyle = 'rgba(255, 240, 180, 0.8)'; ctx.beginPath(); ctx.arc(rx - 0.3, ry - 0.3, 0.5, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = '#6B4A08'; } // ── 8. TURRET (diamond-shaped center cap with gradient) ── const turretR = s * 0.42; ctx.save(); ctx.shadowColor = MC.GOLD; ctx.shadowBlur = 6; const turretGrad = ctx.createRadialGradient(-turretR * 0.3, -turretR * 0.3, 0, 0, 0, turretR); turretGrad.addColorStop(0, '#FFF3A8'); turretGrad.addColorStop(0.5, '#E0B020'); turretGrad.addColorStop(1, '#8B6914'); ctx.fillStyle = turretGrad; // Diamond (rotated square) for "military hatch" feel ctx.beginPath(); ctx.moveTo(0, -turretR); ctx.lineTo(turretR, 0); ctx.lineTo(0, turretR); ctx.lineTo(-turretR, 0); ctx.closePath(); ctx.fill(); ctx.restore(); // Turret edge ctx.strokeStyle = 'rgba(255, 235, 150, 0.8)'; ctx.lineWidth = 0.8; ctx.beginPath(); ctx.moveTo(0, -turretR); ctx.lineTo(turretR, 0); ctx.lineTo(0, turretR); ctx.lineTo(-turretR, 0); ctx.closePath(); ctx.stroke(); // Turret internal cross ctx.strokeStyle = 'rgba(0, 0, 0, 0.35)'; ctx.lineWidth = 0.6; ctx.beginPath(); ctx.moveTo(0, -turretR); ctx.lineTo(0, turretR); ctx.stroke(); ctx.beginPath(); ctx.moveTo(-turretR, 0); ctx.lineTo(turretR, 0); ctx.stroke(); // Turret center hatch ctx.fillStyle = '#FFF3A8'; ctx.beginPath(); ctx.arc(0, 0, 2.5, 0, Math.PI * 2); ctx.fill(); // ── 9. BARREL (thick, with metallic gradient) ── const barrelW = 6; const barrelH = 16; const barrelY = -s - barrelH; ctx.save(); const barrelGrad = ctx.createLinearGradient(-barrelW / 2, 0, barrelW / 2, 0); barrelGrad.addColorStop(0, '#6B4A08'); barrelGrad.addColorStop(0.3, '#B8860B'); barrelGrad.addColorStop(0.5, '#FFF3A8'); barrelGrad.addColorStop(0.7, '#B8860B'); barrelGrad.addColorStop(1, '#6B4A08'); ctx.fillStyle = barrelGrad; this._roundRect(ctx, -barrelW / 2, barrelY, barrelW, barrelH, 1.5); ctx.fill(); ctx.restore(); // Barrel outline ctx.strokeStyle = 'rgba(255, 235, 150, 0.55)'; ctx.lineWidth = 0.6; this._roundRect(ctx, -barrelW / 2, barrelY, barrelW, barrelH, 1.5); ctx.stroke(); // ── 10. MUZZLE TIP (flared end with glow) ── ctx.save(); ctx.shadowColor = '#FFF3A8'; ctx.shadowBlur = 5 + pulse * 3; ctx.fillStyle = '#2A1D05'; this._roundRect(ctx, -barrelW / 2 - 1, barrelY - 2, barrelW + 2, 3, 1); ctx.fill(); ctx.restore(); // Muzzle inner bright dot ctx.fillStyle = `rgba(255, 240, 150, ${0.6 + pulse * 0.4})`; ctx.beginPath(); ctx.arc(0, barrelY - 0.5, 1, 0, Math.PI * 2); ctx.fill(); // ── 11. HEADLIGHTS (front of tank — 2 small glowing dots) ── ctx.fillStyle = `rgba(255, 255, 200, ${0.7 + pulse * 0.3})`; ctx.shadowColor = '#FFF3A8'; ctx.shadowBlur = 4; ctx.beginPath(); ctx.arc(-s * 0.55, -s + 2.5, 1.3, 0, Math.PI * 2); ctx.fill(); ctx.beginPath(); ctx.arc(s * 0.55, -s + 2.5, 1.3, 0, Math.PI * 2); ctx.fill(); ctx.shadowBlur = 0; ctx.restore(); }, // ---- Utility ---- _roundRect(ctx, x, y, w, h, r) { ctx.beginPath(); ctx.moveTo(x + r, y); ctx.lineTo(x + w - r, y); ctx.arcTo(x + w, y, x + w, y + r, r); ctx.lineTo(x + w, y + h - r); ctx.arcTo(x + w, y + h, x + w - r, y + h, r); ctx.lineTo(x + r, y + h); ctx.arcTo(x, y + h, x, y + h - r, r); ctx.lineTo(x, y + r); ctx.arcTo(x, y, x + r, y, r); ctx.closePath(); }, // ============================================================ // Player Profile (nickname acquisition) // ============================================================ /** * Load avatar image from URL for Canvas rendering. * @param {string} url * @private */ /** * Load background image from local path. * @private */ _loadBgImage() { if (this._bgImage) return; try { const img = wx.createImage(); img.onload = () => { this._bgImage = img; console.log('[MenuScene] Background image loaded:', img.width, 'x', img.height); }; img.onerror = (e) => { console.warn('[MenuScene] Failed to load background image, using gradient fallback'); }; img.src = 'js/ui/images/bg.png'; } catch (e) { console.warn('[MenuScene] _loadBgImage error:', e && e.message); } }, _loadAvatarImage(url) { if (!url || this._avatarImg) return; try { const img = wx.createImage(); img.onload = () => { this._avatarImg = img; }; img.onerror = () => { console.warn('[MenuScene] Failed to load avatar image'); }; img.src = url; } catch (e) { console.warn('[MenuScene] _loadAvatarImage error:', e && e.message); } }, /** * Kick off profile acquisition on menu enter. * * Since WeChat 2022-10, `wx.getUserInfo` returns "scope unauthorized" — * the ONLY way to get the real nickname + avatar is `wx.createUserInfoButton`. * We create a transparent overlay button that covers the avatar area in the * top-left corner. When the user taps their avatar, WeChat returns the real * profile data. * @private */ _initPlayerProfile() { const profile = GameGlobal.playerProfile; if (!profile) return; // If already granted (from a previous session's cache), no button needed if (profile.granted) return; // Layout must match the avatar area drawn in render(): // avatarX=10, avatarY=10, avatarSize=28 + nickname text area const avatarLayout = { x: 8, y: 8, width: 120, height: 32, }; // Create the UserInfoButton — visible style covering avatar + nickname area if (typeof profile.fetchSilent === 'function') { profile.fetchSilent(avatarLayout).then((ok) => { console.log('[MenuScene] fetchSilent completed, granted=', ok); if (ok) { // Button was tapped and profile was granted — destroy it profile.destroyUserInfoButton(); } }).catch((e) => { console.warn('[MenuScene] fetchSilent error:', e); }); } }, // ============================================================ // Touch // ============================================================ handleTouch(eventType, e) { if (eventType === 'touchstart') { const touch = e.touches[0]; const tx = touch.clientX; const ty = touch.clientY; for (let i = 0; i < buttonRects.length; i++) { const btn = buttonRects[i]; if (tx >= btn.x && tx <= btn.x + btn.w && ty >= btn.y && ty <= btn.y + btn.h) { this._pressedIndex = i; break; } } } else if (eventType === 'touchend') { if (this._pressedIndex >= 0) { const btn = buttonRects[this._pressedIndex]; this._pressedIndex = -1; const sm = GameGlobal.sceneManager; if (btn.scene === SCENE.GAME) { if (!sm._scenes.has(SCENE.BUFF_SELECT)) { const BuffSelectScene = require('./BuffSelectScene'); sm.register(SCENE.BUFF_SELECT, BuffSelectScene); } sm.switchTo(SCENE.BUFF_SELECT, { mode: btn.mode }); } else if (btn.scene === 'DAILY_GOLD') { const adm = GameGlobal.adManager; if (adm && adm.getDailyGoldRemaining() > 0) { adm.showDailyGoldAd((completed) => { if (completed) { try { wx.showToast({ title: t('dailyGold.reward') || '+100 Gold!', icon: 'none', duration: 1500 }); } catch (e) {} } }); } } else if (btn.scene === SCENE.SHOP) { if (!sm._scenes.has(SCENE.SHOP)) { const ShopScene = require('./ShopScene'); 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'); sm.register(SCENE.SETTINGS, SettingsScene); } sm.switchTo(SCENE.SETTINGS); } else if (btn.scene === SCENE.RANKING) { if (!sm._scenes.has(SCENE.RANKING)) { const RankingScene = require('./RankingScene'); sm.register(SCENE.RANKING, RankingScene); } sm.switchTo(SCENE.RANKING); } else if (btn.scene === SCENE.PVP_ROOM) { if (!sm._scenes.has(SCENE.PVP_ROOM)) { const RoomScene = require('./RoomScene'); sm.register(SCENE.PVP_ROOM, RoomScene); } sm.switchTo(SCENE.PVP_ROOM); } else if (btn.scene === SCENE.TEAM_2V2_ROOM) { if (!sm._scenes.has(SCENE.TEAM_2V2_ROOM)) { const Team2v2RoomScene = require('./Team2v2RoomScene'); sm.register(SCENE.TEAM_2V2_ROOM, Team2v2RoomScene); } sm.switchTo(SCENE.TEAM_2V2_ROOM); } else if (btn.scene === SCENE.TEAM_ROOM) { if (!sm._scenes.has(SCENE.TEAM_ROOM)) { const TeamRoomScene = require('./TeamRoomScene'); sm.register(SCENE.TEAM_ROOM, TeamRoomScene); } sm.switchTo(SCENE.TEAM_ROOM); } } } }, }; module.exports = MenuScene;