/*
* CaelLab BY-SA Code License
* Copyright (c) 2026 Yunyun(云云) By 虚舟实验室(CaelLab) / CaelLabGameTS
* 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 `
${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);
}