1
0
Fork 0
GoodPlanCraftLaunCher/renderer/renderer.js

3258 lines
106 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* 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 = `
<span class="toast-icon">${icons[type] || icons.info}</span>
<div style="flex:1">
<div class="toast-title">${title}</div>
${message ? `<div class="toast-message">${message}</div>` : ''}
</div>
<button class="toast-close" onclick="this.parentElement.remove()">×</button>
`;
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 = '<option value="">请选择版本</option>';
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 = '<div class="version-list-empty">暂无已安装的版本</div>';
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 = `
<span class="version-item-icon">${icon}</span>
<span class="version-item-id">${versionId}</span>
<span class="version-item-time">${timeText}</span>
`;
item.addEventListener('click', () => {
selectVersion(versionId);
});
container.appendChild(item);
});
countEl.textContent = `${versions.length} 个版本`;
// 如果没有选中的版本,自动选中第一个
if (!selectedVersionId && versions.length > 0) {
selectVersion(versions[0]);
}
} catch (err) {
container.innerHTML = '<div class="version-list-empty">加载版本列表失败</div>';
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 = `
<span class="detail-option-version-name">${version.name}</span>
<span class="detail-option-version-check">✓</span>
`;
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 = `
<span class="version-category-arrow ${isRelease ? '' : 'collapsed'}">▼</span>
<span class="version-category-icon">${getCategoryIcon(type)}</span>
<span class="version-category-name">${getCategoryLabel(type)}</span>
<span class="version-category-count">${items.length} 个版本</span>
`;
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 = `
<span class="version-item-id">${displayName}</span>
<span class="version-item-time">${formatDate(v.releaseTime)}</span>
`;
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 = '<div class="version-list-loading">正在加载版本列表...</div>';
}
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 = '<div class="version-list-error">无法获取版本列表<br><button class="refresh-btn" style="margin-top:12px;" onclick="loadRemoteVersions()">重试</button></div>';
}
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 = '<div class="version-list-error">加载失败,请检查网络连接<br><button class="refresh-btn" style="margin-top:12px;" onclick="loadRemoteVersions()">重试</button></div>';
}
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 = `<div class="update-log-title">发现 ${allNewerVersions.length} 个新版本!</div>${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 ? `<div class="update-card-title">${v.title}</div>` : '';
const logHtml = v.log ? `<div class="update-card-log">${v.log}</div>` : '';
return `<div class="update-version-card ${isNewest ? 'newest' : ''}">` +
`<div class="update-card-header"><span class="update-card-version">${v.version}</span>` +
`${isNewest ? '<span class="update-card-badge">最新</span>' : ''}</div>` +
`${titleHtml}${logHtml}</div>`;
}).reverse().join('');
return `<div class="update-versions-list">${cards}</div>`;
}
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(b.version, a.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);
}