/* * CaelLab BY-SA Code License * Copyright (c) 2026 Yunyun(云云) By 虚舟实验室(CaelLab) / CaelLabGameTS * Source: https://git.caellab.com/yunyun/GoodPlanCraftLauncher * Source: https://github.com/yunyun-3782/GoodPlanCraftLauncher */ let GAME_DIR = null; async function initGameDir() { if (!GAME_DIR) { GAME_DIR = await window.gpcl.getGameDir(); } return GAME_DIR; } const usernameInput = document.getElementById('username'); const versionSelect = document.getElementById('version'); const launchBtn = document.getElementById('launch-btn'); const statusDiv = document.getElementById('status'); // Download page elements const cancelDownloadBtn = document.getElementById('cancel-download-btn'); const downloadBtn = document.getElementById('download-btn-page'); const versionListContainer = document.getElementById('version-list-container'); const versionListSection = document.getElementById('version-list-section'); const versionDetailPage = document.getElementById('version-detail-page'); const detailBackBtn = document.getElementById('detail-back-btn'); const detailDownloadBtn = document.getElementById('detail-download-btn'); const versionCountLabel = document.getElementById('version-count-label'); const menuDownload = document.getElementById('menu-download'); const menuLaunch = document.getElementById('menu-launch'); const menuSettings = document.getElementById('menu-settings'); const downloadPage = document.getElementById('download-page'); const launchPanel = document.getElementById('launch-panel'); const mainContent = document.querySelector('.main-content .container'); const minimizeBtn = document.getElementById('minimize-btn'); const closeBtn = document.getElementById('close-btn'); // Toast notification system const toastContainer = document.getElementById('toast-container'); const toastHistory = new Map(); const TOAST_DEDUP_MS = 3000; const dialogOverlay = document.getElementById('dialog-overlay'); const dialogIcon = document.getElementById('dialog-icon'); const dialogTitle = document.getElementById('dialog-title'); const dialogMessage = document.getElementById('dialog-message'); const dialogFooterSingle = document.getElementById('dialog-footer-single'); const dialogFooterConfirm = document.getElementById('dialog-footer-confirm'); const dialogBtnOk = document.getElementById('dialog-btn-ok'); const dialogBtnYes = document.getElementById('dialog-btn-yes'); const dialogBtnCancel = document.getElementById('dialog-btn-cancel'); let dialogResolve = null; function showDialog(options) { return new Promise((resolve) => { dialogResolve = resolve; const icons = { info: 'ℹ️', warning: '⚠️', error: '❌', question: '❓' }; dialogIcon.textContent = icons[options.type] || icons.info; dialogTitle.textContent = options.title || '提示'; dialogMessage.textContent = options.message || ''; if (options.type === 'confirm' || options.buttons) { dialogFooterSingle.classList.add('hidden'); dialogFooterConfirm.classList.remove('hidden'); } else { dialogFooterSingle.classList.remove('hidden'); dialogFooterConfirm.classList.add('hidden'); } dialogOverlay.classList.remove('hidden'); }); } function hideDialog() { dialogOverlay.classList.add('hidden'); dialogResolve = null; } if (dialogBtnOk) { dialogBtnOk.addEventListener('click', () => { if (dialogResolve) dialogResolve(true); hideDialog(); }); } if (dialogBtnYes) { dialogBtnYes.addEventListener('click', () => { if (dialogResolve) dialogResolve(true); hideDialog(); }); } if (dialogBtnCancel) { dialogBtnCancel.addEventListener('click', () => { if (dialogResolve) dialogResolve(false); hideDialog(); }); } // 监听下载时关闭窗口的询问 if (window.gpcl && window.gpcl.onConfirmCloseWhileDownloading) { window.gpcl.onConfirmCloseWhileDownloading(async () => { const result = await showDialog({ type: 'confirm', title: '确认关闭', message: '当前正在下载中,关闭会停止下载并清理已下载的文件。确定要关闭吗?' }); if (result) { // 用户点击"是",确认关闭并清理 if (window.gpcl && window.gpcl.confirmCloseDownload) { await window.gpcl.confirmCloseDownload(); } } else { // 用户点击"否",取消关闭 if (window.gpcl && window.gpcl.cancelClose) { await window.gpcl.cancelClose(); } } }); } function showToast(title, message, type = 'info', key = null) { if (!toastContainer) return; if (key) { const last = toastHistory.get(key); if (last && Date.now() - last < TOAST_DEDUP_MS) return; toastHistory.set(key, Date.now()); } const icons = { success: '✅', error: '❌', warning: '⚠️', info: 'ℹ️' }; const toast = document.createElement('div'); toast.className = `toast toast-${type}`; toast.innerHTML = ` ${icons[type] || icons.info}
${title}
${message ? `
${message}
` : ''}
`; toastContainer.appendChild(toast); setTimeout(() => { toast.classList.add('toast-out'); setTimeout(() => toast.remove(), 300); }, 4000); } // Chart elements let chartLoaded = false; function loadChartJs() { return new Promise((resolve) => { if (chartLoaded || window.Chart) { resolve(); return; } const script = document.createElement('script'); script.src = 'js/chart.umd.js'; script.onload = () => { chartLoaded = true; resolve(); }; script.onerror = () => resolve(); document.head.appendChild(script); }); } let speedChart = null; let speedData = []; let peakSpeed = 0; const downloadPanel = document.getElementById('download-panel'); let currentPage = 'launch'; function getMaxConcurrentFromSettings() { return DEFAULT_MAX_THREADS; } function showPage(name) { currentPage = name; const settingsPage = document.getElementById('settings-page'); const settingsSidebar = document.getElementById('settings-sidebar'); if (name === 'download') { if (downloadPage) downloadPage.classList.remove('hidden'); if (launchPanel) launchPanel.classList.add('hidden'); if (settingsPage) settingsPage.classList.add('hidden'); } else if (name === 'launch') { if (downloadPage) downloadPage.classList.add('hidden'); if (launchPanel) launchPanel.classList.remove('hidden'); if (settingsPage) settingsPage.classList.add('hidden'); } else if (name === 'settings') { if (downloadPage) downloadPage.classList.add('hidden'); if (launchPanel) launchPanel.classList.add('hidden'); if (settingsPage) settingsPage.classList.remove('hidden'); // 默认选中第一个子页面(游戏) switchSettingsTab('game'); } if (menuDownload) menuDownload.classList.toggle('active', name === 'download'); if (menuLaunch) menuLaunch.classList.toggle('active', name === 'launch'); if (menuSettings) menuSettings.classList.toggle('active', name === 'settings'); } function switchSettingsTab(tabName) { const sidebarItems = document.querySelectorAll('.settings-sidebar-item'); const contentPanels = document.querySelectorAll('.settings-content-panel'); sidebarItems.forEach(item => { item.classList.toggle('active', item.dataset.tab === tabName); }); contentPanels.forEach(panel => { panel.classList.toggle('active', panel.id === `settings-content-${tabName}`); panel.classList.toggle('hidden', panel.id !== `settings-content-${tabName}`); }); } // 更新菜单选中状态(确保菜单始终与当前页面同步) function updateMenuActive(pageName) { if (menuDownload) menuDownload.classList.toggle('active', pageName === 'download'); if (menuLaunch) menuLaunch.classList.toggle('active', pageName === 'launch'); if (menuSettings) menuSettings.classList.toggle('active', pageName === 'settings'); } if (menuDownload) menuDownload.addEventListener('click', () => showPage('download')); if (menuLaunch) menuLaunch.addEventListener('click', () => showPage('launch')); if (menuSettings) menuSettings.addEventListener('click', () => showPage('settings')); if (minimizeBtn) minimizeBtn.addEventListener('click', () => { if (window.gpcl && window.gpcl.minimizeWindow) window.gpcl.minimizeWindow(); }); if (closeBtn) closeBtn.addEventListener('click', () => { if (window.gpcl && typeof window.gpcl.closeWindow === 'function') { window.gpcl.closeWindow(); } else { // 备选方案:尝试直接关闭窗口 window.close(); } }); const customSelectHandlers = new Map(); function initCustomSelect(selectId, onChangeCallback) { const selectContainer = document.querySelector(`.custom-select[data-select-id="${selectId}"]`); if (!selectContainer) return; const trigger = selectContainer.querySelector('.custom-select-trigger'); const dropdown = selectContainer.querySelector('.custom-select-dropdown'); const valueSpan = selectContainer.querySelector('.custom-select-value'); const options = selectContainer.querySelectorAll('.custom-select-option'); // 保存初始状态 let currentValue = null; options.forEach(option => { if (option.classList.contains('selected')) { currentValue = option.dataset.value; } }); // 点击触发器打开/关闭下拉框 trigger.addEventListener('click', (e) => { e.stopPropagation(); const isOpen = dropdown.classList.contains('open'); closeAllCustomSelects(); if (!isOpen) { dropdown.classList.add('open'); trigger.classList.add('active'); } }); // 点击选项 options.forEach(option => { option.addEventListener('click', (e) => { e.stopPropagation(); if (option.classList.contains('disabled')) return; const value = option.dataset.value; const label = option.textContent; // 更新选中状态 options.forEach(opt => opt.classList.remove('selected')); option.classList.add('selected'); valueSpan.textContent = label; currentValue = value; // 关闭下拉框 closeAllCustomSelects(); // 调用回调 if (onChangeCallback) { onChangeCallback(value); } }); }); // 保存处理函数以便后续使用 customSelectHandlers.set(selectId, { setValue: (value, label) => { options.forEach(opt => opt.classList.remove('selected')); const targetOpt = selectContainer.querySelector(`.custom-select-option[data-value="${value}"]`); if (targetOpt) { targetOpt.classList.add('selected'); valueSpan.textContent = targetOpt.textContent; currentValue = value; } else if (label) { valueSpan.textContent = label; currentValue = value; } }, getValue: () => currentValue }); } function closeAllCustomSelects() { document.querySelectorAll('.custom-select-dropdown').forEach(d => d.classList.remove('open')); document.querySelectorAll('.custom-select-trigger').forEach(t => t.classList.remove('active')); } // 点击页面其他地方关闭下拉框 document.addEventListener('click', closeAllCustomSelects); // 提供全局访问方法 function setCustomSelectValue(selectId, value, label) { const handler = customSelectHandlers.get(selectId); if (handler) { handler.setValue(value, label); } } function getCustomSelectValue(selectId) { const handler = customSelectHandlers.get(selectId); return handler ? handler.getValue() : null; } const DEFAULT_MAX_THREADS = 64; const MAX_THREADS_LIMIT = 128; // Java版本 - 官方runtime目录名映射(用于显示提示) const JAVA_RUNTIME_NAME_MAP = { "8": "jre-legacy", "16": "java-runtime-beta", "17": "java-runtime-gamma", "21": "java-runtime-delta", "25": "java-runtime-epsilon" }; // Java版本说明(改进版,更准确的推荐) const JAVA_VERSION_DESC = { "8": { mcVersions: "1.7.10 - 1.16.5", desc: "经典版本兼容" }, "17": { mcVersions: "1.17 - 1.20.4", desc: "最稳定,推荐使用" }, "21": { mcVersions: "1.20.5 - 1.21+", desc: "最新LTS版本" }, "25": { mcVersions: "1.21+", desc: "最新尝鲜版" } }; function getJavaRuntimeName(javaVersion) { return JAVA_RUNTIME_NAME_MAP[javaVersion] || `jre${javaVersion}`; } // 初始化Java版本状态检测 async function initJavaVersionStatus() { const javaVersions = ["8", "17", "21", "25"]; for (const ver of javaVersions) { try { const result = await window.gpcl.checkJava(ver); const card = document.querySelector(`.java-version-card[data-version="${ver}"]`); const badge = document.getElementById(`java-${ver}-status`); const btn = card?.querySelector('.java-install-btn'); if (result.installed) { if (card) card.classList.add('installed'); if (badge) { badge.textContent = '已安装'; badge.className = 'java-version-badge installed'; } if (btn) { btn.textContent = '卸载'; btn.classList.add('installed'); btn.disabled = false; } } } catch (e) { console.error(`检查Java ${ver} 状态失败`, e); } } // 更新Java安装状态提示 updateJavaInstallStatus(); } // 更新Java安装状态提示 function updateJavaInstallStatus() { const statusEl = document.getElementById('java-install-status'); if (!statusEl) return; const javaVersions = ["8", "17", "21", "25"]; const installed = []; const notInstalled = []; javaVersions.forEach(ver => { const card = document.querySelector(`.java-version-card[data-version="${ver}"]`); if (card?.classList.contains('installed')) { installed.push(ver); } else { notInstalled.push(ver); } }); if (installed.length === javaVersions.length) { statusEl.textContent = '✅ 所有Java版本均已安装,可以运行任何Minecraft版本'; statusEl.style.background = 'rgba(76, 175, 80, 0.15)'; } else { statusEl.textContent = `已安装 Java ${installed.join(', ') || '无'} | 未安装 Java ${notInstalled.join(', ')}`; statusEl.style.background = 'rgba(79, 195, 247, 0.08)'; } } // 绑定Java安装按钮事件 function bindJavaInstallButtons() { const buttons = document.querySelectorAll('.java-install-btn'); buttons.forEach(btn => { btn.addEventListener('click', async () => { const version = btn.dataset.version; if (btn.classList.contains('installed')) { await uninstallJavaWithPanel(version); } else { await installJavaWithPanel(version); } }); }); } // 使用通用面板安装Java async function installJavaWithPanel(javaVersion) { const filenameEl = document.getElementById('download-filename'); const downloadStatusEl = document.getElementById('download-status'); const downloadPanel = document.getElementById('download-panel'); const progressFill = document.getElementById('progress-fill'); const progressText = document.getElementById('progress-text'); const cancelBtn = document.getElementById('cancel-download-btn'); // 滚动到下载页面顶部(用户主动操作) const downloadPage = document.getElementById('download-page'); if (downloadPage) { downloadPage.scrollTop = 0; } // 显示通用下载面板 if (downloadPanel) { downloadPanel.classList.remove('hidden'); downloadPanel.classList.add('download-panel-container'); } if (filenameEl) filenameEl.textContent = `正在安装: Java ${javaVersion}`; if (downloadStatusEl) downloadStatusEl.textContent = `准备下载 Java ${javaVersion}...`; if (progressFill) progressFill.style.width = '0%'; if (progressText) progressText.textContent = '0%'; if (cancelBtn) cancelBtn.disabled = false; // 禁用Java安装按钮 document.querySelectorAll('.java-install-btn').forEach(btn => { btn.disabled = true; }); try { const result = await window.gpcl.installJava(javaVersion); if (result.success) { if (downloadStatusEl) downloadStatusEl.textContent = `✅ Java ${javaVersion} 安装成功!`; if (progressFill) progressFill.style.width = '100%'; if (progressText) progressText.textContent = '100%'; showToast('安装成功', `Java ${javaVersion} 已安装完成`, 'success', 'java-install-success'); // 更新Java卡片状态 const card = document.querySelector(`.java-version-card[data-version="${javaVersion}"]`); const badge = document.getElementById(`java-${javaVersion}-status`); const btn = card?.querySelector('.java-install-btn'); if (card) card.classList.add('installed'); if (badge) { badge.textContent = '已安装'; badge.className = 'java-version-badge installed'; } if (btn) { btn.textContent = '卸载'; btn.classList.add('installed'); btn.disabled = false; } updateJavaInstallStatus(); setTimeout(() => { if (downloadPanel) { downloadPanel.classList.add('hidden'); downloadPanel.classList.remove('download-panel-container'); } }, 2000); } else { throw new Error(result.error || '安装失败'); } } catch (err) { if (downloadStatusEl) downloadStatusEl.textContent = `❌ 安装失败: ${err.message || err}`; showToast('安装失败', err.message || String(err), 'error', 'java-install-failed'); } // 重新启用Java安装按钮 document.querySelectorAll('.java-install-btn').forEach(btn => { if (!btn.classList.contains('installed')) { btn.disabled = false; } }); if (cancelBtn) cancelBtn.disabled = true; } // 卸载Java运行时(使用弹窗确认) async function uninstallJavaWithPanel(javaVersion) { const confirmed = await showDialog({ type: 'confirm', title: '确认卸载', message: `确定要卸载 Java ${javaVersion} 吗?` }); if (!confirmed) return; showToast('正在卸载', `正在卸载 Java ${javaVersion}...`, 'info', 'java-uninstalling'); try { const result = await window.gpcl.uninstallJava(javaVersion); if (result.success) { showToast('卸载成功', `Java ${javaVersion} 已卸载`, 'success', 'java-uninstall-success'); // 更新Java卡片状态 const card = document.querySelector(`.java-version-card[data-version="${javaVersion}"]`); const badge = document.getElementById(`java-${javaVersion}-status`); const btn = card?.querySelector('.java-install-btn'); if (card) card.classList.remove('installed'); if (badge) { badge.textContent = ''; badge.className = 'java-version-badge'; } if (btn) { btn.textContent = '安装'; btn.classList.remove('installed'); btn.disabled = false; } updateJavaInstallStatus(); } else { throw new Error(result.error || '卸载失败'); } } catch (err) { showToast('卸载失败', err.message || String(err), 'error', 'java-uninstall-failed'); } } // 改进的Java版本推荐(基于Minecraft版本JSON) function getRecommendedJavaVersionFromMC(mcVersion) { // 解析Minecraft版本号 const parts = mcVersion.split('.'); let major = parseInt(parts[0], 10); let minor = parseInt(parts[1], 10); let patch = parts.length > 2 ? parseInt(parts[2], 10) || 0 : 0; // 处理无效 major if (isNaN(major)) major = 1; // Minecraft版本对应Java版本(Mojang官方推荐) // 1.7-1.16: Java 8 // 1.17-1.20.4: Java 17 // 1.20.5-1.21.4: Java 21 // 1.22+ / 21+: Java 21 // 26+: Java 21 // 处理像 "26" 这种只有 major 没有 minor 的情况 if (isNaN(minor)) { // 26 及以上使用 Java 25 if (major >= 26) { return { version: '25', reason: `Minecraft ${mcVersion} 推荐使用 Java 25` }; } // 21-25 使用 Java 21 if (major >= 21) { return { version: '21', reason: `Minecraft ${mcVersion} 推荐使用 Java 21` }; } return { version: '17', reason: '使用推荐的稳定版' }; } // 处理 1.x.x 的旧版本 if (major === 1) { if (minor <= 16) { return { version: '8', reason: `Minecraft ${mcVersion} 官方推荐 Java 8` }; } else if (minor <= 20 || (minor === 20 && patch <= 4)) { return { version: '17', reason: `Minecraft ${mcVersion} 官方推荐 Java 17` }; } else if (minor === 20 && patch >= 5) { return { version: '21', reason: `Minecraft ${mcVersion} 推荐使用 Java 21` }; } else if (minor >= 21) { return { version: '21', reason: `Minecraft ${mcVersion} 推荐使用 Java 21` }; } } // 处理 2.x.x 和更大的版本(21, 24, 26, ...) if (major >= 2) { // 26 及以上使用 Java 25 if (major >= 26) { return { version: '25', reason: `Minecraft ${mcVersion} 推荐使用 Java 25` }; } return { version: '21', reason: `Minecraft ${mcVersion} 推荐使用 Java 21` }; } // 默认返回最稳定的版本 return { version: '17', reason: '使用推荐的稳定版' }; } // 监听Java下载进度 if (window.gpcl && window.gpcl.onDownloadProgress) { const originalCallback = window.gpcl.onDownloadProgress; window.gpcl.onDownloadProgress((data) => { // 更新通用下载面板的进度 const progressFill = document.getElementById('progress-fill'); const progressText = document.getElementById('progress-text'); const filenameEl = document.getElementById('download-filename'); const downloadStatusEl = document.getElementById('download-status'); if (progressFill && progressText) { const percent = data.percent || 0; progressFill.style.width = percent + '%'; progressText.textContent = percent.toFixed(1) + '%'; } if (data.label && filenameEl) { filenameEl.textContent = data.label; } if (data.label && downloadStatusEl) { downloadStatusEl.textContent = `正在下载: ${data.label}`; } // 更新漂浮通知的进度 const floatProgressFill = document.getElementById('float-progress-fill'); const floatProgressText = document.getElementById('float-progress-text'); const floatNotification = document.getElementById('float-notification'); if (floatProgressFill && floatProgressText && !floatNotification.classList.contains('hidden')) { const percent = data.percent || 0; floatProgressFill.style.width = percent + '%'; floatProgressText.textContent = percent.toFixed(1) + '%'; } // 调用原始回调 if (typeof originalCallback === 'function') { originalCallback(data); } }); } // 漂浮通知控制函数 function showFloatNotification(title, text) { const notification = document.getElementById('float-notification'); const titleEl = document.getElementById('float-notification-title'); const textEl = document.getElementById('float-notification-text'); if (notification && titleEl && textEl) { titleEl.textContent = title; textEl.textContent = text; // 重置进度 const progressFill = document.getElementById('float-progress-fill'); const progressText = document.getElementById('float-progress-text'); if (progressFill) progressFill.style.width = '0%'; if (progressText) progressText.textContent = '0%'; notification.classList.remove('hidden'); } } function hideFloatNotification() { const notification = document.getElementById('float-notification'); if (notification) { notification.classList.add('hidden'); } } // 监听自动下载开始事件(通过游戏日志检测) if (window.gpcl && window.gpcl.onGameLog) { window.gpcl.onGameLog((text) => { if (text.includes('Java') && text.includes('未安装') && text.includes('自动开始下载')) { const match = text.match(/Java\s+(\d+)/); if (match) { showFloatNotification( `正在下载 Java ${match[1]}`, `检测到您没有 Java ${match[1]},正在自动下载` ); } } }); } // 绑定漂浮通知关闭按钮 const floatCloseBtn = document.getElementById('float-notification-close'); if (floatCloseBtn) { floatCloseBtn.addEventListener('click', hideFloatNotification); } // 监听Java下载完成 if (window.gpcl && window.gpcl.onJavaDownloadCompleted) { window.gpcl.onJavaDownloadCompleted((data) => { const { javaVersion } = data; const downloadStatusEl = document.getElementById('download-status'); if (downloadStatusEl) { downloadStatusEl.textContent = '✅ 下载完成,正在解压安装...'; } }); } // 监听Java下载失败 if (window.gpcl && window.gpcl.onJavaDownloadFailed) { window.gpcl.onJavaDownloadFailed((data) => { const { error } = data; const downloadStatusEl = document.getElementById('download-status'); if (downloadStatusEl) { downloadStatusEl.textContent = `❌ 下载失败: ${error}`; } showToast('下载失败', error, 'error', 'java-download-failed'); }); } async function loadSettingFromStorage() { try { const settings = await loadSettings(); return settings.download?.maxConcurrent || DEFAULT_MAX_THREADS; } catch (e) { return DEFAULT_MAX_THREADS; } } async function saveSettingToStorage(maxConcurrent) { try { const settings = await loadSettings(); settings.download.maxConcurrent = maxConcurrent; await gpcl.saveSettings(settings); return true; } catch (e) { return false; } } const settingsNumInput = document.getElementById('num-threads'); const settingsBtnMinus = document.getElementById('btn-minus'); const settingsBtnPlus = document.getElementById('btn-plus'); const settingsBtnSave = document.getElementById('btn-save'); if (settingsNumInput) { (async () => { settingsNumInput.value = await loadSettingFromStorage(); })(); function clampSettingsInput() { let v = parseInt(settingsNumInput.value, 10); if (isNaN(v) || v < 1) v = 1; if (v > MAX_THREADS_LIMIT) v = MAX_THREADS_LIMIT; settingsNumInput.value = v; } settingsNumInput.addEventListener('change', clampSettingsInput); settingsNumInput.addEventListener('blur', clampSettingsInput); if (settingsBtnMinus) { settingsBtnMinus.addEventListener('click', () => { let v = parseInt(settingsNumInput.value, 10) || 1; if (v > 1) { settingsNumInput.value = v - 1; clampSettingsInput(); } }); } if (settingsBtnPlus) { settingsBtnPlus.addEventListener('click', () => { let v = parseInt(settingsNumInput.value, 10) || 1; if (v < MAX_THREADS_LIMIT) { settingsNumInput.value = v + 1; clampSettingsInput(); } }); } if (settingsBtnSave) { settingsBtnSave.addEventListener('click', async () => { clampSettingsInput(); await saveSettingToStorage(parseInt(settingsNumInput.value, 10)); const orig = settingsBtnSave.textContent; settingsBtnSave.textContent = '已保存!'; setTimeout(() => { settingsBtnSave.textContent = orig; }, 1500); }); } async function reloadAllSettingsToUI() { const settings = await loadSettings(); const settingsMemory = document.getElementById('settings-memory'); if (settingsMemory) { settingsMemory.value = settings.game?.memory || '4'; } const settingsWindowMode = document.getElementById('settings-window-mode'); if (settingsWindowMode) { settingsWindowMode.value = settings.game?.windowMode || 'windowed'; } const settingsTheme = document.getElementById('settings-theme'); if (settingsTheme) { settingsTheme.value = settings.appearance?.theme || 'dark'; applyTheme(settings.appearance?.theme || 'dark'); } const settingsScale = document.getElementById('settings-scale'); if (settingsScale) { settingsScale.value = settings.appearance?.scale || '100'; applyScale(settings.appearance?.scale || '100'); } const settingsNumInput = document.getElementById('num-threads'); if (settingsNumInput) { settingsNumInput.value = settings.download?.maxConcurrent || 64; } const javaMirrorSelect = document.getElementById('java-mirror-select'); const customMirrorContainer = document.getElementById('custom-java-mirror-container'); const customMirrorUrl = document.getElementById('custom-java-mirror-url'); if (javaMirrorSelect) { javaMirrorSelect.value = settings.download?.javaMirror || 'tsinghua'; if (customMirrorContainer) { customMirrorContainer.classList.toggle('hidden', javaMirrorSelect.value !== 'custom'); } if (customMirrorUrl) { customMirrorUrl.value = settings.download?.customJavaMirror || ''; } } const autoCheckUpdate = document.getElementById('auto-check-update'); if (autoCheckUpdate) { autoCheckUpdate.checked = settings.advanced?.autoCheckUpdate !== false; } const preventMultipleLaunch = document.getElementById('prevent-multiple-launch'); if (preventMultipleLaunch) { preventMultipleLaunch.checked = settings.advanced?.preventMultipleLaunch !== false; } const autoClearLogs = document.getElementById('auto-clear-logs'); const logRetentionContainer = document.getElementById('log-retention-container'); const logRetentionValue = document.getElementById('log-retention-value'); const logRetentionUnit = document.getElementById('log-retention-unit'); if (autoClearLogs) { autoClearLogs.checked = settings.advanced?.autoClearLogs !== false; } if (logRetentionContainer) { logRetentionContainer.classList.toggle('hidden', settings.advanced?.autoClearLogs === false); } if (logRetentionValue) { logRetentionValue.value = settings.advanced?.logRetentionValue || 7; } if (logRetentionUnit) { logRetentionUnit.value = settings.advanced?.logRetentionUnit || 'day'; } const playStartupAnimationEl = document.getElementById('play-startup-animation'); if (playStartupAnimationEl) { playStartupAnimationEl.checked = settings.advanced?.playStartupAnimation === true; } const developerModeEl = document.getElementById('developer-mode'); if (developerModeEl) { developerModeEl.checked = settings.advanced?.developerMode === true; } } } function initChart() { const ctx = document.getElementById('speedChart'); if (!ctx) return; const chartCtx = ctx.getContext('2d'); const gradient = chartCtx.createLinearGradient(0, 0, 0, 180); gradient.addColorStop(0, 'rgba(79, 195, 247, 0.3)'); gradient.addColorStop(1, 'rgba(79, 195, 247, 0)'); speedChart = new Chart(chartCtx, { type: 'line', data: { labels: Array(40).fill(''), datasets: [{ label: '下载速度', data: Array(40).fill(0), borderColor: '#4fc3f7', backgroundColor: gradient, borderWidth: 2, fill: true, tension: 0.4, pointRadius: 0, pointHoverRadius: 4, pointHoverBackgroundColor: '#4fc3f7' }] }, options: { responsive: true, maintainAspectRatio: false, interaction: { intersect: false, mode: 'index' }, plugins: { legend: { display: false }, tooltip: { backgroundColor: 'rgba(0, 0, 0, 0.8)', titleColor: '#fff', bodyColor: '#4fc3f7', borderColor: 'rgba(79, 195, 247, 0.3)', borderWidth: 1, padding: 10, callbacks: { label: function(context) { return `速度: ${context.parsed.y.toFixed(2)} Mbps`; } } } }, scales: { x: { display: false }, y: { beginAtZero: true, suggestedMax: 10, grid: { color: 'rgba(255, 255, 255, 0.05)' }, ticks: { display: false } } } } }); } function updateChart(speed) { if (!speedChart) return; speedData.push(speed); if (speedData.length > 40) speedData.shift(); speedChart.data.datasets[0].data = speedData; const maxSpeed = Math.max(...speedData, 1); speedChart.options.scales.y.suggestedMax = maxSpeed * 1.5; speedChart.update('none'); } function resetChart() { speedData = Array(40).fill(0); peakSpeed = 0; if (speedChart) { speedChart.data.datasets[0].data = speedData; speedChart.update(); } const currentSpeedEl = document.getElementById('current-speed'); const peakSpeedEl = document.getElementById('peak-speed'); const progressFillEl = document.getElementById('progress-fill'); const progressTextEl = document.getElementById('progress-text'); if (currentSpeedEl) currentSpeedEl.textContent = '0.00 Mbps'; if (peakSpeedEl) peakSpeedEl.textContent = '0.00 Mbps'; if (progressFillEl) progressFillEl.style.width = '0%'; if (progressTextEl) progressTextEl.textContent = '0%'; } // 注意:下载进度监听在 startDownload 函数中单独处理,避免全局监听器冲突 // addLog removed - using toast notifications instead function setStatus(text, type = 'info') { statusDiv.textContent = text; statusDiv.style.color = type === 'error' ? '#ef5350' : type === 'success' ? '#66bb6a' : '#e0e0e0'; } async function loadVersions(silent = false) { try { let versions = []; // 尝试调用主进程API if (window.gpcl && typeof window.gpcl.scanVersions === 'function') { try { versions = await window.gpcl.scanVersions(null, silent); } catch (e) { console.error('调用scanVersions失败:', e); } } // 更新启动按钮状态(无论 versionSelect 是否存在都要更新) if (!versions || versions.length === 0) { if (!silent) setStatus('没有本地版本,请先下载'); // 切换按钮为前往下载状态 updateLaunchButtonState(false); } else { if (!silent) setStatus(`发现 ${versions.length} 个已安装版本`); // 切换按钮为开始游戏状态 updateLaunchButtonState(true); } // 检查 versionSelect 是否存在 if (!versionSelect) { if (!silent) console.warn('versionSelect 元素不存在,跳过版本列表渲染'); return; } versionSelect.innerHTML = ''; if (!versions || versions.length === 0) { const opt = document.createElement('option'); opt.disabled = true; opt.textContent = '未发现已安装的版本'; versionSelect.appendChild(opt); return; } versions.sort((a, b) => b.localeCompare(a, undefined, { numeric: true })); for (const v of versions) { let displayName = `Minecraft ${v}`; if (window.gpcl && typeof window.gpcl.getVersionDisplayName === 'function') { try { displayName = await window.gpcl.getVersionDisplayName(v); } catch (e) { console.warn(`获取版本显示名称失败: ${v}`, e); } } const opt = document.createElement('option'); opt.value = v; opt.textContent = displayName; versionSelect.appendChild(opt); } } catch (err) { if (!silent) { setStatus('扫描本地版本失败', 'error'); showToast('扫描失败', err.message || String(err), 'error'); } // 出错时也更新按钮为无游戏状态 updateLaunchButtonState(false); } } // 更新启动按钮状态 function updateLaunchButtonState(hasGame) { const launchBtn = document.getElementById('launch-btn'); const launchText = launchBtn?.querySelector('.launch-text'); if (launchBtn && launchText) { if (hasGame) { launchText.textContent = '▶ 开始游戏'; launchBtn.dataset.mode = 'launch'; // 移除红色样式 launchBtn.classList.remove('no-game'); } else { launchText.textContent = '⏬ 前往下载'; launchBtn.dataset.mode = 'download'; // 添加红色样式 launchBtn.classList.add('no-game'); } } } // 版本时间记录相关功能 const VERSION_HISTORY_FILE = 'gpcl_version_history.json'; const VERSION_HISTORY_PATH = 'users'; // 获取版本历史记录 async function getVersionHistory() { try { if (window.gpcl && typeof window.gpcl.readJsonFile === 'function') { const history = await window.gpcl.readJsonFile(VERSION_HISTORY_PATH, VERSION_HISTORY_FILE); return history || {}; } } catch (e) { console.error('从文件读取版本历史失败:', e); } return {}; } // 保存版本历史记录 async function saveVersionHistory(history) { if (window.gpcl && typeof window.gpcl.writeJsonFile === 'function') { try { await window.gpcl.writeJsonFile(VERSION_HISTORY_PATH, VERSION_HISTORY_FILE, history); } catch (e) { console.error('写入版本历史到文件失败:', e); } } } // 记录版本启动时间 async function recordVersionLaunch(versionId) { try { const history = await getVersionHistory(); if (!history[versionId]) { history[versionId] = {}; } history[versionId].lastLaunch = Date.now(); await saveVersionHistory(history); } catch (e) { console.error('记录版本启动时间失败:', e); } } // 记录版本下载成功时间 async function recordVersionDownload(versionId) { try { const history = await getVersionHistory(); if (!history[versionId]) { history[versionId] = {}; } history[versionId].downloadTime = Date.now(); history[versionId].lastLaunch = Date.now(); // 下载成功也作为最后启动时间 await saveVersionHistory(history); } catch (e) { console.error('记录版本下载时间失败:', e); } } // 渲染版本选择列表 async function renderVersionSelectList() { const container = document.getElementById('launch-version-list'); const countEl = document.getElementById('version-count'); if (!container) return; try { let versions = []; // 尝试调用主进程API if (window.gpcl && typeof window.gpcl.scanVersions === 'function') { try { versions = await window.gpcl.scanVersions(null, true); } catch (e) { console.error('调用scanVersions失败:', e); } } const history = await getVersionHistory(); if (!versions || versions.length === 0) { container.innerHTML = '
暂无已安装的版本
'; countEl.textContent = '0 个版本'; return; } // 按最后启动时间排序,最近启动的排在前面 versions.sort((a, b) => { const timeA = history[a]?.lastLaunch || history[a]?.downloadTime || 0; const timeB = history[b]?.lastLaunch || history[b]?.downloadTime || 0; return timeB - timeA; }); container.innerHTML = ''; versions.forEach((versionId, index) => { const item = document.createElement('div'); item.className = 'version-item'; item.dataset.versionId = versionId; // 获取版本类型图标 const icon = index === 0 && (history[versionId]?.lastLaunch || history[versionId]?.downloadTime) ? '⭐' : '📦'; // 格式化时间 const lastTime = history[versionId]?.lastLaunch || history[versionId]?.downloadTime; let timeText = ''; if (lastTime) { const date = new Date(lastTime); const now = new Date(); const diff = now.getTime() - date.getTime(); if (diff < 60000) { timeText = '刚刚'; } else if (diff < 3600000) { timeText = `${Math.floor(diff / 60000)} 分钟前`; } else if (diff < 86400000) { timeText = `${Math.floor(diff / 3600000)} 小时前`; } else { timeText = `${date.getMonth() + 1}/${date.getDate()}`; } } item.innerHTML = ` ${icon} ${versionId} ${timeText} `; item.addEventListener('click', () => { selectVersion(versionId); }); container.appendChild(item); }); countEl.textContent = `${versions.length} 个版本`; // 如果没有选中的版本,自动选中第一个 if (!selectedVersionId && versions.length > 0) { selectVersion(versions[0]); } } catch (err) { container.innerHTML = '
加载版本列表失败
'; countEl.textContent = '加载失败'; } } // 选择版本 function selectVersion(versionId) { selectedVersionId = versionId; // 更新UI const items = document.querySelectorAll('.version-item'); items.forEach(item => { item.classList.toggle('selected', item.dataset.versionId === versionId); }); const infoEl = document.getElementById('selected-version-info'); const nameEl = document.getElementById('selected-version-name'); if (infoEl && nameEl) { nameEl.textContent = `Minecraft ${versionId}`; infoEl.classList.remove('hidden'); } // 更新启动按钮下方的小字显示 const displayEl = document.getElementById('selected-version-display'); const textEl = document.getElementById('selected-version-text'); if (displayEl && textEl) { textEl.textContent = versionId; displayEl.classList.remove('hidden'); } // 更新启动按钮状态 updateLaunchButtonState(true); } // 检查版本是否已安装 async function isVersionInstalled(versionId) { try { const versions = await window.gpcl.scanVersions(null, true); return versions.includes(versionId); } catch { return false; } } let allVersions = []; let selectedVersionId = null; // 模组加载器相关状态 let modLoaderState = { forge: { available: false, versions: [], selected: null, expanded: false }, fabric: { available: false, versions: [], selected: null, expanded: false }, optifine: { available: false, versions: [], selected: null, expanded: false } }; // 模组加载器兼容性规则 const MOD_LOADER_COMPATIBILITY = { // Forge 和 Fabric 互斥 forge: ['fabric'], fabric: ['forge'], // OptiFine 与 Forge/Fabric 都互斥 optifine: ['forge', 'fabric'] }; // 检测 Forge 可用性 async function checkForgeAvailability(mcVersion) { try { // Forge 支持 1.1 及以上版本 const parts = mcVersion.split('.'); const major = parseInt(parts[0], 10) || 1; const minor = parseInt(parts[1], 10) || 0; // 1.1 以下不支持 if (major === 1 && minor < 1) { return { available: false, versions: [] }; } // 从主进程获取 Forge 版本列表 const result = await window.gpcl.getForgeVersions(mcVersion); if (result.success) { return { available: result.versions.length > 0, versions: result.versions }; } return { available: false, versions: [] }; } catch (e) { console.error('检测 Forge 可用性失败:', e); return { available: false, versions: [] }; } } // 检测所有模组加载器的可用性 async function checkAllModLoaders(mcVersion) { // 重置状态 modLoaderState = { forge: { available: false, versions: [], selected: null, expanded: false } }; // 检测 Forge const forgeResult = await checkForgeAvailability(mcVersion); modLoaderState.forge = { ...modLoaderState.forge, ...forgeResult }; // 更新 UI updateModLoaderUI(); } // 更新模组加载器 UI function updateModLoaderUI() { const loaders = ['forge']; loaders.forEach(loader => { const state = modLoaderState[loader]; const card = document.getElementById(`${loader}-option`); const badge = document.getElementById(`${loader}-badge`); const content = document.getElementById(`${loader}-content`); const loading = document.getElementById(`${loader}-loading`); const versionsContainer = document.getElementById(`${loader}-versions`); const error = document.getElementById(`${loader}-error`); if (!card || !badge) return; // 更新徽章状态 if (state.available) { badge.textContent = '可用'; badge.className = 'detail-option-badge available'; card.classList.remove('not-available'); } else { badge.textContent = '不可用'; badge.className = 'detail-option-badge unavailable'; card.classList.add('not-available'); } // 更新内容区域(仅在已展开时显示) if (state.expanded && state.available) { content.classList.remove('hidden'); loading.classList.add('hidden'); error.classList.add('hidden'); versionsContainer.classList.remove('hidden'); // 渲染版本列表 renderModLoaderVersionList(loader, state.versions); } else if (state.expanded && !state.available) { content.classList.remove('hidden'); loading.classList.add('hidden'); versionsContainer.classList.add('hidden'); error.classList.remove('hidden'); } else { content.classList.add('hidden'); } }); } // 渲染模组加载器版本列表 function renderModLoaderVersionList(loader, versions) { const container = document.getElementById(`${loader}-versions`); if (!container) return; container.innerHTML = ''; versions.forEach((version, index) => { const item = document.createElement('div'); item.className = 'detail-option-version-item'; item.dataset.versionId = version.id; item.dataset.version = version.version; // 如果是已选中的版本,添加选中样式 if (modLoaderState[loader].selected === version.version) { item.classList.add('selected'); } item.innerHTML = ` ${version.name} `; item.addEventListener('click', () => selectModLoaderVersion(loader, version.version, version.id)); container.appendChild(item); }); } // 选择模组加载器版本 function selectModLoaderVersion(loader, version, versionId) { const state = modLoaderState[loader]; // 如果点击的是已选中的版本,则取消选择 if (state.selected === version) { state.selected = null; } else { state.selected = version; } // 更新版本列表 UI const container = document.getElementById(`${loader}-versions`); if (container) { const items = container.querySelectorAll('.detail-option-version-item'); items.forEach(item => { item.classList.toggle('selected', item.dataset.version === state.selected); }); } } // 更新互斥 UI function updateIncompatibilityUI() { // 目前只有 Forge,无需处理互斥 } // 获取加载器显示名称 function getLoaderDisplayName(loader) { const names = { forge: 'Forge' }; return names[loader] || loader; } // 切换模组加载器展开/折叠 function toggleModLoader(loader) { const state = modLoaderState[loader]; const card = document.getElementById(`${loader}-option`); const content = document.getElementById(`${loader}-content`); const arrow = card?.querySelector('.detail-option-arrow'); if (!content) return; // 如果不可用,不允许展开 if (!state.available) return; state.expanded = !state.expanded; if (state.expanded) { if (arrow) arrow.classList.add('expanded'); } else { if (arrow) arrow.classList.remove('expanded'); } // 重新更新整个 UI updateModLoaderUI(); } // 绑定模组加载器事件 function bindModLoaderEvents() { const loaders = ['forge']; loaders.forEach(loader => { const card = document.getElementById(`${loader}-option`); if (!card) return; const header = card.querySelector('.detail-option-header'); if (header) { header.addEventListener('click', () => toggleModLoader(loader)); } }); } // 重置模组加载器状态 function resetModLoaderState() { modLoaderState = { forge: { available: false, versions: [], selected: null, expanded: false } }; // 重置 UI const loaders = ['forge']; loaders.forEach(loader => { const card = document.getElementById(`${loader}-option`); const badge = document.getElementById(`${loader}-badge`); const content = document.getElementById(`${loader}-content`); const loading = document.getElementById(`${loader}-loading`); const versions = document.getElementById(`${loader}-versions`); const error = document.getElementById(`${loader}-error`); const arrow = card?.querySelector('.detail-option-arrow'); if (card) { card.classList.remove('not-available', 'incompatible'); } if (badge) { badge.textContent = '检测中...'; badge.className = 'detail-option-badge'; } if (content) content.classList.add('hidden'); if (loading) loading.classList.remove('hidden'); if (versions) versions.classList.add('hidden'); if (error) error.classList.add('hidden'); if (arrow) arrow.classList.remove('expanded'); }); } function getCategoryLabel(type) { const map = { release: '正式版', snapshot: '快照版', old_beta: '老版本 Beta', old_alpha: '老版本 Alpha' }; return map[type] || type; } function getCategoryIcon(type) { const map = { release: '🟢', snapshot: '🟡', old_beta: '🟠', old_alpha: '🔴' }; return map[type] || '⚪'; } function formatDate(dateStr) { if (!dateStr) return ''; const d = new Date(dateStr); return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`; } function renderVersionList(versionMap) { if (!versionListContainer) return; versionListContainer.innerHTML = ''; const categoryOrder = ['release', 'snapshot', 'old_beta', 'old_alpha']; let totalCount = 0; categoryOrder.forEach(type => { const items = versionMap[type]; if (!items || items.length === 0) return; totalCount += items.length; const categoryEl = document.createElement('div'); categoryEl.className = 'version-category'; categoryEl.dataset.category = type; const isRelease = type === 'release'; const header = document.createElement('div'); header.className = 'version-category-header'; header.innerHTML = ` ${getCategoryIcon(type)} ${getCategoryLabel(type)} ${items.length} 个版本 `; const body = document.createElement('div'); body.className = `version-category-items ${isRelease ? '' : 'collapsed'}`; items.forEach(v => { const item = document.createElement('div'); item.className = 'version-item'; item.dataset.versionId = v.id; const displayName = v.displayName || v.id; item.innerHTML = ` ${displayName} ${formatDate(v.releaseTime)} `; item.addEventListener('click', (e) => { e.stopPropagation(); showVersionDetail(v); }); body.appendChild(item); }); header.addEventListener('click', () => { const arrow = header.querySelector('.version-category-arrow'); const isCollapsed = body.classList.toggle('collapsed'); arrow.classList.toggle('collapsed', isCollapsed); }); categoryEl.appendChild(header); categoryEl.appendChild(body); versionListContainer.appendChild(categoryEl); }); if (versionCountLabel) { versionCountLabel.textContent = `共 ${totalCount} 个版本`; } } function showVersionDetail(version) { selectedVersionId = version.id; if (versionListSection) versionListSection.classList.add('hidden'); if (versionDetailPage) versionDetailPage.classList.remove('hidden'); // 重置下载按钮状态,确保它是可用的 if (detailDownloadBtn) detailDownloadBtn.disabled = false; const idEl = document.getElementById('detail-version-id'); const typeEl = document.getElementById('detail-version-type'); const typeLabelEl = document.getElementById('detail-type-label'); const timeEl = document.getElementById('detail-release-time'); if (idEl) idEl.textContent = `Minecraft ${version.id}`; if (typeEl) typeEl.textContent = getCategoryLabel(version.type); if (typeLabelEl) typeLabelEl.textContent = `${getCategoryIcon(version.type)} ${getCategoryLabel(version.type)}`; if (timeEl) timeEl.textContent = formatDate(version.releaseTime) || '未知'; // 重置并检测模组加载器可用性 resetModLoaderState(); checkAllModLoaders(version.id); } function hideVersionDetail() { selectedVersionId = null; if (versionDetailPage) versionDetailPage.classList.add('hidden'); if (versionListSection) versionListSection.classList.remove('hidden'); } async function loadRemoteVersions() { const refreshBtn = document.getElementById('refresh-btn-page'); try { if (refreshBtn) refreshBtn.classList.add('hidden'); if (versionListContainer) { versionListContainer.innerHTML = '
正在加载版本列表...
'; } let versions = []; // 尝试调用主进程API if (window.gpcl && typeof window.gpcl.getVersionManifest === 'function') { try { versions = await window.gpcl.getVersionManifest(); } catch (e) { console.error('调用getVersionManifest失败:', e); } } // 尝试从文件缓存获取 if (!versions || versions.length === 0) { try { if (window.gpcl && typeof window.gpcl.readJsonFile === 'function') { const cached = await window.gpcl.readJsonFile('cache', 'remote_versions.json'); if (cached) { versions = cached; } } } catch (e) { console.error('从文件缓存获取远程版本列表失败:', e); } } // 如果还是没有数据,显示错误或示例数据 if (!versions || versions.length === 0) { setStatus('无法获取远程版本列表', 'error'); if (versionListContainer) { versionListContainer.innerHTML = '
无法获取版本列表
'; } if (refreshBtn) refreshBtn.classList.remove('hidden'); return; } // Group versions by type const versionMap = {}; versions.forEach(v => { if (!versionMap[v.type]) versionMap[v.type] = []; versionMap[v.type].push(v); }); // 保存到文件缓存 try { if (window.gpcl && typeof window.gpcl.writeJsonFile === 'function') { await window.gpcl.writeJsonFile('cache', 'remote_versions.json', versions); } } catch (e) { console.error('保存远程版本列表到缓存失败:', e); } allVersions = versions; renderVersionList(versionMap); setStatus('远程版本列表已加载'); } catch (err) { setStatus('无法获取远程版本列表,请检查网络', 'error'); showToast('版本列表加载失败', err.message || '请检查网络连接', 'error', 'remote-versions-error'); if (versionListContainer) { versionListContainer.innerHTML = '
加载失败,请检查网络连接
'; } if (refreshBtn) refreshBtn.classList.remove('hidden'); } } async function launchGame() { // 检查按钮模式 const launchBtn = document.getElementById('launch-btn'); const mode = launchBtn?.dataset.mode || 'launch'; if (mode === 'download') { // 下载模式:跳转到下载页面 showPage('download'); setStatus('请选择要下载的游戏版本'); return; } // 启动模式:正常启动游戏 setStatus('[动画] launchGame 函数被调用了'); const username = usernameInput.value.trim() || 'GPCL_Player'; // 使用右侧面板选择的版本 const versionId = selectedVersionId; if (!versionId) { setStatus('请先选择已安装的版本', 'error'); return; } // 记录版本启动时间(不管是否成功都会记录) await recordVersionLaunch(versionId); if (window.gpcl && window.gpcl.savePlayerName) { await window.gpcl.savePlayerName(username); } launchBtn.disabled = true; // 显示启动动画层 showLaunchAnimation(versionId); try { // 尝试启动游戏(主进程会自动处理Java下载) const gameDir = await initGameDir(); setStatus('[动画] 开始启动游戏'); // 获取窗口模式设置 const settings = await loadSettings(); let windowMode = settings.game?.windowMode || 'windowed'; let welcomeAnimPlaying = false; if (settings.advanced?.playStartupAnimation && window.gpcl && window.gpcl.playStartupAnimation) { setStatus('[启动动画] 正在播放欢迎动画...'); welcomeAnimPlaying = true; windowMode = 'fullscreen'; settings.advanced.playStartupAnimation = false; await gpcl.saveSettings(settings); const playStartupEl = document.getElementById('play-startup-animation'); if (playStartupEl) playStartupEl.checked = false; await window.gpcl.playStartupAnimation(); setStatus('[启动动画] 动画播放完毕,正在启动游戏...'); } // 确保游戏窗口创建事件监听器已设置 setupGameWindowListener(); const result = await window.gpcl.launch({ versionId, username, gameDir, windowMode }); if (result.success) { setStatus('[动画] 游戏启动成功,窗口已创建'); if (welcomeAnimPlaying && window.gpcl && window.gpcl.dismissStartupAnimation) { window.gpcl.dismissStartupAnimation(); } // 显示成功动画后隐藏 showLaunchSuccess(); setTimeout(() => { hideLaunchAnimation(); }, 800); setStatus(`游戏已启动!PID: ${result.pid}`, 'success'); showToast('游戏已启动', `Minecraft ${versionId} (${username})`, 'success', 'game-started'); } else if (result.cancelled) { // 用户取消了启动 if (welcomeAnimPlaying && window.gpcl && window.gpcl.dismissStartupAnimation) { window.gpcl.dismissStartupAnimation(); } hideLaunchAnimation(); setStatus('启动已取消', 'info'); showToast('启动已取消', '用户取消了游戏启动', 'info', 'launch-cancelled'); } else { if (welcomeAnimPlaying && window.gpcl && window.gpcl.dismissStartupAnimation) { window.gpcl.dismissStartupAnimation(); } hideLaunchAnimation(); setStatus(result.error, 'error'); showToast('启动失败', result.error, 'error', 'game-launch-failed'); } } catch (err) { if (welcomeAnimPlaying && window.gpcl && window.gpcl.dismissStartupAnimation) { window.gpcl.dismissStartupAnimation(); } hideLaunchAnimation(); setStatus('启动出错', 'error'); showToast('启动出错', err.message, 'error', 'launch-error'); } finally { launchBtn.disabled = false; } } // 显示启动动画 async function showLaunchAnimation(versionId) { try { const overlay = document.getElementById('launch-animation-overlay'); const statusText = document.getElementById('launch-status-text'); const versionInfo = document.getElementById('launch-version-info'); const progressFill = document.getElementById('launch-progress-fill'); if (overlay) { overlay.classList.remove('hidden', 'fade-out', 'launch-success'); overlay.style.display = 'flex'; void overlay.offsetHeight; overlay.classList.add('show'); setStatus('[动画] 启动动画层已显示'); } else { setStatus('[动画] 未找到启动动画层元素', 'error'); return; } if (statusText) { statusText.textContent = '正在启动游戏...'; } // 获取启动显示名称(不包含 OptiFine) let displayName = versionId; if (window.gpcl && typeof window.gpcl.getLaunchDisplayName === 'function') { try { displayName = await window.gpcl.getLaunchDisplayName(versionId); } catch (e) { console.warn(`获取启动显示名称失败: ${versionId}`, e); } } if (versionInfo) { versionInfo.textContent = `将启动: ${displayName}`; } if (progressFill) { progressFill.style.width = '0%'; } simulateLaunchProgress(); showRandomTip(); } catch (err) { setStatus('[动画] 显示启动动画失败: ' + err.message, 'error'); } } // 小知识列表 const launchTips = [ 'Minecraft 最初由 Markus "Notch" Persson 于 2009 年独立开发,最初版本仅用 6 天完成。', 'Minecraft 的苦力怕(Creeper)是 Notch 在尝试制作猪模型时,因搞错长宽比而意外诞生的。', '末影人(Enderman)会随机搬运一些方块,这是参考了现实中的都市传说 Slender Man。', 'Minecraft 中最大的自然结构是下界要塞(Nether Fortress),可绵延数百个区块。', 'Minecraft 中钻石矿石只在 Y 坐标 16 以下生成,最集中的区域是 Y=5 到 Y=12 之间。', 'Minecraft 的附魔台周围的图书馆架数量会影响附魔等级,最高可达到 30 级。', '红石(Redstone)是 Minecraft 中的"电力"系统,可以构建从简单门到计算机的各种电路。', 'Minecraft 中潮涌核心(Conduit)可以为水下玩家提供无限呼吸和夜视效果。', 'Minecraft 的下界合金(Netherite)装备需要先制作品铁装备再锻造升级。', 'Minecraft 中的嗅探兽(Sniffer)是 2022 年 Minecraft Live 由玩家投票选出的生物。', 'Minecraft 中沼泽小屋(Swamp Hut)必定会生成女巫,是获取红石粉的稳定来源。', 'Minecraft 中你可以用线制作羊毛,但不能用羊毛反合成线。', 'Minecraft 的创造模式最初是为开发调试而加入的,后来才成为正式游戏模式。', 'Minecraft 中装备了冰霜行者(Frost Walker)附魔的靴子可以在水上行走。', 'Minecraft 中每个区块(Chunk)是 16×16 方块大小,游戏世界是通过区块流式加载的。', 'Minecraft 中鞘翅(Elytra)可以在末地船中找到,配合烟花火箭可以实现飞行。', 'Minecraft 中下界对应主世界的比例为 1:8,在下界走 1 格相当于主世界 8 格。', 'Minecraft 中掠夺者前哨站(Pillager Outpost)会生成灾厄旗帜,周围的灾厄村民不会攻击。', 'Minecraft 中蜜蜂(Bee)在采蜜后会帮助作物加速生长。', 'Minecraft 中史莱姆(Slime)只在特定区块生成,可以使用种子计算器定位史莱姆区块。', 'Minecraft 的唱片机播放的是 C418 创作的音乐,13 和 cat 两张唱片可在要塞的箱子中找到。', 'Minecraft 中深海监守者(Warden)是盲人,依靠声音和振动感知玩家。', 'Minecraft 的紫颂果(Chorus Fruit)食用后会随机传送,但会失去一些饥饿值。', 'Minecraft 中袭击(Raid)是不祥之兆效果进入村庄时触发的多波战斗事件。', 'Minecraft 中下界传送门(Nether Portal)的大小最小为 4×5,最大为 23×23。', 'Minecraft 中海龟壳帽(Turtle Shell)可以让玩家在水下呼吸更长时间。', 'Minecraft 中甜浆果(Sweet Berries)可以在针叶林生物群系中找到,也可种植。', 'Minecraft 中你已经可以用铜(Copper)制作避雷针、望远镜和装饰方块。', 'Minecraft 中营火(Campfire)可以烹饪食物,而且不会像熔炉那样消耗燃料。', 'Minecraft 中幽匿块(Sculk)会在深暗之域生成,挖掉之前最好先准备好。', 'GPCL 是云云一个人开发的启动器,并没有什么所谓的开发团队。', ]; // 显示随机小知识 function showRandomTip() { const tipText = document.getElementById('launch-tip-text'); if (!tipText) return; const randomIndex = Math.floor(Math.random() * launchTips.length); tipText.textContent = launchTips[randomIndex]; } // 模拟启动进度 function simulateLaunchProgress() { const progressFill = document.getElementById('launch-progress-fill'); const statusText = document.getElementById('launch-status-text'); let progress = 0; const statusMessages = [ '正在检查游戏文件...', '正在准备Java运行时...', '正在初始化游戏环境...', '正在启动游戏窗口...' ]; const interval = setInterval(() => { // 随机增加进度 progress += Math.random() * 15 + 5; if (progress >= 90) { progress = 90; } if (progressFill) { progressFill.style.width = `${progress}%`; } // 更新状态文字 const messageIndex = Math.floor(progress / 25); if (statusText && messageIndex < statusMessages.length) { statusText.textContent = statusMessages[messageIndex]; } }, 200); // 保存interval ID以便清除 window.launchProgressInterval = interval; } // 游戏窗口创建监听器设置 let gameWindowResolve = null; let gameWindowTimeout = null; function setupGameWindowListener() { // 移除旧的监听器避免重复 if (window.gpcl && window.gpcl.removeAllListeners) { window.gpcl.removeAllListeners(); } // 重新注册关闭确认监听器 if (window.gpcl && window.gpcl.onConfirmCloseWhileDownloading) { window.gpcl.onConfirmCloseWhileDownloading(async () => { const result = await showDialog({ type: 'confirm', title: '确认关闭', message: '当前正在下载中,关闭会停止下载并清理已下载的文件。确定要关闭吗?如有不完整的"libraries"需自行清理。' }); if (result) { if (window.gpcl && window.gpcl.confirmCloseDownload) { await window.gpcl.confirmCloseDownload(); } } else { if (window.gpcl && window.gpcl.cancelClose) { await window.gpcl.cancelClose(); } } }); } // 注册游戏窗口创建监听器 if (window.gpcl && window.gpcl.onGameWindowCreated) { window.gpcl.onGameWindowCreated((data) => { setStatus('[动画] 收到游戏窗口创建事件'); if (gameWindowResolve) { clearTimeout(gameWindowTimeout); gameWindowResolve(); gameWindowResolve = null; gameWindowTimeout = null; } }); setStatus('[动画] 游戏窗口监听器已设置'); } } // 等待游戏窗口创建成功 function waitForGameWindow() { return new Promise((resolve) => { gameWindowResolve = resolve; gameWindowTimeout = setTimeout(() => { setStatus('[动画] 窗口检测超时'); gameWindowResolve = null; gameWindowTimeout = null; resolve(); }, 10000); // 10秒超时 }); } // 显示成功动画 function showLaunchSuccess() { try { const overlay = document.getElementById('launch-animation-overlay'); const pickaxe = document.getElementById('pickaxe'); const block = document.getElementById('block'); if (overlay) { overlay.classList.add('launch-success'); setStatus('[动画] 显示成功动画'); } if (block) { block.classList.add('breaking'); } // 创建粒子效果 createParticles(); } catch (err) { setStatus('[动画] 显示成功动画失败: ' + err.message, 'error'); } } // 创建粒子效果 function createParticles() { const particlesContainer = document.getElementById('particles'); if (!particlesContainer) return; // 清除现有粒子 particlesContainer.innerHTML = ''; // 创建多个粒子 for (let i = 0; i < 12; i++) { const particle = document.createElement('div'); particle.className = 'particle'; // 随机方向 const angle = (Math.PI * 2 * i) / 12; const distance = 50 + Math.random() * 50; particle.style.setProperty('--dx', `${Math.cos(angle) * distance}px`); particle.style.setProperty('--dy', `${Math.sin(angle) * distance}px`); // 随机大小和延迟 particle.style.width = `${4 + Math.random() * 8}px`; particle.style.height = particle.style.width; particle.style.animationDelay = `${Math.random() * 0.2}s`; particlesContainer.appendChild(particle); } } // 隐藏启动动画 function hideLaunchAnimation() { try { const overlay = document.getElementById('launch-animation-overlay'); if (overlay) { overlay.classList.remove('show'); overlay.classList.add('fade-out'); setTimeout(() => { overlay.style.display = 'none'; overlay.classList.remove('fade-out', 'launch-success'); overlay.classList.add('hidden'); setStatus('[动画] 启动动画层已隐藏'); }, 400); } // 清除进度interval if (window.launchProgressInterval) { clearInterval(window.launchProgressInterval); window.launchProgressInterval = null; } } catch (err) { setStatus('[动画] 隐藏启动动画失败: ' + err.message, 'error'); } } async function startDownload(versionId) { if (!versionId) { setStatus('请先选择要下载的版本', 'error'); return; } // 检查版本是否已安装 const installed = await isVersionInstalled(versionId); // 检查是否选择了模组加载器 let selectedModLoader = null; let selectedModLoaderVersion = null; let modLoaderName = ''; for (const [loader, state] of Object.entries(modLoaderState)) { if (state.selected) { selectedModLoader = loader; selectedModLoaderVersion = state.selected; if (loader === 'forge') modLoaderName = 'Forge'; break; } } if (detailDownloadBtn) detailDownloadBtn.disabled = true; await loadChartJs(); initChart(); resetChart(); if (downloadPanel) downloadPanel.classList.remove('hidden'); // 从详情页回到列表页,显示下载监控 hideVersionDetail(); showPage('download'); // 滚动到下载页面顶部 const scrollContainer = document.querySelector('.main-content'); if (scrollContainer) { scrollContainer.scrollTop = 0; } const downloadStatusEl = document.getElementById('download-status'); const filenameEl = document.getElementById('download-filename'); const cancelBtn = document.getElementById('cancel-download-btn'); // 重置按钮状态 if (cancelBtn) { cancelBtn.textContent = '取消下载'; cancelBtn.classList.remove('complete'); cancelBtn.disabled = false; } let displayName = `Minecraft ${versionId}`; if (selectedModLoader) { displayName = `${modLoaderName} ${versionId}`; } if (downloadStatusEl) downloadStatusEl.textContent = `开始下载 ${displayName} ...`; if (filenameEl) filenameEl.textContent = `正在下载: ${displayName}`; window.gpcl.removeAllListeners(); // 重新注册关闭确认监听器(不会被removeAllListeners移除) window.gpcl.onConfirmCloseWhileDownloading(async () => { const result = await showDialog({ type: 'confirm', title: '确认关闭', message: '当前正在下载中,关闭会停止下载并清理已下载的文件。确定要关闭吗?如有不完整的"libraries"需自行清理。' }); if (result) { if (window.gpcl && window.gpcl.confirmCloseDownload) { await window.gpcl.confirmCloseDownload(); } } else { if (window.gpcl && window.gpcl.cancelClose) { await window.gpcl.cancelClose(); } } }); let lastTime = Date.now(); let lastBytes = 0; let lastLabel = ''; window.gpcl.onDownloadProgress((data) => { const currentTime = Date.now(); const timeDiff = (currentTime - lastTime) / 1000; const currentSpeedEl = document.getElementById('current-speed'); const peakSpeedEl = document.getElementById('peak-speed'); const progressFillEl = document.getElementById('progress-fill'); const progressTextEl = document.getElementById('progress-text'); if (data.label && data.label !== lastLabel) { lastLabel = data.label; lastBytes = 0; lastTime = currentTime; } if (timeDiff >= 0.5 && data.bytesDownloaded !== undefined) { const bytesDiff = data.bytesDownloaded - lastBytes; if (bytesDiff < 0) { lastBytes = data.bytesDownloaded; lastTime = currentTime; return; } const speedBps = Math.max(0, (bytesDiff * 8) / timeDiff / 1000000); if (currentSpeedEl) currentSpeedEl.textContent = speedBps.toFixed(2) + ' Mbps'; if (speedBps > peakSpeed) { peakSpeed = speedBps; if (peakSpeedEl) peakSpeedEl.textContent = peakSpeed.toFixed(2) + ' Mbps'; } updateChart(speedBps); lastTime = currentTime; lastBytes = data.bytesDownloaded; } const percent = data.percent || 0; if (progressFillEl) progressFillEl.style.width = percent + '%'; if (progressTextEl) progressTextEl.textContent = percent.toFixed(1) + '%'; if (data.label && filenameEl) { filenameEl.textContent = data.label; } if (downloadStatusEl) downloadStatusEl.textContent = `正在下载 ${versionId}: ${percent.toFixed(1)}%`; if (menuDownload) menuDownload.classList.add('active'); if (data.percent >= 100 && menuDownload) { menuDownload.classList.remove('active'); } }); try { const maxConcurrent = getMaxConcurrentFromSettings(); let result; if (selectedModLoader) { // 下载带模组加载器的版本 result = await window.gpcl.downloadWithModLoader( versionId, selectedModLoader, selectedModLoaderVersion, maxConcurrent ); } else { // 下载原版 result = await window.gpcl.downloadVersion(versionId, maxConcurrent); } if (result.success) { // 记录版本下载时间 await recordVersionDownload(result.finalVersionId || versionId); const finalDisplayName = selectedModLoader ? `${modLoaderName} ${versionId}` : `Minecraft ${versionId}`; setStatus('下载完成', 'success'); if (downloadStatusEl) downloadStatusEl.textContent = `下载完成: ${finalDisplayName}`; const progressFillEl = document.getElementById('progress-fill'); const progressTextEl = document.getElementById('progress-text'); if (progressFillEl) progressFillEl.style.width = '100%'; if (progressTextEl) progressTextEl.textContent = '100%'; showToast('下载完成', `${finalDisplayName} 已准备就绪`, 'success', 'download-complete'); await loadVersions(); // 刷新版本选择列表 renderVersionSelectList(); // 修改按钮状态为下载完成(绿色) if (cancelBtn) { cancelBtn.textContent = '下载完成'; cancelBtn.classList.add('complete'); cancelBtn.disabled = true; } } else { setStatus(`下载失败: ${result.error}`, 'error'); if (downloadStatusEl) downloadStatusEl.textContent = `下载失败: ${result.error}`; } } catch (err) { setStatus('下载出错', 'error'); if (downloadStatusEl) downloadStatusEl.textContent = `下载出错: ${err.message || err}`; showToast('下载出错', err.message || String(err), 'error', 'download-error'); } if (detailDownloadBtn) detailDownloadBtn.disabled = false; } // 详情页面事件 if (detailBackBtn) { detailBackBtn.addEventListener('click', hideVersionDetail); } if (detailDownloadBtn) { detailDownloadBtn.addEventListener('click', () => { if (selectedVersionId) { startDownload(selectedVersionId); } }); } window.gpcl.onGameClosed((code) => { setStatus('游戏已退出'); showToast('游戏已退出', `退出码: ${code}`, 'info', 'game-closed'); launchBtn.disabled = false; }); window.gpcl.onGameError((message) => { setStatus(`启动出错: ${message}`, 'error'); showToast('游戏错误', message, 'error', 'game-error'); launchBtn.disabled = false; }); launchBtn.addEventListener('click', launchGame); (async function init() { setStatus('正在加载...'); initTheme(); initScale(); if (menuLaunch) menuLaunch.classList.add('active'); if (usernameInput && window.gpcl && window.gpcl.getPlayerName) { const savedName = await window.gpcl.getPlayerName(); if (savedName) usernameInput.value = savedName; } await loadVersions(); await loadRemoteVersions(); // 初始化版本选择列表并自动选中最近启动的版本 await renderVersionSelectList(); // 初始化Java版本状态 await initJavaVersionStatus(); bindJavaInstallButtons(); // 绑定模组加载器事件 bindModLoaderEvents(); setInterval(async () => { // 刷新游戏版本(使用静默模式,不写日志) if (currentPage === 'launch') { await loadVersions(true); await renderVersionSelectList(); } // 刷新Java版本状态(在所有页面都刷新) if (currentPage === 'download') { await refreshJavaStatus(); } }, 2000); // 刷新Java版本状态的函数 async function refreshJavaStatus() { const javaVersions = ["8", "17", "21", "25"]; for (const ver of javaVersions) { try { const result = await window.gpcl.checkJava(ver); const card = document.querySelector(`.java-version-card[data-version="${ver}"]`); const badge = document.getElementById(`java-${ver}-status`); const btn = card?.querySelector('.java-install-btn'); if (result.installed) { if (!card?.classList.contains('installed')) { if (card) card.classList.add('installed'); if (badge) { badge.textContent = '已安装'; badge.className = 'java-version-badge installed'; } if (btn) { btn.textContent = '卸载'; btn.classList.add('installed'); btn.disabled = false; } } } else { if (card?.classList.contains('installed')) { card.classList.remove('installed'); if (badge) { badge.textContent = ''; badge.className = 'java-version-badge'; } if (btn) { btn.textContent = '安装'; btn.classList.remove('installed'); btn.disabled = false; } } } } catch (e) { console.error(`检查Java ${ver} 状态失败`, e); } } updateJavaInstallStatus(); } // bind refresh button const refreshBtn = document.getElementById('refresh-btn-page'); if (refreshBtn) refreshBtn.addEventListener('click', async () => { setStatus('重试加载远程版本...'); await loadRemoteVersions(); // 同时刷新Java状态 await refreshJavaStatus(); setStatus('准备就绪'); }); // bind cancel download button if (cancelDownloadBtn) cancelDownloadBtn.addEventListener('click', async () => { const result = await showDialog({ type: 'confirm', title: '确认取消', message: '确定要取消当前下载吗?已下载的文件将被清理。' }); if (result) { if (window.gpcl && window.gpcl.cancelDownloadOnly) { await window.gpcl.cancelDownloadOnly(); if (downloadPanel) downloadPanel.classList.add('hidden'); if (detailDownloadBtn) detailDownloadBtn.disabled = false; showToast('已取消', '下载已取消,文件已清理', 'info'); } } }); setStatus('准备就绪'); // 启动器加载完成后自动前置窗口 if (window.gpcl && window.gpcl.focusWindow) { setTimeout(() => { window.gpcl.focusWindow(); }, 100); } // 绑定启动取消按钮 const launchCancelBtn = document.getElementById('launch-cancel-btn'); if (launchCancelBtn) { launchCancelBtn.addEventListener('click', () => { setStatus('[动画] 用户取消启动'); if (window.gpcl && window.gpcl.cancelLaunch) { window.gpcl.cancelLaunch(); } hideLaunchAnimation(); }); } // 静默检查更新 checkForUpdates(true); // 绑定检查更新按钮 const checkUpdateBtn = document.getElementById('check-update-btn'); if (checkUpdateBtn) { checkUpdateBtn.addEventListener('click', async () => { checkUpdateBtn.disabled = true; checkUpdateBtn.textContent = '检查中...'; await checkForUpdates(false); const updateStatus = document.getElementById('update-status'); const goDownloadBtn = document.getElementById('go-download-btn'); if (updateAvailable) { const newest = allNewerVersions[allNewerVersions.length - 1]; checkUpdateBtn.textContent = `发现 ${allNewerVersions.length} 个新版本`; checkUpdateBtn.classList.add('new-version'); if (updateStatus) { updateStatus.innerHTML = `
发现 ${allNewerVersions.length} 个新版本!
${renderUpdateVersionCards(allNewerVersions)} `; updateStatus.classList.add('show', 'has-update'); } if (goDownloadBtn) { goDownloadBtn.classList.remove('hidden'); } } else { checkUpdateBtn.textContent = '已是最新版本'; checkUpdateBtn.classList.remove('new-version'); if (updateStatus) { updateStatus.innerHTML = '当前已是最新版本'; updateStatus.classList.add('show'); updateStatus.classList.remove('has-update'); } if (goDownloadBtn) { goDownloadBtn.classList.add('hidden'); } } checkUpdateBtn.disabled = false; }); } // 绑定立即前往下载按钮 const goDownloadBtn = document.getElementById('go-download-btn'); // 绑定自动检查更新开关 const autoCheckUpdate = document.getElementById('auto-check-update'); // 绑定防止多次启动开关 const preventMultipleLaunch = document.getElementById('prevent-multiple-launch'); // 绑定自动清除日志开关 const autoClearLogs = document.getElementById('auto-clear-logs'); const logRetentionContainer = document.getElementById('log-retention-container'); const logRetentionValue = document.getElementById('log-retention-value'); const logRetentionUnit = document.getElementById('log-retention-unit'); // 初始化版本显示 const aboutVersion = document.getElementById('about-version'); const customMirrorContainer = document.getElementById('custom-java-mirror-container'); const customMirrorUrl = document.getElementById('custom-java-mirror-url'); // 游戏内存 initCustomSelect('settings-memory', async (value) => { const settings = await loadSettings(); settings.game.memory = value; await gpcl.saveSettings(settings); }); // 窗口模式 initCustomSelect('settings-window-mode', async (value) => { const settings = await loadSettings(); if (!settings.game) settings.game = {}; settings.game.windowMode = value; await gpcl.saveSettings(settings); }); // 主题模式 initCustomSelect('settings-theme', async (value) => { const settings = await loadSettings(); if (!settings.appearance) settings.appearance = {}; settings.appearance.theme = value; await gpcl.saveSettings(settings); applyTheme(value); }); // 界面缩放 initCustomSelect('settings-scale', async (value) => { const settings = await loadSettings(); if (!settings.appearance) settings.appearance = {}; settings.appearance.scale = value; await gpcl.saveSettings(settings); applyScale(value); }); // Java镜像源 initCustomSelect('java-mirror-select', async (value) => { if (customMirrorContainer) { customMirrorContainer.classList.toggle('hidden', value !== 'custom'); } await saveJavaMirrorSettings(value, customMirrorUrl?.value || ''); }); // 日志保留时间单位 initCustomSelect('log-retention-unit', async (unit) => { // 根据单位限制当前值 const maxValues = { hour: 23, day: 30, month: 11, year: 10 }; if (logRetentionValue) { let value = parseInt(logRetentionValue.value, 10); if (isNaN(value) || value < 1) value = 1; if (value > maxValues[unit]) { value = maxValues[unit]; logRetentionValue.value = value; } } const settings = await loadSettings(); settings.advanced.logRetentionUnit = unit; if (logRetentionValue) { settings.advanced.logRetentionValue = parseInt(logRetentionValue.value, 10); } await gpcl.saveSettings(settings); }); // 绑定立即前往下载按钮 if (goDownloadBtn) { goDownloadBtn.addEventListener('click', () => { if (window.gpcl && window.gpcl.openExternal) { window.gpcl.openExternal('https://gamets.caellab.com/gpcl/#download'); } }); } // 绑定自动检查更新开关 if (autoCheckUpdate) { // 从设置中加载状态 (async () => { const settings = await loadSettings(); autoCheckUpdate.checked = settings.advanced?.autoCheckUpdate !== false; })(); autoCheckUpdate.addEventListener('change', async () => { const settings = await loadSettings(); settings.advanced.autoCheckUpdate = autoCheckUpdate.checked; await gpcl.saveSettings(settings); updateSettingsBadge(); }); } // 绑定防止多次启动开关 if (preventMultipleLaunch) { (async () => { const settings = await loadSettings(); preventMultipleLaunch.checked = settings.advanced?.preventMultipleLaunch !== false; })(); preventMultipleLaunch.addEventListener('change', async () => { const settings = await loadSettings(); settings.advanced.preventMultipleLaunch = preventMultipleLaunch.checked; await gpcl.saveSettings(settings); }); } const playStartupAnimation = document.getElementById('play-startup-animation'); if (playStartupAnimation) { (async () => { const settings = await loadSettings(); playStartupAnimation.checked = settings.advanced?.playStartupAnimation === true; })(); playStartupAnimation.addEventListener('change', async () => { const settings = await loadSettings(); settings.advanced.playStartupAnimation = playStartupAnimation.checked; await gpcl.saveSettings(settings); }); } const developerMode = document.getElementById('developer-mode'); if (developerMode) { (async () => { const settings = await loadSettings(); developerMode.checked = settings.advanced?.developerMode === true; })(); developerMode.addEventListener('change', async () => { const settings = await loadSettings(); settings.advanced.developerMode = developerMode.checked; await gpcl.saveSettings(settings); if (window.gpcl && window.gpcl.setDeveloperMode) { await window.gpcl.setDeveloperMode(developerMode.checked); } }); } // 绑定自动清除日志开关 if (autoClearLogs) { (async () => { const settings = await loadSettings(); autoClearLogs.checked = settings.advanced?.autoClearLogs !== false; })(); autoClearLogs.addEventListener('change', async () => { const settings = await loadSettings(); settings.advanced.autoClearLogs = autoClearLogs.checked; await gpcl.saveSettings(settings); if (logRetentionContainer) { logRetentionContainer.classList.toggle('hidden', !autoClearLogs.checked); } }); } // 绑定保留日志时间输入 if (logRetentionValue) { (async () => { const settings = await loadSettings(); logRetentionValue.value = settings.advanced?.logRetentionValue || 7; })(); logRetentionValue.addEventListener('change', async () => { let value = parseInt(logRetentionValue.value, 10); const unit = getCustomSelectValue('log-retention-unit') || 'day'; // 根据单位限制最大值 const maxValues = { hour: 23, day: 30, month: 11, year: 10 }; if (isNaN(value) || value < 1) { value = 1; } else if (value > maxValues[unit]) { value = maxValues[unit]; } logRetentionValue.value = value; const settings = await loadSettings(); settings.advanced.logRetentionValue = value; await gpcl.saveSettings(settings); }); } // 初始化版本显示 if (aboutVersion) { const version = await getCurrentVersion(); aboutVersion.textContent = `版本: ${version}`; } // 绑定Java镜像URL输入 if (customMirrorUrl) { customMirrorUrl.addEventListener('input', async () => { await saveJavaMirrorSettings(getCustomSelectValue('java-mirror-select') || 'tsinghua', customMirrorUrl.value); }); } // 加载所有设置到界面 (async () => { const settings = await loadSettings(); // 游戏内存 let memoryValue = '2'; // 默认2GB if (settings.game?.memory) { const storedMemory = String(settings.game.memory); if (storedMemory.includes('096')) { // 旧格式,转换为GB const mb = parseInt(storedMemory); if (!isNaN(mb)) { memoryValue = String(mb / 1024); } } else { memoryValue = storedMemory; } } setCustomSelectValue('settings-memory', memoryValue); // 窗口模式 setCustomSelectValue('settings-window-mode', settings.game?.windowMode || 'windowed'); // 主题 const theme = settings.appearance?.theme || 'dark'; setCustomSelectValue('settings-theme', theme); applyTheme(theme); // 缩放 const scale = settings.appearance?.scale || '100'; setCustomSelectValue('settings-scale', scale); applyScale(scale); // Java镜像 const javaMirror = settings.download?.javaMirror || 'tsinghua'; setCustomSelectValue('java-mirror-select', javaMirror); if (customMirrorContainer) { customMirrorContainer.classList.toggle('hidden', javaMirror !== 'custom'); } if (customMirrorUrl && settings.download?.customJavaMirror) { customMirrorUrl.value = settings.download.customJavaMirror; } // 日志保留时间单位 setCustomSelectValue('log-retention-unit', settings.advanced?.logRetentionUnit || 'day'); })(); // 绑定兼容性设置 (async () => { const resetDefaultToggle = document.getElementById('settings-reset-default'); if (resetDefaultToggle) { resetDefaultToggle.checked = false; // 默认关闭 resetDefaultToggle.addEventListener('change', async function() { if (this.checked) { // 显示确认对话框 const confirmed = await showDialog({ type: 'confirm', title: '恢复默认设置', message: '确定要将所有设置恢复为默认值吗?\n\n此操作会重启启动器。' }); if (confirmed) { // 恢复默认设置 if (window.gpcl && window.gpcl.resetSettings) { const result = await window.gpcl.resetSettings(); if (result.success) { // 重启应用 if (window.gpcl && window.gpcl.restartApp) { await window.gpcl.restartApp(); } } } } else { // 用户取消,重置开关状态 this.checked = false; } } }); } })(); // 绑定设置侧边栏点击事件 const settingsSidebarItems = document.querySelectorAll('.settings-sidebar-item'); settingsSidebarItems.forEach(item => { item.addEventListener('click', () => { const tabName = item.dataset.tab; if (tabName) { switchSettingsTab(tabName); } }); }); // 绑定版本设置按钮 - 先声明所有需要的变量 let currentVersionForSettings = null; const versionSettingsBtn = document.getElementById('version-settings'); const versionSettingsPanel = document.getElementById('version-settings-panel'); const versionSettingsBackBtn = document.getElementById('version-settings-back-btn'); const versionSettingsTitle = document.getElementById('version-settings-title'); const versionDeleteEnable = document.getElementById('version-delete-enable'); const versionServerIpContainer = document.getElementById('version-server-ip-container'); const versionServerIpInput = document.getElementById('version-server-ip'); const versionSelectPanel = document.getElementById('version-select-panel'); const statusElement = document.getElementById('status'); async function getVersionSettings(versionId) { try { if (window.gpcl && window.gpcl.getVersionSettings) { const result = await window.gpcl.getVersionSettings(versionId); if (result.success && result.settings) { return result.settings; } } } catch (e) { console.error('获取版本设置失败:', e); } return {}; } async function saveVersionSettings(versionId, settings) { try { if (window.gpcl && window.gpcl.saveVersionSettings) { const result = await window.gpcl.saveVersionSettings(versionId, settings); return result.success; } } catch (e) { console.error('保存版本设置失败:', e); } return false; } // 更新服务器IP输入框的显示状态 function updateServerIpVisibility() { const startupMode = getCustomSelectValue('version-startup-mode'); if (startupMode === 'join') { versionServerIpContainer.classList.add('visible'); } else { versionServerIpContainer.classList.remove('visible'); } } async function loadVersionSettingsToUI(versionId) { const settings = await getVersionSettings(versionId); versionDeleteEnable.checked = false; setCustomSelectValue('version-memory', settings.memory || 'global'); setCustomSelectValue('version-window-mode', settings.windowMode || 'global'); setCustomSelectValue('version-startup-mode', settings.startupMode || 'default'); versionServerIpInput.value = settings.serverIp || ''; updateServerIpVisibility(); } let saveTimeout = null; function saveCurrentVersionSettings() { if (!currentVersionForSettings) return; const settings = { memory: getCustomSelectValue('version-memory') || 'global', windowMode: getCustomSelectValue('version-window-mode') || 'global', startupMode: getCustomSelectValue('version-startup-mode') || 'default', serverIp: versionServerIpInput.value || '' }; if (saveTimeout) clearTimeout(saveTimeout); saveTimeout = setTimeout(async () => { const success = await saveVersionSettings(currentVersionForSettings, settings); if (success) { showToast('保存成功', `版本 ${currentVersionForSettings} 的设置已保存`, 'success'); } else { showToast('保存失败', '无法保存版本设置', 'error'); } }, 300); } async function showVersionSettingsPanel(versionId) { if (!versionSettingsPanel) return; currentVersionForSettings = versionId; versionSettingsTitle.textContent = `版本设置 - ${versionId}`; versionSelectPanel.classList.add('hidden'); if (statusElement) statusElement.style.display = 'none'; versionSettingsPanel.classList.remove('hidden'); versionSettingsPanel.classList.remove('slide-out'); await loadVersionSettingsToUI(versionId); } async function hideVersionSettingsPanel() { if (!versionSettingsPanel) return; // 立即保存当前设置(取消延迟保存) if (saveTimeout) clearTimeout(saveTimeout); if (currentVersionForSettings) { const settings = { memory: getCustomSelectValue('version-memory') || 'global', windowMode: getCustomSelectValue('version-window-mode') || 'global', startupMode: getCustomSelectValue('version-startup-mode') || 'default', serverIp: versionServerIpInput.value || '' }; await saveVersionSettings(currentVersionForSettings, settings); } versionSettingsPanel.classList.add('slide-out'); setTimeout(() => { versionSettingsPanel.classList.add('hidden'); versionSettingsPanel.classList.remove('slide-out'); if (statusElement) statusElement.style.display = ''; currentVersionForSettings = null; }, 300); } if (versionSettingsBtn) { versionSettingsBtn.addEventListener('click', async () => { if (selectedVersionId) { // 如果版本选择面板是打开的,先关闭它 if (!versionSelectPanel.classList.contains('hidden')) { versionSelectPanel.classList.add('hidden'); } await showVersionSettingsPanel(selectedVersionId); } else { showToast('未选择版本', '请先选择一个版本', 'warning'); } }); } if (versionSettingsBackBtn) { versionSettingsBackBtn.addEventListener('click', async () => { await hideVersionSettingsPanel(); }); } if (versionDeleteEnable) { versionDeleteEnable.addEventListener('change', async function() { if (this.checked) { const confirmed = await showDialog({ type: 'confirm', title: '确认删除', message: `确定要删除版本 "${currentVersionForSettings}" 吗?此操作不可恢复!` }); if (confirmed) { if (window.gpcl && window.gpcl.deleteVersion) { try { const result = await window.gpcl.deleteVersion(currentVersionForSettings); if (result.success) { showToast('删除成功', `版本 ${currentVersionForSettings} 已删除`, 'success'); await hideVersionSettingsPanel(); loadVersions(true); renderVersionSelectList(); } else { showToast('删除失败', result.error || '无法删除版本', 'error'); this.checked = false; } } catch (e) { showToast('删除失败', e.message, 'error'); this.checked = false; } } } else { this.checked = false; } } }); } // 初始化版本设置下拉框 initCustomSelect('version-memory', () => { saveCurrentVersionSettings(); }); initCustomSelect('version-window-mode', () => { saveCurrentVersionSettings(); }); initCustomSelect('version-startup-mode', () => { updateServerIpVisibility(); saveCurrentVersionSettings(); }); // 版本服务器IP输入框 if (versionServerIpInput) { versionServerIpInput.addEventListener('input', () => { saveCurrentVersionSettings(); }); } // 绑定版本选择按钮 const versionSelectBtn = document.getElementById('version-select'); const versionPanel = document.getElementById('version-select-panel'); if (versionSelectBtn && versionPanel) { versionSelectBtn.addEventListener('click', () => { // 如果版本设置面板是打开的,就不动 if (!versionSettingsPanel.classList.contains('hidden')) { return; } // 切换版本面板显示状态 if (versionPanel.classList.contains('hidden')) { versionPanel.classList.remove('hidden'); renderVersionSelectList(); } else { versionPanel.classList.add('hidden'); } }); } // 绑定正版登录按钮(显示提示) const authMicrosoftBtn = document.getElementById('auth-microsoft'); if (authMicrosoftBtn) { authMicrosoftBtn.addEventListener('click', () => { showToast('正版登录', '正版登录功能尚未对接微软账号系统', 'info'); }); } })(); // 版本更新检查 let latestVersion = null; let latestVersionLog = null; let updateAvailable = false; let allNewerVersions = []; async function getCurrentVersion() { try { if (window.gpcl && window.gpcl.getAppVersion) { return await window.gpcl.getAppVersion(); } } catch (e) {} return '1.0.0'; } function compareVersions(current, latest) { const currentParts = current.split('.').map(Number); const latestParts = latest.split('.').map(Number); for (let i = 0; i < Math.max(currentParts.length, latestParts.length); i++) { const c = currentParts[i] || 0; const l = latestParts[i] || 0; if (l > c) return 1; if (c > l) return -1; } return 0; } function parseLauncherVersions(launcherData) { const versions = []; let current = null; for (const item of launcherData) { if (item.name === 'version' && item.key) { if (current) versions.push(current); current = { version: item.key, title: '', log: '' }; } else if (current) { if (item.name === 'versionlog' && item.key) { current.log = item.key; } else if (item.name === 'versiontitle' && item.key) { current.title = item.key; } else if (!item.key && item.name && item.name !== 'versionlog' && item.name !== 'versiontitle') { current.title = item.name; } } } if (current) versions.push(current); return versions; } function renderUpdateVersionCards(newerVersions) { if (!newerVersions || newerVersions.length === 0) return ''; const cards = newerVersions.map((v, index) => { const isNewest = index === newerVersions.length - 1; const titleHtml = v.title ? `
${v.title}
` : ''; const logHtml = v.log ? `
${v.log}
` : ''; return `
${v.version} ${isNewest ? '最新' : ''}
${titleHtml} ${logHtml}
`; }).reverse().join(''); return `
${cards}
`; } async function checkForUpdates(silent = false) { setStatus('[版本检查] 开始检查更新...'); try { const result = await window.gpcl.checkForUpdates(); if (!result.success) { throw new Error(result.error || '检查更新失败'); } const data = result.data; const launcherData = data.Launcher || []; const parsedVersions = parseLauncherVersions(launcherData); if (parsedVersions.length === 0) throw new Error('无法获取版本信息'); latestVersion = parsedVersions[0].version; latestVersionLog = parsedVersions[0].log; const currentVersion = await getCurrentVersion(); setStatus(`[版本检查] 当前版本: ${currentVersion}, 最新版本: ${latestVersion}`); allNewerVersions = parsedVersions .filter(v => compareVersions(currentVersion, v.version) > 0) .sort((a, b) => compareVersions(a.version, b.version)); updateAvailable = allNewerVersions.length > 0; setStatus(`[版本检查] 有更新: ${updateAvailable}, 新版本数: ${allNewerVersions.length}`); if (!silent) { if (updateAvailable) { setStatus(`发现 ${allNewerVersions.length} 个新版本`, 'warning'); } else { setStatus('当前已是最新版本', 'success'); } } if (updateAvailable && silent) { const newest = allNewerVersions[allNewerVersions.length - 1]; const titlePart = newest.title ? ` "${newest.title}"` : ''; showToast('发现新版本', `GPCL ${newest.version}${titlePart} 已发布`, 'warning', 'update-available'); } await updateSettingsBadge(); } catch (e) { setStatus(`[版本检查] 错误: ${e.message}`, 'error'); console.error('[版本检查] 详细错误:', e); if (!silent) { setStatus('检查更新失败: ' + e.message, 'error'); } updateAvailable = false; allNewerVersions = []; await updateSettingsBadge(); } } async function updateSettingsBadge() { const settingsMenu = document.getElementById('menu-settings'); const checkUpdateBtn = document.getElementById('check-update-btn'); if (!settingsMenu) return; // 移除旧的红点 const oldBadge = settingsMenu.querySelector('.update-badge'); if (oldBadge) oldBadge.remove(); // 获取设置 const settings = await loadSettings(); // 只有在有新版本且自动检查更新开启时才显示红点 if (updateAvailable && settings.advanced?.autoCheckUpdate !== false) { const badge = document.createElement('span'); badge.className = 'update-badge'; badge.textContent = ''; badge.style.cssText = 'position:absolute;top:2px;right:2px;width:8px;height:8px;background:#f44336;border-radius:50%;'; settingsMenu.style.position = 'relative'; settingsMenu.appendChild(badge); // 检查更新按钮也标红 if (checkUpdateBtn) { checkUpdateBtn.classList.add('new-version'); checkUpdateBtn.textContent = `发现 ${allNewerVersions.length} 个新版本`; } } else { // 移除检查更新按钮的红标(如果当前没有更新) if (checkUpdateBtn && !updateAvailable) { checkUpdateBtn.classList.remove('new-version'); checkUpdateBtn.textContent = '检查更新'; } } } async function loadSettings() { try { return await gpcl.getSettings(); } catch (e) { console.error('加载设置失败:', e); return { game: { memory: '2', playerName: 'GPCL_Player', javaPath: '', jvmArgs: '', windowMode: 'windowed' }, appearance: { theme: 'light', scale: '100' }, download: { maxConcurrent: 64, javaMirror: 'tsinghua', customJavaMirror: '' }, advanced: { autoCheckUpdate: true, preventMultipleLaunch: true, autoClearLogs: true, logRetentionValue: 7, logRetentionUnit: 'day', playStartupAnimation: true, developerMode: false } }; } } function applyTheme(theme) { if (theme === 'light') { document.body.classList.add('light-mode'); } else { document.body.classList.remove('light-mode'); } } async function saveThemeSetting(theme) { const settings = await loadSettings(); settings.appearance.theme = theme; await gpcl.saveSettings(settings); } async function initTheme() { const settings = await loadSettings(); const theme = settings.appearance?.theme || 'dark'; applyTheme(theme); const themeSelect = document.getElementById('settings-theme'); if (themeSelect) { themeSelect.value = theme; themeSelect.addEventListener('change', async () => { const newTheme = themeSelect.value; applyTheme(newTheme); await saveThemeSetting(newTheme); }); } } function applyScale(scale) { const factor = parseInt(scale, 10) / 100; document.body.style.zoom = factor; } async function saveScaleSetting(scale) { const settings = await loadSettings(); settings.appearance.scale = scale; await gpcl.saveSettings(settings); } async function initScale() { const settings = await loadSettings(); const scale = settings.appearance?.scale || '100'; applyScale(scale); const scaleSelect = document.getElementById('settings-scale'); if (scaleSelect) { scaleSelect.value = scale; scaleSelect.addEventListener('change', async () => { const newScale = scaleSelect.value; applyScale(newScale); await saveScaleSetting(newScale); }); } } async function saveJavaMirrorSettings(mirror, customUrl) { const settings = await loadSettings(); settings.download.javaMirror = mirror; settings.download.customJavaMirror = customUrl; await gpcl.saveSettings(settings); }