diff --git a/GUI/js/chat.js b/GUI/js/chat.js
new file mode 100644
index 0000000..90bd1d8
--- /dev/null
+++ b/GUI/js/chat.js
@@ -0,0 +1,358 @@
+
+let socket = null;
+let isAuthenticated = false;
+let messageQueue = [];
+let chatUsername = '';
+const SOCKET_URL = 'http://3.10.208.30:3001';
+const MAX_MESSAGE_LENGTH = 500;
+
+export async function initChat() {
+ if (window.electronAPI?.loadChatUsername) {
+ chatUsername = await window.electronAPI.loadChatUsername();
+ }
+
+ if (!chatUsername || chatUsername.trim() === '') {
+ showUsernameModal();
+ return;
+ }
+
+ setupChatUI();
+ await connectToChat();
+}
+
+function showUsernameModal() {
+ const modal = document.getElementById('chatUsernameModal');
+ if (modal) {
+ modal.style.display = 'flex';
+
+ const input = document.getElementById('chatUsernameInput');
+ if (input) {
+ setTimeout(() => input.focus(), 100);
+ }
+ }
+}
+
+function hideUsernameModal() {
+ const modal = document.getElementById('chatUsernameModal');
+ if (modal) {
+ modal.style.display = 'none';
+ }
+}
+
+async function submitChatUsername() {
+ const input = document.getElementById('chatUsernameInput');
+ const errorMsg = document.getElementById('chatUsernameError');
+
+ if (!input) return;
+
+ const username = input.value.trim();
+
+ if (username.length === 0) {
+ if (errorMsg) errorMsg.textContent = 'Username cannot be empty';
+ return;
+ }
+
+ if (username.length < 3) {
+ if (errorMsg) errorMsg.textContent = 'Username must be at least 3 characters';
+ return;
+ }
+
+ if (username.length > 20) {
+ if (errorMsg) errorMsg.textContent = 'Username must be 20 characters or less';
+ return;
+ }
+
+ if (!/^[a-zA-Z0-9_-]+$/.test(username)) {
+ if (errorMsg) errorMsg.textContent = 'Username can only contain letters, numbers, - and _';
+ return;
+ }
+
+ chatUsername = username;
+ if (window.electronAPI?.saveChatUsername) {
+ await window.electronAPI.saveChatUsername(username);
+ }
+
+ hideUsernameModal();
+
+ setupChatUI();
+ await connectToChat();
+}
+
+function setupChatUI() {
+ const sendBtn = document.getElementById('chatSendBtn');
+ const chatInput = document.getElementById('chatInput');
+ const chatMessages = document.getElementById('chatMessages');
+
+ if (!sendBtn || !chatInput || !chatMessages) {
+ console.warn('Chat UI elements not found');
+ return;
+ }
+
+ sendBtn.addEventListener('click', () => {
+ sendMessage();
+ });
+
+ chatInput.addEventListener('keypress', (e) => {
+ if (e.key === 'Enter' && !e.shiftKey) {
+ e.preventDefault();
+ sendMessage();
+ }
+ });
+
+ chatInput.addEventListener('input', () => {
+ if (chatInput.value.length > MAX_MESSAGE_LENGTH) {
+ chatInput.value = chatInput.value.substring(0, MAX_MESSAGE_LENGTH);
+ }
+ updateCharCounter();
+ });
+
+ updateCharCounter();
+}
+
+async function connectToChat() {
+ try {
+ if (!window.io) {
+ await loadSocketIO();
+ }
+
+ const userId = await window.electronAPI?.getUserId();
+
+ if (!userId) {
+ console.error('User ID not available');
+ addSystemMessage('Error: Could not connect to chat');
+ return;
+ }
+
+ if (!chatUsername || chatUsername.trim() === '') {
+ console.error('Chat username not set');
+ addSystemMessage('Error: Username not set');
+ return;
+ }
+
+ socket = io(SOCKET_URL, {
+ transports: ['websocket', 'polling'],
+ reconnection: true,
+ reconnectionAttempts: 5,
+ reconnectionDelay: 1000
+ });
+
+ socket.on('connect', () => {
+ console.log('Connected to chat server');
+ socket.emit('authenticate', { username: chatUsername, userId });
+ });
+
+ socket.on('authenticated', (data) => {
+ isAuthenticated = true;
+ addSystemMessage(`Connected as ${data.username}`);
+
+ while (messageQueue.length > 0) {
+ const msg = messageQueue.shift();
+ socket.emit('send_message', { message: msg });
+ }
+ });
+
+ socket.on('message', (data) => {
+ if (data.type === 'system') {
+ addSystemMessage(data.message);
+ } else if (data.type === 'user') {
+ addUserMessage(data.username, data.message, data.timestamp);
+ }
+ });
+
+ socket.on('users_update', (data) => {
+ updateOnlineCount(data.count);
+ });
+
+ socket.on('error', (data) => {
+ addSystemMessage(`Error: ${data.message}`, 'error');
+ });
+
+ socket.on('clear_chat', (data) => {
+ clearAllMessages();
+ addSystemMessage(data.message || 'Chat cleared by server', 'warning');
+ });
+
+ socket.on('disconnect', () => {
+ isAuthenticated = false;
+ console.log('Disconnected from chat server');
+ addSystemMessage('Disconnected from chat', 'error');
+ });
+
+ socket.on('connect_error', (error) => {
+ console.error('Connection error:', error);
+ addSystemMessage('Connection error. Retrying...', 'error');
+ });
+
+ } catch (error) {
+ console.error('Error connecting to chat:', error);
+ addSystemMessage('Failed to connect to chat server', 'error');
+ }
+}
+
+function loadSocketIO() {
+ return new Promise((resolve, reject) => {
+ const script = document.createElement('script');
+ script.src = 'https://cdn.socket.io/4.6.1/socket.io.min.js';
+ script.onload = resolve;
+ script.onerror = reject;
+ document.head.appendChild(script);
+ });
+}
+
+function sendMessage() {
+ const chatInput = document.getElementById('chatInput');
+ const message = chatInput.value.trim();
+
+ if (!message || message.length === 0) {
+ return;
+ }
+
+ if (message.length > MAX_MESSAGE_LENGTH) {
+ addSystemMessage(`Message too long (max ${MAX_MESSAGE_LENGTH} characters)`, 'error');
+ return;
+ }
+
+ if (!socket || !isAuthenticated) {
+ messageQueue.push(message);
+ addSystemMessage('Connecting... Your message will be sent soon.', 'warning');
+ chatInput.value = '';
+ updateCharCounter();
+ return;
+ }
+
+ socket.emit('send_message', { message });
+
+ chatInput.value = '';
+ updateCharCounter();
+}
+
+function addUserMessage(username, message, timestamp) {
+ const chatMessages = document.getElementById('chatMessages');
+ if (!chatMessages) return;
+
+ const messageDiv = document.createElement('div');
+ messageDiv.className = 'chat-message user-message';
+
+ const time = new Date(timestamp).toLocaleTimeString('en-US', {
+ hour: '2-digit',
+ minute: '2-digit'
+ });
+
+ messageDiv.innerHTML = `
+
+ ${message}
+ `;
+
+ chatMessages.appendChild(messageDiv);
+ scrollToBottom();
+}
+
+function addSystemMessage(message, type = 'info') {
+ const chatMessages = document.getElementById('chatMessages');
+ if (!chatMessages) return;
+
+ const messageDiv = document.createElement('div');
+ messageDiv.className = `chat-message system-message system-${type}`;
+ messageDiv.innerHTML = `
+
+ ${escapeHtml(message)}
+
+ `;
+
+ chatMessages.appendChild(messageDiv);
+ scrollToBottom();
+}
+
+function updateOnlineCount(count) {
+ const onlineCountElement = document.getElementById('chatOnlineCount');
+ if (onlineCountElement) {
+ onlineCountElement.textContent = count;
+ }
+}
+
+function updateCharCounter() {
+ const chatInput = document.getElementById('chatInput');
+ const charCounter = document.getElementById('chatCharCounter');
+
+ if (chatInput && charCounter) {
+ const length = chatInput.value.length;
+ charCounter.textContent = `${length}/${MAX_MESSAGE_LENGTH}`;
+
+ if (length > MAX_MESSAGE_LENGTH * 0.9) {
+ charCounter.classList.add('warning');
+ } else {
+ charCounter.classList.remove('warning');
+ }
+ }
+}
+
+function scrollToBottom() {
+ const chatMessages = document.getElementById('chatMessages');
+ if (chatMessages) {
+ chatMessages.scrollTop = chatMessages.scrollHeight;
+ }
+}
+
+function clearAllMessages() {
+ const chatMessages = document.getElementById('chatMessages');
+ if (chatMessages) {
+ chatMessages.innerHTML = '';
+ console.log('Chat cleared');
+ }
+}
+
+function escapeHtml(text) {
+ const div = document.createElement('div');
+ div.textContent = text;
+ return div.innerHTML;
+}
+
+window.addEventListener('beforeunload', () => {
+ if (socket && socket.connected) {
+ socket.disconnect();
+ }
+});
+
+document.addEventListener('DOMContentLoaded', () => {
+ const usernameSubmitBtn = document.getElementById('chatUsernameSubmit');
+ const usernameCancelBtn = document.getElementById('chatUsernameCancel');
+ const usernameInput = document.getElementById('chatUsernameInput');
+
+ if (usernameSubmitBtn) {
+ usernameSubmitBtn.addEventListener('click', submitChatUsername);
+ }
+
+ if (usernameCancelBtn) {
+ usernameCancelBtn.addEventListener('click', () => {
+ hideUsernameModal();
+ const playNavItem = document.querySelector('[data-page="play"]');
+ if (playNavItem) playNavItem.click();
+ });
+ }
+
+ if (usernameInput) {
+ usernameInput.addEventListener('keypress', (e) => {
+ if (e.key === 'Enter') {
+ e.preventDefault();
+ submitChatUsername();
+ }
+ });
+ }
+
+ const chatNavItem = document.querySelector('[data-page="chat"]');
+ if (chatNavItem) {
+ chatNavItem.addEventListener('click', () => {
+ if (!socket) {
+ initChat();
+ }
+ });
+ }
+});
+
+window.ChatAPI = {
+ send: sendMessage,
+ disconnect: () => socket?.disconnect()
+};
diff --git a/GUI/js/install.js b/GUI/js/install.js
new file mode 100644
index 0000000..0fa5a78
--- /dev/null
+++ b/GUI/js/install.js
@@ -0,0 +1,194 @@
+let isDownloading = false;
+
+let installPage;
+let installBtn;
+let installText;
+let installPlayerName;
+let installCustomCheck;
+let installCustomOptions;
+let installPathInput;
+
+export function setupInstallation() {
+ installPage = document.getElementById('install-page');
+ installBtn = document.getElementById('installBtn');
+ installText = document.getElementById('installText');
+ installPlayerName = document.getElementById('installPlayerName');
+ installCustomCheck = document.getElementById('installCustomCheck');
+ installCustomOptions = document.getElementById('installCustomOptions');
+ installPathInput = document.getElementById('installPath');
+
+ if (installCustomCheck && installCustomOptions) {
+ installCustomCheck.addEventListener('change', (e) => {
+ if (e.target.checked) {
+ installCustomOptions.classList.add('show');
+ } else {
+ installCustomOptions.classList.remove('show');
+ }
+ });
+ }
+
+ if (installPlayerName) {
+ installPlayerName.addEventListener('change', savePlayerName);
+ }
+}
+
+export async function installGame() {
+ if (isDownloading || (installBtn && installBtn.disabled)) return;
+
+ const playerName = (installPlayerName ? installPlayerName.value.trim() : '') || 'Player';
+ const installPath = installPathInput ? installPathInput.value.trim() : '';
+
+ if (window.LauncherUI) window.LauncherUI.showProgress();
+ isDownloading = true;
+ if (installBtn) {
+ installBtn.disabled = true;
+ installText.textContent = 'INSTALLING...';
+ }
+
+ try {
+ if (window.electronAPI && window.electronAPI.installGame) {
+ const result = await window.electronAPI.installGame(playerName, '', installPath);
+
+ if (result.success) {
+ if (window.LauncherUI) {
+ window.LauncherUI.updateProgress({ message: 'Installation completed successfully!' });
+ setTimeout(() => {
+ window.LauncherUI.hideProgress();
+ window.LauncherUI.showLauncherOrInstall(true);
+ const playerNameInput = document.getElementById('playerName');
+ if (playerNameInput) playerNameInput.value = playerName;
+ }, 2000);
+ }
+ } else {
+ throw new Error(result.error || 'Installation failed');
+ }
+ } else {
+ simulateInstallation(playerName);
+ }
+ } catch (error) {
+ if (window.LauncherUI) {
+ window.LauncherUI.updateProgress({ message: `Installation failed: ${error.message}` });
+ setTimeout(() => {
+ window.LauncherUI.hideProgress();
+ resetInstallButton();
+ }, 3000);
+ }
+ }
+}
+
+function simulateInstallation(playerName) {
+ let progress = 0;
+ const interval = setInterval(() => {
+ progress += Math.random() * 3;
+ if (progress > 100) progress = 100;
+
+ if (window.LauncherUI) {
+ window.LauncherUI.updateProgress({
+ percent: progress,
+ message: progress < 100 ? 'Installing game files...' : 'Installation complete!',
+ speed: 1024 * 1024 * (5 + Math.random() * 10),
+ downloaded: progress * 1024 * 1024 * 20,
+ total: 1024 * 1024 * 2000
+ });
+ }
+
+ if (progress >= 100) {
+ clearInterval(interval);
+ setTimeout(() => {
+ if (window.LauncherUI) {
+ window.LauncherUI.updateProgress({ message: 'Installation completed successfully!' });
+ setTimeout(() => {
+ window.LauncherUI.hideProgress();
+ window.LauncherUI.showLauncherOrInstall(true);
+ const playerNameInput = document.getElementById('playerName');
+ if (playerNameInput) playerNameInput.value = playerName;
+ resetInstallButton();
+ }, 2000);
+ }
+ }, 1000);
+ }
+ }, 200);
+}
+
+function resetInstallButton() {
+ isDownloading = false;
+ if (installBtn) {
+ installBtn.disabled = false;
+ installText.textContent = 'INSTALL HYTALE';
+ }
+}
+
+export async function browseInstallPath() {
+ try {
+ if (window.electronAPI && window.electronAPI.selectInstallPath) {
+ const result = await window.electronAPI.selectInstallPath();
+ if (result && installPathInput) {
+ installPathInput.value = result;
+ }
+ }
+ } catch (error) {
+ console.error('Error browsing install path:', error);
+ }
+}
+
+async function savePlayerName() {
+ try {
+ if (window.electronAPI && window.electronAPI.saveSettings) {
+ const playerName = (installPlayerName ? installPlayerName.value.trim() : '') || 'Player';
+ await window.electronAPI.saveSettings({ playerName });
+ }
+ } catch (error) {
+ console.error('Error saving player name:', error);
+ }
+}
+
+export async function checkGameStatusAndShowInterface() {
+ try {
+ if (window.electronAPI && window.electronAPI.isGameInstalled) {
+ const installed = await window.electronAPI.isGameInstalled();
+ if (window.LauncherUI) {
+ window.LauncherUI.showLauncherOrInstall(installed);
+ }
+ if (installed) {
+ await loadPlayerSettings();
+ }
+ } else {
+ if (window.LauncherUI) {
+ window.LauncherUI.showLauncherOrInstall(false);
+ }
+ }
+ } catch (error) {
+ console.error('Error checking game status:', error);
+ if (window.LauncherUI) {
+ window.LauncherUI.showLauncherOrInstall(false);
+ }
+ }
+}
+
+async function loadPlayerSettings() {
+ try {
+ if (window.electronAPI && window.electronAPI.loadSettings) {
+ const settings = await window.electronAPI.loadSettings();
+ if (settings) {
+ const playerNameInput = document.getElementById('playerName');
+ const javaPathInput = document.getElementById('javaPath');
+ if (settings.playerName && playerNameInput) {
+ playerNameInput.value = settings.playerName;
+ }
+ if (settings.javaPath && javaPathInput) {
+ javaPathInput.value = settings.javaPath;
+ }
+ }
+ }
+ } catch (error) {
+ console.error('Error loading settings:', error);
+ }
+}
+
+window.installGame = installGame;
+window.browseInstallPath = browseInstallPath;
+
+document.addEventListener('DOMContentLoaded', async () => {
+ setupInstallation();
+ await checkGameStatusAndShowInterface();
+});
diff --git a/GUI/js/launcher.js b/GUI/js/launcher.js
new file mode 100644
index 0000000..977f4ea
--- /dev/null
+++ b/GUI/js/launcher.js
@@ -0,0 +1,235 @@
+let isDownloading = false;
+
+let playBtn;
+let playText;
+let homePlayBtn;
+let uninstallBtn;
+let playerNameInput;
+let javaPathInput;
+
+export function setupLauncher() {
+ playBtn = document.getElementById('playBtn');
+ playText = document.getElementById('playText');
+ homePlayBtn = document.getElementById('homePlayBtn');
+ uninstallBtn = document.getElementById('uninstallBtn');
+ playerNameInput = document.getElementById('playerName');
+ javaPathInput = document.getElementById('javaPath');
+
+ if (playerNameInput) {
+ playerNameInput.addEventListener('change', savePlayerName);
+ }
+
+ if (javaPathInput) {
+ javaPathInput.addEventListener('change', saveJavaPath);
+ }
+
+ if (window.electronAPI && window.electronAPI.onProgressUpdate) {
+ window.electronAPI.onProgressUpdate((data) => {
+ if (window.LauncherUI) {
+ window.LauncherUI.showProgress();
+ window.LauncherUI.updateProgress(data);
+ }
+ });
+ }
+}
+
+export async function launch() {
+ if (isDownloading || (playBtn && playBtn.disabled)) return;
+
+ let playerName = 'Player';
+ if (window.SettingsAPI && window.SettingsAPI.getCurrentPlayerName) {
+ playerName = window.SettingsAPI.getCurrentPlayerName();
+ } else if (playerNameInput && playerNameInput.value.trim()) {
+ playerName = playerNameInput.value.trim();
+ }
+
+ let javaPath = '';
+ if (window.SettingsAPI && window.SettingsAPI.getCurrentJavaPath) {
+ javaPath = window.SettingsAPI.getCurrentJavaPath();
+ }
+
+ if (window.LauncherUI) window.LauncherUI.showProgress();
+ isDownloading = true;
+ if (playBtn) {
+ playBtn.disabled = true;
+ playText.textContent = 'LAUNCHING...';
+ }
+
+ try {
+ if (window.electronAPI && window.electronAPI.launchGame) {
+ const result = await window.electronAPI.launchGame(playerName, javaPath, '');
+
+ if (result.success) {
+ if (window.LauncherUI) {
+ window.LauncherUI.updateProgress({ message: 'Game started successfully!' });
+ setTimeout(() => {
+ window.LauncherUI.hideProgress();
+ if (window.electronAPI.minimizeWindow) {
+ window.electronAPI.minimizeWindow();
+ }
+ }, 2000);
+ }
+ } else {
+ throw new Error(result.error || 'Launch failed');
+ }
+ } else {
+ setTimeout(() => {
+ if (window.LauncherUI) {
+ window.LauncherUI.updateProgress({ message: 'Game started successfully!' });
+ setTimeout(() => {
+ window.LauncherUI.hideProgress();
+ resetPlayButton();
+ }, 2000);
+ }
+ }, 2000);
+ }
+ } catch (error) {
+ if (window.LauncherUI) {
+ window.LauncherUI.updateProgress({ message: `Failed: ${error.message}` });
+ setTimeout(() => {
+ window.LauncherUI.hideProgress();
+ resetPlayButton();
+ }, 3000);
+ }
+ }
+}
+
+export async function uninstallGame() {
+ if (!confirm('Are you sure you want to uninstall Hytale? All game files will be deleted.')) {
+ return;
+ }
+
+ if (window.LauncherUI) window.LauncherUI.showProgress();
+ if (window.LauncherUI) window.LauncherUI.updateProgress({ message: 'Uninstalling game...' });
+ if (uninstallBtn) uninstallBtn.disabled = true;
+
+ try {
+ if (window.electronAPI && window.electronAPI.uninstallGame) {
+ const result = await window.electronAPI.uninstallGame();
+
+ if (result.success) {
+ if (window.LauncherUI) {
+ window.LauncherUI.updateProgress({ message: 'Game uninstalled successfully!' });
+ setTimeout(() => {
+ window.LauncherUI.hideProgress();
+ window.LauncherUI.showLauncherOrInstall(false);
+ }, 2000);
+ }
+ } else {
+ throw new Error(result.error || 'Uninstall failed');
+ }
+ } else {
+ setTimeout(() => {
+ if (window.LauncherUI) {
+ window.LauncherUI.updateProgress({ message: 'Game uninstalled successfully!' });
+ setTimeout(() => {
+ window.LauncherUI.hideProgress();
+ window.LauncherUI.showLauncherOrInstall(false);
+ }, 2000);
+ }
+ }, 2000);
+ }
+ } catch (error) {
+ if (window.LauncherUI) {
+ window.LauncherUI.updateProgress({ message: `Uninstall failed: ${error.message}` });
+ setTimeout(() => window.LauncherUI.hideProgress(), 3000);
+ }
+ } finally {
+ if (uninstallBtn) uninstallBtn.disabled = false;
+ }
+}
+
+function resetPlayButton() {
+ isDownloading = false;
+ if (playBtn) {
+ playBtn.disabled = false;
+ playText.textContent = 'PLAY';
+ }
+}
+
+async function savePlayerName() {
+ try {
+ if (window.electronAPI && window.electronAPI.saveSettings) {
+ const playerName = (playerNameInput ? playerNameInput.value.trim() : '') || 'Player';
+ await window.electronAPI.saveSettings({ playerName });
+ }
+ } catch (error) {
+ console.error('Error saving player name:', error);
+ }
+}
+
+async function saveJavaPath() {
+ try {
+ if (window.electronAPI && window.electronAPI.saveSettings) {
+ const javaPath = (javaPathInput ? javaPathInput.value.trim() : '') || '';
+ await window.electronAPI.saveSettings({ javaPath });
+ }
+ } catch (error) {
+ console.error('Error saving Java path:', error);
+ }
+}
+
+function toggleCustomJava() {
+ if (!customJavaOptions) return;
+
+ if (customJavaCheck && customJavaCheck.checked) {
+ customJavaOptions.style.display = 'block';
+ } else {
+ customJavaOptions.style.display = 'none';
+ if (customJavaPath) customJavaPath.value = '';
+ saveCustomJavaPath('');
+ }
+}
+
+async function browseJavaPath() {
+ try {
+ if (window.electronAPI && window.electronAPI.browseJavaPath) {
+ const result = await window.electronAPI.browseJavaPath();
+ if (result && result.filePaths && result.filePaths.length > 0) {
+ const selectedPath = result.filePaths[0];
+ if (customJavaPath) {
+ customJavaPath.value = selectedPath;
+ }
+ await saveCustomJavaPath(selectedPath);
+ }
+ }
+ } catch (error) {
+ console.error('Error browsing Java path:', error);
+ }
+}
+
+async function saveCustomJavaPath(path) {
+ try {
+ if (window.electronAPI && window.electronAPI.saveJavaPath) {
+ await window.electronAPI.saveJavaPath(path);
+ }
+ } catch (error) {
+ console.error('Error saving custom Java path:', error);
+ }
+}
+
+async function loadCustomJavaPath() {
+ try {
+ if (window.electronAPI && window.electronAPI.loadJavaPath) {
+ const savedPath = await window.electronAPI.loadJavaPath();
+ if (savedPath && savedPath.trim()) {
+ if (customJavaPath) {
+ customJavaPath.value = savedPath;
+ }
+ if (customJavaCheck) {
+ customJavaCheck.checked = true;
+ }
+ if (customJavaOptions) {
+ customJavaOptions.style.display = 'block';
+ }
+ }
+ }
+ } catch (error) {
+ console.error('Error loading custom Java path:', error);
+ }
+}
+
+window.launch = launch;
+window.uninstallGame = uninstallGame;
+
+document.addEventListener('DOMContentLoaded', setupLauncher);
diff --git a/GUI/js/mods.js b/GUI/js/mods.js
new file mode 100644
index 0000000..9f5d540
--- /dev/null
+++ b/GUI/js/mods.js
@@ -0,0 +1,724 @@
+
+const API_KEY = '$2a$10$bqk254NMZOWVTzLVJCcxEOmhcyUujKxA5xk.kQCN9q0KNYFJd5b32';
+const CURSEFORGE_API = 'https://api.curseforge.com/v1';
+const HYTALE_GAME_ID = 70216;
+
+let installedMods = [];
+let browseMods = [];
+let searchQuery = '';
+let modsPage = 0;
+let modsPageSize = 20;
+let modsTotalPages = 1;
+
+export async function initModsManager() {
+ setupModsEventListeners();
+ await loadInstalledMods();
+ await loadBrowseMods();
+}
+
+function setupModsEventListeners() {
+ const searchInput = document.getElementById('modsSearch');
+ if (searchInput) {
+ let searchTimeout;
+ searchInput.addEventListener('input', (e) => {
+ searchQuery = e.target.value.toLowerCase().trim();
+
+ clearTimeout(searchTimeout);
+ searchTimeout = setTimeout(() => {
+ modsPage = 0;
+ loadBrowseMods();
+ }, 500);
+ });
+ }
+
+ const myModsBtn = document.getElementById('myModsBtn');
+ if (myModsBtn) {
+ myModsBtn.addEventListener('click', openMyModsModal);
+ }
+
+ const closeModalBtn = document.getElementById('closeMyModsModal');
+ if (closeModalBtn) {
+ closeModalBtn.addEventListener('click', closeMyModsModal);
+ }
+
+ const modal = document.getElementById('myModsModal');
+ if (modal) {
+ modal.addEventListener('click', (e) => {
+ if (e.target === modal) {
+ closeMyModsModal();
+ }
+ });
+ }
+
+ const prevPageBtn = document.getElementById('prevPage');
+ const nextPageBtn = document.getElementById('nextPage');
+
+ if (prevPageBtn) {
+ prevPageBtn.addEventListener('click', () => {
+ if (modsPage > 0) {
+ modsPage--;
+ loadBrowseMods();
+ }
+ });
+ }
+
+ if (nextPageBtn) {
+ nextPageBtn.addEventListener('click', () => {
+ if (modsPage < modsTotalPages - 1) {
+ modsPage++;
+ loadBrowseMods();
+ }
+ });
+ }
+}
+
+function openMyModsModal() {
+ const modal = document.getElementById('myModsModal');
+ if (modal) {
+ modal.classList.add('active');
+ loadInstalledMods();
+ }
+}
+
+function closeMyModsModal() {
+ const modal = document.getElementById('myModsModal');
+ if (modal) {
+ modal.classList.remove('active');
+ }
+}
+
+async function loadInstalledMods() {
+ try {
+ const modsPath = await window.electronAPI?.getModsPath();
+ if (!modsPath) {
+ showInstalledModsError('Could not get mods directory');
+ return;
+ }
+
+ const mods = await window.electronAPI?.loadInstalledMods(modsPath);
+ installedMods = mods || [];
+
+ displayInstalledMods(installedMods);
+ } catch (error) {
+ console.error('Error loading installed mods:', error);
+ showInstalledModsError('Failed to load installed mods');
+ }
+}
+
+function displayInstalledMods(mods) {
+ const modsContainer = document.getElementById('installedModsList');
+ if (!modsContainer) return;
+
+ if (mods.length === 0) {
+ modsContainer.innerHTML = `
+
+
+
No Mods Installed
+
Add mods from CurseForge or import local files
+
+ `;
+ return;
+ }
+
+ modsContainer.innerHTML = mods.map(mod => createInstalledModCard(mod)).join('');
+
+ mods.forEach(mod => {
+ const toggleBtn = document.getElementById(`toggle-installed-${mod.id}`);
+ const deleteBtn = document.getElementById(`delete-installed-${mod.id}`);
+
+ if (toggleBtn) {
+ toggleBtn.addEventListener('click', () => toggleMod(mod.id));
+ }
+
+ if (deleteBtn) {
+ deleteBtn.addEventListener('click', () => deleteMod(mod.id));
+ }
+ });
+}
+
+function createInstalledModCard(mod) {
+ const statusClass = mod.enabled ? 'text-primary' : 'text-zinc-500';
+ const statusText = mod.enabled ? 'ACTIVE' : 'DISABLED';
+ const toggleBtnClass = mod.enabled ? 'btn-disable' : 'btn-enable';
+ const toggleBtnText = mod.enabled ? 'DISABLE' : 'ENABLE';
+ const toggleIcon = mod.enabled ? 'fa-pause' : 'fa-play';
+
+ return `
+
+
+
+
+
+
+
+
${mod.description || 'No description available'}
+
+
+
+
+
+ ${statusText}
+
+
+
+
+
+
+
+ `;
+}
+
+async function loadBrowseMods() {
+ const browseContainer = document.getElementById('browseModsList');
+ if (!browseContainer) return;
+
+ browseContainer.innerHTML = 'Loading mods from CurseForge... ';
+
+ try {
+ if (!API_KEY || API_KEY.length < 10) {
+ browseContainer.innerHTML = `
+
+
+
API Key Required
+
CurseForge API key is needed to browse mods
+
+ `;
+ return;
+ }
+
+ const offset = modsPage * modsPageSize;
+ let url = `${CURSEFORGE_API}/mods/search?gameId=${HYTALE_GAME_ID}&pageSize=${modsPageSize}&sortOrder=desc&sortField=6&index=${offset}`;
+
+ if (searchQuery && searchQuery.length > 0) {
+ url += `&searchFilter=${encodeURIComponent(searchQuery)}`;
+ }
+
+ console.log('Fetching mods from page', modsPage + 1, 'offset:', offset, 'search:', searchQuery || 'none', 'URL:', url);
+
+ const response = await fetch(url, {
+ headers: {
+ 'x-api-key': API_KEY,
+ 'Accept': 'application/json'
+ }
+ });
+
+ console.log('Response status:', response.status);
+
+ if (!response.ok) {
+ const errorText = await response.text();
+ console.error('API Error Response:', errorText);
+ throw new Error(`CurseForge API error: ${response.status} - ${errorText}`);
+ }
+
+ const data = await response.json();
+ console.log('API Response data:', data);
+ console.log('Total mods found:', data.data?.length || 0);
+
+ browseMods = (data.data || []).map(mod => ({
+ id: mod.id.toString(),
+ name: mod.name,
+ slug: mod.slug,
+ summary: mod.summary || 'No description available',
+ downloadCount: mod.downloadCount || 0,
+ author: mod.authors?.[0]?.name || 'Unknown',
+ version: mod.latestFiles?.[0]?.displayName || 'Unknown',
+ thumbnailUrl: mod.logo?.thumbnailUrl || null,
+ websiteUrl: mod.links?.websiteUrl || null,
+ modId: mod.id,
+ fileId: mod.latestFiles?.[0]?.id,
+ fileName: mod.latestFiles?.[0]?.fileName,
+ downloadUrl: mod.latestFiles?.[0]?.downloadUrl
+ }));
+
+ console.log('Processed mods:', browseMods.length);
+
+ modsTotalPages = Math.ceil((data.pagination?.totalCount || 1) / modsPageSize);
+ displayBrowseMods(browseMods);
+ updatePagination();
+ } catch (error) {
+ console.error('Error loading browse mods:', error);
+ browseContainer.innerHTML = `
+
+
+
API Error
+
Failed to load mods from CurseForge
+
${error.message}
+
+ `;
+ }
+}
+
+function displayBrowseMods(mods) {
+ const browseContainer = document.getElementById('browseModsList');
+ if (!browseContainer) return;
+
+ if (mods.length === 0) {
+ browseContainer.innerHTML = `
+
+
+
No Mods Found
+
Try adjusting your search
+
+ `;
+ return;
+ }
+
+ browseContainer.innerHTML = mods.map(mod => createBrowseModCard(mod)).join('');
+
+ mods.forEach(mod => {
+ const installBtn = document.getElementById(`install-${mod.id}`);
+ if (installBtn) {
+ installBtn.addEventListener('click', () => downloadAndInstallMod(mod));
+ }
+ });
+}
+
+function createBrowseModCard(mod) {
+ const isInstalled = installedMods.some(installed =>
+ installed.name.toLowerCase().includes(mod.name.toLowerCase()) ||
+ installed.curseForgeId == mod.id
+ );
+
+ return `
+
+
+ ${mod.thumbnailUrl ?
+ `

'\">` :
+ `
`
+ }
+
+
+
+
+
${mod.summary}
+
+
+
+ ${mod.author}
+
+
+
+ ${formatNumber(mod.downloadCount)}
+
+
+
+
+
+
+ ${!isInstalled ?
+ `` :
+ ``
+ }
+
+
+ `;
+}
+
+async function downloadAndInstallMod(modInfo) {
+ try {
+ window.LauncherUI?.showProgress(`Downloading ${modInfo.name}...`);
+
+ const result = await window.electronAPI?.downloadMod(modInfo);
+
+ if (result?.success) {
+ const newMod = {
+ id: result.modInfo.id,
+ name: modInfo.name,
+ version: modInfo.version,
+ description: modInfo.summary,
+ author: modInfo.author,
+ enabled: true,
+ fileName: result.fileName,
+ fileSize: result.modInfo.fileSize,
+ dateInstalled: new Date().toISOString(),
+ curseForgeId: modInfo.modId,
+ curseForgeFileId: modInfo.fileId
+ };
+
+ installedMods.push(newMod);
+
+ await loadInstalledMods();
+ await loadBrowseMods();
+ window.LauncherUI?.hideProgress();
+ showNotification(`${modInfo.name} installed successfully! 🎉`, 'success');
+ } else {
+ throw new Error(result?.error || 'Failed to download mod');
+ }
+ } catch (error) {
+ console.error('Error downloading mod:', error);
+ window.LauncherUI?.hideProgress();
+ showNotification('Failed to download mod: ' + error.message, 'error');
+ }
+}
+
+async function toggleMod(modId) {
+ try {
+ window.LauncherUI?.showProgress('Toggling mod...');
+
+ const modsPath = await window.electronAPI?.getModsPath();
+ const result = await window.electronAPI?.toggleMod(modId, modsPath);
+
+ if (result?.success) {
+ await loadInstalledMods();
+ window.LauncherUI?.hideProgress();
+ } else {
+ throw new Error(result?.error || 'Failed to toggle mod');
+ }
+ } catch (error) {
+ console.error('Error toggling mod:', error);
+ window.LauncherUI?.hideProgress();
+ showNotification('Failed to toggle mod: ' + error.message, 'error');
+ }
+}
+
+async function deleteMod(modId) {
+ const mod = installedMods.find(m => m.id === modId);
+ if (!mod) return;
+
+ showConfirmModal(
+ `Are you sure you want to delete "${mod.name}"? This action cannot be undone.`,
+ async () => {
+ try {
+ window.LauncherUI?.showProgress('Deleting mod...');
+
+ const modsPath = await window.electronAPI?.getModsPath();
+ const result = await window.electronAPI?.uninstallMod(modId, modsPath);
+
+ if (result?.success) {
+ await loadInstalledMods();
+ await loadBrowseMods();
+ window.LauncherUI?.hideProgress();
+ showNotification(`"${mod.name}" deleted successfully`, 'success');
+ } else {
+ throw new Error(result?.error || 'Failed to delete mod');
+ }
+ } catch (error) {
+ console.error('Error deleting mod:', error);
+ window.LauncherUI?.hideProgress();
+ showNotification('Failed to delete mod: ' + error.message, 'error');
+ }
+ }
+ );
+}
+
+function formatNumber(num) {
+ if (!num) return '0';
+ if (num >= 1000000) return (num / 1000000).toFixed(1) + 'M';
+ if (num >= 1000) return (num / 1000).toFixed(1) + 'K';
+ return num.toString();
+}
+
+function showNotification(message, type = 'info', duration = 4000) {
+ const existing = document.querySelector(`.mod-notification.${type}`);
+ if (existing) {
+ existing.remove();
+ }
+
+ const notification = document.createElement('div');
+ notification.className = `mod-notification ${type}`;
+
+ const icons = {
+ success: 'fa-check-circle',
+ error: 'fa-exclamation-circle',
+ info: 'fa-info-circle',
+ warning: 'fa-exclamation-triangle'
+ };
+
+ const colors = {
+ success: '#10b981',
+ error: '#ef4444',
+ info: '#3b82f6',
+ warning: '#f59e0b'
+ };
+
+ notification.innerHTML = `
+
+
+ ${message}
+
+
+ `;
+
+ notification.style.cssText = `
+ position: fixed;
+ top: 20px;
+ right: 20px;
+ background: ${colors[type]};
+ color: white;
+ padding: 16px 20px;
+ border-radius: 8px;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
+ z-index: 10000;
+ min-width: 300px;
+ max-width: 400px;
+ transform: translateX(100%);
+ transition: transform 0.3s ease;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ font-size: 14px;
+ font-weight: 500;
+ `;
+
+ const contentStyle = `
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ flex: 1;
+ `;
+
+ const closeStyle = `
+ background: none;
+ border: none;
+ color: white;
+ cursor: pointer;
+ padding: 4px;
+ border-radius: 4px;
+ opacity: 0.8;
+ transition: opacity 0.2s;
+ margin-left: 10px;
+ `;
+
+ notification.querySelector('.notification-content').style.cssText = contentStyle;
+ notification.querySelector('.notification-close').style.cssText = closeStyle;
+
+ document.body.appendChild(notification);
+
+ // Animate in
+ setTimeout(() => {
+ notification.style.transform = 'translateX(0)';
+ }, 10);
+
+ // Auto remove
+ setTimeout(() => {
+ if (notification.parentElement) {
+ notification.style.transform = 'translateX(100%)';
+ setTimeout(() => {
+ notification.remove();
+ }, 300);
+ }
+ }, duration);
+}
+
+// Custom confirmation modal
+function showConfirmModal(message, onConfirm, onCancel = null) {
+ const existingModal = document.querySelector('.mod-confirm-modal');
+ if (existingModal) {
+ existingModal.remove();
+ }
+
+ const modal = document.createElement('div');
+ modal.className = 'mod-confirm-modal';
+ modal.style.cssText = `
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0, 0, 0, 0.8);
+ backdrop-filter: blur(4px);
+ z-index: 20000;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ opacity: 0;
+ transition: opacity 0.3s ease;
+ `;
+
+ const dialog = document.createElement('div');
+ dialog.className = 'mod-confirm-dialog';
+ dialog.style.cssText = `
+ background: #1f2937;
+ border-radius: 12px;
+ padding: 0;
+ min-width: 400px;
+ max-width: 500px;
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.6);
+ border: 1px solid rgba(239, 68, 68, 0.3);
+ transform: scale(0.9);
+ transition: transform 0.3s ease;
+ `;
+
+ dialog.innerHTML = `
+
+
+
+
Confirm Deletion
+
+
+
+
+
+
+
+ `;
+
+ modal.appendChild(dialog);
+ document.body.appendChild(modal);
+
+ // Animate in
+ setTimeout(() => {
+ modal.style.opacity = '1';
+ dialog.style.transform = 'scale(1)';
+ }, 10);
+
+ // Event handlers
+ const cancelBtn = dialog.querySelector('.mod-confirm-cancel');
+ const deleteBtn = dialog.querySelector('.mod-confirm-delete');
+
+ const closeModal = () => {
+ modal.style.opacity = '0';
+ dialog.style.transform = 'scale(0.9)';
+ setTimeout(() => {
+ modal.remove();
+ }, 300);
+ };
+
+ cancelBtn.onclick = () => {
+ closeModal();
+ if (onCancel) onCancel();
+ };
+
+ deleteBtn.onclick = () => {
+ closeModal();
+ onConfirm();
+ };
+
+ modal.onclick = (e) => {
+ if (e.target === modal) {
+ closeModal();
+ if (onCancel) onCancel();
+ }
+ };
+
+ // Escape key
+ const handleEscape = (e) => {
+ if (e.key === 'Escape') {
+ closeModal();
+ if (onCancel) onCancel();
+ document.removeEventListener('keydown', handleEscape);
+ }
+ };
+ document.addEventListener('keydown', handleEscape);
+}
+
+function updatePagination() {
+ const currentPageEl = document.getElementById('currentPage');
+ const totalPagesEl = document.getElementById('totalPages');
+ const prevBtn = document.getElementById('prevPage');
+ const nextBtn = document.getElementById('nextPage');
+
+ if (currentPageEl) currentPageEl.textContent = modsPage + 1;
+ if (totalPagesEl) totalPagesEl.textContent = modsTotalPages;
+
+ if (prevBtn) {
+ prevBtn.disabled = modsPage === 0;
+ prevBtn.style.opacity = modsPage === 0 ? '0.5' : '1';
+ prevBtn.style.cursor = modsPage === 0 ? 'not-allowed' : 'pointer';
+ }
+
+ if (nextBtn) {
+ nextBtn.disabled = modsPage >= modsTotalPages - 1;
+ nextBtn.style.opacity = modsPage >= modsTotalPages - 1 ? '0.5' : '1';
+ nextBtn.style.cursor = modsPage >= modsTotalPages - 1 ? 'not-allowed' : 'pointer';
+ }
+}
+
+function showInstalledModsError(message) {
+ const modsContainer = document.getElementById('installedModsList');
+ if (!modsContainer) return;
+
+ modsContainer.innerHTML = `
+
+ `;
+}
+
+function viewModPage(modId) {
+ console.log('Looking for mod with ID:', modId, 'Type:', typeof modId);
+ console.log('Available mods:', browseMods.map(m => ({ id: m.id, name: m.name, type: typeof m.id })));
+
+ const mod = browseMods.find(m => m.id.toString() === modId.toString());
+ if (mod) {
+ console.log('Found mod:', mod.name);
+ let modUrl;
+ if (mod.websiteUrl && mod.websiteUrl.includes('curseforge.com')) {
+ modUrl = mod.websiteUrl;
+ } else if (mod.slug) {
+ modUrl = `https://www.curseforge.com/hytale/mods/${mod.slug}`;
+ } else {
+ const nameSlug = mod.name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
+ modUrl = `https://www.curseforge.com/hytale/mods/${nameSlug}`;
+ }
+
+ console.log('Opening URL:', modUrl);
+
+ if (window.electronAPI && window.electronAPI.openExternalLink) {
+ window.electronAPI.openExternalLink(modUrl);
+ } else {
+ if (window.electronAPI && window.electronAPI.shell) {
+ window.electronAPI.shell.openExternal(modUrl);
+ } else {
+ window.open(modUrl, '_blank');
+ }
+ }
+ } else {
+ console.error('Mod not found with ID:', modId);
+ showNotification('Mod information not found', 'error');
+ }
+}
+
+window.modsManager = {
+ toggleMod,
+ deleteMod,
+ openMyModsModal,
+ closeMyModsModal,
+ viewModPage
+};
+
+document.addEventListener('DOMContentLoaded', initModsManager);
diff --git a/GUI/js/news.js b/GUI/js/news.js
new file mode 100644
index 0000000..7bc5abd
--- /dev/null
+++ b/GUI/js/news.js
@@ -0,0 +1,124 @@
+
+let newsData = [];
+
+export async function loadNews() {
+ try {
+ if (window.electronAPI && window.electronAPI.getHytaleNews) {
+ try {
+ const realNews = await window.electronAPI.getHytaleNews();
+ if (realNews && realNews.length > 0) {
+ newsData = realNews.slice(0, 10).map((article, index) => ({
+ id: index + 1,
+ title: article.title,
+ summary: article.description,
+ type: "NEWS",
+ image: article.imageUrl || '',
+ date: formatDate(article.date),
+ url: article.destUrl
+ }));
+ displayHomeNews(newsData.slice(0, 5));
+ displayFullNews(newsData);
+ } else {
+ showErrorNews();
+ }
+ } catch (error) {
+ console.log('Failed to load news:', error.message);
+ showErrorNews();
+ }
+ } else {
+ showErrorNews();
+ }
+ } catch (error) {
+ console.error('Error loading news:', error);
+ showErrorNews();
+ }
+}
+
+function displayHomeNews(news) {
+ const newsGrid = document.getElementById('newsGrid');
+ if (!newsGrid) return;
+
+ newsGrid.innerHTML = news.map(article => `
+
+
+
+ ${article.type}
+ ${article.date}
+
+
+
${article.title}
+
${article.summary}
+
+
+ `).join('');
+}
+
+function displayFullNews(news) {
+ const allNewsGrid = document.getElementById('allNewsGrid');
+ if (!allNewsGrid) return;
+
+ allNewsGrid.innerHTML = news.map(article => `
+
+
+
+ ${article.type}
+ ${article.date}
+
+
+
${article.title}
+
${article.summary}
+
+
+ `).join('');
+}
+
+function showErrorNews() {
+ const newsGrid = document.getElementById('newsGrid');
+ if (newsGrid) {
+ newsGrid.innerHTML = `
+
+
+ Unable to load news
+
+ `;
+ }
+}
+
+function openNewsDetails(newsId) {
+ const article = newsData.find(item => item.id === newsId);
+ if (article && article.url) {
+ openNewsArticle(article.url);
+ } else {
+ console.log('Opening news article:', article);
+ }
+}
+
+function openNewsArticle(url) {
+ if (url && url !== '#' && window.electronAPI && window.electronAPI.openExternal) {
+ window.electronAPI.openExternal(url);
+ }
+}
+
+function formatDate(dateString) {
+ if (!dateString) return 'RECENTLY';
+
+ const date = new Date(dateString);
+ const now = new Date();
+ const diffTime = Math.abs(now - date);
+ const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
+
+ if (diffDays === 1) return '1 DAY AGO';
+ if (diffDays < 7) return `${diffDays} DAYS AGO`;
+ if (diffDays < 30) return `${Math.ceil(diffDays / 7)} WEEKS AGO`;
+ return date.toLocaleDateString();
+}
+
+window.openNewsDetails = openNewsDetails;
+window.navigateToPage = (page) => {
+ if (window.LauncherUI) {
+ window.LauncherUI.showPage(`${page}-page`);
+ window.LauncherUI.setActiveNav(page);
+ }
+};
+
+document.addEventListener('DOMContentLoaded', loadNews);
diff --git a/GUI/js/players.js b/GUI/js/players.js
new file mode 100644
index 0000000..84efc0a
--- /dev/null
+++ b/GUI/js/players.js
@@ -0,0 +1,154 @@
+
+const API_URL = 'http://3.10.208.30/api';
+let updateInterval = null;
+let currentUserId = null;
+
+export async function initPlayersCounter() {
+ setupPlayersCounter();
+
+ if (window.electronAPI && window.electronAPI.getUserId) {
+ currentUserId = await window.electronAPI.getUserId();
+ } else {
+ console.error('Electron API not available');
+ return;
+ }
+
+ let username = 'Player';
+ if (window.electronAPI.loadUsername) {
+ const savedUsername = await window.electronAPI.loadUsername();
+ if (savedUsername) username = savedUsername;
+ }
+
+ await registerPlayer(username, currentUserId);
+
+ await fetchPlayerStats();
+ startAutoUpdate();
+}
+
+function setupPlayersCounter() {
+ const counterElement = document.getElementById('playersOnlineCounter');
+ if (!counterElement) {
+ console.warn('Players counter element not found');
+ }
+}
+
+async function fetchPlayerStats() {
+ try {
+ const response = await fetch(`${API_URL}/players/stats`);
+
+ if (!response.ok) {
+ throw new Error(`API error: ${response.status}`);
+ }
+
+ const data = await response.json();
+ updateCounterDisplay(data);
+ } catch (error) {
+ console.error('Error fetching player stats:', error);
+ updateCounterDisplay({ online: 0, peak: 0 });
+ }
+}
+
+function updateCounterDisplay(stats) {
+ const counterElement = document.getElementById('playersOnlineCounter');
+ const onlineCount = document.getElementById('onlineCount');
+
+ if (onlineCount) {
+ onlineCount.textContent = stats.online || 0;
+ }
+
+ if (counterElement) {
+ counterElement.classList.add('updated');
+ setTimeout(() => {
+ counterElement.classList.remove('updated');
+ }, 300);
+ }
+}
+
+async function registerPlayer(username, userId) {
+ try {
+ const response = await fetch(`${API_URL}/players/register`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({ username, userId })
+ });
+
+ if (!response.ok) {
+ throw new Error(`Failed to register player: ${response.status}`);
+ }
+
+ const data = await response.json();
+ currentUserId = userId;
+ console.log('Player registered:', data);
+
+ await fetchPlayerStats();
+
+ return data;
+ } catch (error) {
+ console.error('Error registering player:', error);
+ return null;
+ }
+}
+
+async function unregisterPlayer(userId) {
+ try {
+ const response = await fetch(`${API_URL}/players/unregister`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({ userId })
+ });
+
+ if (!response.ok) {
+ throw new Error(`Failed to unregister player: ${response.status}`);
+ }
+
+ const data = await response.json();
+ currentUserId = null;
+ console.log('Player unregistered:', data);
+
+ await fetchPlayerStats();
+
+ return data;
+ } catch (error) {
+ console.error('Error unregistering player:', error);
+ return null;
+ }
+}
+
+function startAutoUpdate() {
+ updateInterval = setInterval(async () => {
+ await fetchPlayerStats();
+
+ if (currentUserId) {
+ const username = window.LauncherState?.username || 'Player';
+ await registerPlayer(username, currentUserId);
+ }
+ }, 3000);
+}
+
+function stopAutoUpdate() {
+ if (updateInterval) {
+ clearInterval(updateInterval);
+ updateInterval = null;
+ }
+}
+
+
+window.addEventListener('beforeunload', () => {
+ if (currentUserId) {
+ const data = JSON.stringify({ userId: currentUserId });
+ navigator.sendBeacon(`${API_URL}/players/unregister`, data);
+ }
+ stopAutoUpdate();
+});
+
+window.PlayersAPI = {
+ register: registerPlayer,
+ unregister: unregisterPlayer,
+ fetchStats: fetchPlayerStats
+};
+
+document.addEventListener('DOMContentLoaded', initPlayersCounter);
diff --git a/GUI/js/settings.js b/GUI/js/settings.js
new file mode 100644
index 0000000..e987871
--- /dev/null
+++ b/GUI/js/settings.js
@@ -0,0 +1,143 @@
+
+let customJavaCheck;
+let customJavaOptions;
+let customJavaPath;
+let browseJavaBtn;
+let settingsPlayerName;
+
+export function initSettings() {
+ setupSettingsElements();
+ loadAllSettings();
+}
+
+function setupSettingsElements() {
+ customJavaCheck = document.getElementById('customJavaCheck');
+ customJavaOptions = document.getElementById('customJavaOptions');
+ customJavaPath = document.getElementById('customJavaPath');
+ browseJavaBtn = document.getElementById('browseJavaBtn');
+ settingsPlayerName = document.getElementById('settingsPlayerName');
+
+ if (customJavaCheck) {
+ customJavaCheck.addEventListener('change', toggleCustomJava);
+ }
+
+ if (browseJavaBtn) {
+ browseJavaBtn.addEventListener('click', browseJavaPath);
+ }
+
+ if (settingsPlayerName) {
+ settingsPlayerName.addEventListener('change', savePlayerName);
+ }
+}
+
+function toggleCustomJava() {
+ if (!customJavaOptions) return;
+
+ if (customJavaCheck && customJavaCheck.checked) {
+ customJavaOptions.style.display = 'block';
+ } else {
+ customJavaOptions.style.display = 'none';
+ if (customJavaPath) customJavaPath.value = '';
+ saveCustomJavaPath('');
+ }
+}
+
+async function browseJavaPath() {
+ try {
+ if (window.electronAPI && window.electronAPI.browseJavaPath) {
+ const result = await window.electronAPI.browseJavaPath();
+ if (result && result.filePaths && result.filePaths.length > 0) {
+ const selectedPath = result.filePaths[0];
+ if (customJavaPath) {
+ customJavaPath.value = selectedPath;
+ }
+ await saveCustomJavaPath(selectedPath);
+ }
+ }
+ } catch (error) {
+ console.error('Error browsing Java path:', error);
+ }
+}
+
+async function saveCustomJavaPath(path) {
+ try {
+ if (window.electronAPI && window.electronAPI.saveJavaPath) {
+ await window.electronAPI.saveJavaPath(path);
+ }
+ } catch (error) {
+ console.error('Error saving custom Java path:', error);
+ }
+}
+
+async function loadCustomJavaPath() {
+ try {
+ if (window.electronAPI && window.electronAPI.loadJavaPath) {
+ const savedPath = await window.electronAPI.loadJavaPath();
+ if (savedPath && savedPath.trim()) {
+ if (customJavaPath) {
+ customJavaPath.value = savedPath;
+ }
+ if (customJavaCheck) {
+ customJavaCheck.checked = true;
+ }
+ if (customJavaOptions) {
+ customJavaOptions.style.display = 'block';
+ }
+ }
+ }
+ } catch (error) {
+ console.error('Error loading custom Java path:', error);
+ }
+}
+
+async function savePlayerName() {
+ try {
+ if (window.electronAPI && window.electronAPI.saveUsername && settingsPlayerName) {
+ const playerName = settingsPlayerName.value.trim() || 'Player';
+ await window.electronAPI.saveUsername(playerName);
+ }
+ } catch (error) {
+ console.error('Error saving player name:', error);
+ }
+}
+
+async function loadPlayerName() {
+ try {
+ if (window.electronAPI && window.electronAPI.loadUsername && settingsPlayerName) {
+ const savedName = await window.electronAPI.loadUsername();
+ if (savedName) {
+ settingsPlayerName.value = savedName;
+ }
+ }
+ } catch (error) {
+ console.error('Error loading player name:', error);
+ }
+}
+
+async function loadAllSettings() {
+ await loadCustomJavaPath();
+ await loadPlayerName();
+}
+
+
+export function getCurrentJavaPath() {
+ if (customJavaCheck && customJavaCheck.checked && customJavaPath) {
+ return customJavaPath.value.trim();
+ }
+ return '';
+}
+
+
+export function getCurrentPlayerName() {
+ if (settingsPlayerName && settingsPlayerName.value.trim()) {
+ return settingsPlayerName.value.trim();
+ }
+ return 'Player';
+}
+
+document.addEventListener('DOMContentLoaded', initSettings);
+
+window.SettingsAPI = {
+ getCurrentJavaPath,
+ getCurrentPlayerName
+};
\ No newline at end of file
diff --git a/GUI/js/ui.js b/GUI/js/ui.js
new file mode 100644
index 0000000..9f12233
--- /dev/null
+++ b/GUI/js/ui.js
@@ -0,0 +1,469 @@
+
+let progressOverlay;
+let progressBar;
+let progressBarFill;
+let progressText;
+let progressPercent;
+let progressSpeed;
+let progressSize;
+
+function showPage(pageId) {
+ const pages = document.querySelectorAll('.page');
+ pages.forEach(page => {
+ if (page.id === pageId) {
+ page.classList.add('active');
+ page.style.display = '';
+ } else {
+ page.classList.remove('active');
+ page.style.display = 'none';
+ }
+ });
+}
+
+function setActiveNav(page) {
+ const navItems = document.querySelectorAll('.nav-item');
+ navItems.forEach(item => {
+ if (item.getAttribute('data-page') === page) {
+ item.classList.add('active');
+ } else {
+ item.classList.remove('active');
+ }
+ });
+}
+
+function handleNavigation() {
+ const navItems = document.querySelectorAll('.nav-item');
+ navItems.forEach(item => {
+ item.addEventListener('click', () => {
+ const page = item.getAttribute('data-page');
+ showPage(`${page}-page`);
+ setActiveNav(page);
+ });
+ });
+}
+
+function setupWindowControls() {
+ const minimizeBtn = document.querySelector('.window-controls .minimize');
+ const closeBtn = document.querySelector('.window-controls .close');
+
+ const windowControls = document.querySelector('.window-controls');
+ const header = document.querySelector('.header');
+
+ if (windowControls) {
+ windowControls.style.pointerEvents = 'auto';
+ windowControls.style.zIndex = '10000';
+ }
+
+ if (header) {
+ header.style.webkitAppRegion = 'drag';
+ if (windowControls) {
+ windowControls.style.webkitAppRegion = 'no-drag';
+ }
+ }
+
+ if (window.electronAPI) {
+ if (minimizeBtn) {
+ minimizeBtn.onclick = (e) => {
+ e.stopPropagation();
+ window.electronAPI.minimizeWindow();
+ };
+ }
+ if (closeBtn) {
+ closeBtn.onclick = (e) => {
+ e.stopPropagation();
+ window.electronAPI.closeWindow();
+ };
+ }
+ }
+}
+
+function showLauncherOrInstall(isInstalled) {
+ const launcher = document.getElementById('launcher-container');
+ const install = document.getElementById('install-page');
+ const sidebar = document.querySelector('.sidebar');
+ const gameTitle = document.querySelector('.game-title-section');
+
+ if (isInstalled) {
+ if (launcher) launcher.style.display = '';
+ if (install) install.style.display = 'none';
+ if (sidebar) sidebar.style.pointerEvents = 'auto';
+ if (gameTitle) gameTitle.style.display = '';
+ showPage('play-page');
+ setActiveNav('play');
+ } else {
+ if (launcher) launcher.style.display = 'none';
+ if (install) {
+ install.style.display = '';
+ install.classList.add('active');
+ }
+ if (sidebar) sidebar.style.pointerEvents = 'none';
+ if (gameTitle) gameTitle.style.display = 'none';
+ const pages = document.querySelectorAll('#launcher-container .page');
+ pages.forEach(page => page.classList.remove('active'));
+ }
+}
+
+function setupSidebarLogo() {
+ const logo = document.querySelector('.sidebar-logo img');
+ if (logo) {
+ logo.addEventListener('click', () => {
+ showPage('play-page');
+ setActiveNav('play');
+ });
+ }
+}
+
+function showProgress() {
+ if (progressOverlay) {
+ progressOverlay.style.display = 'block';
+ setTimeout(() => {
+ progressOverlay.style.opacity = '1';
+ progressOverlay.style.transform = 'translateY(0)';
+ }, 10);
+ }
+}
+
+function hideProgress() {
+ if (progressOverlay) {
+ progressOverlay.style.opacity = '0';
+ progressOverlay.style.transform = 'translateY(20px)';
+ setTimeout(() => {
+ progressOverlay.style.display = 'none';
+ }, 300);
+ }
+}
+
+function updateProgress(data) {
+ if (data.message && progressText) {
+ progressText.textContent = data.message;
+ }
+
+ if (data.percent !== null && data.percent !== undefined) {
+ const percent = Math.min(100, Math.max(0, Math.round(data.percent)));
+ if (progressPercent) progressPercent.textContent = `${percent}%`;
+ if (progressBarFill) progressBarFill.style.width = `${percent}%`;
+ if (progressBar) progressBar.style.width = `${percent}%`;
+ }
+
+ if (data.speed && data.downloaded && data.total) {
+ const speedMB = (data.speed / 1024 / 1024).toFixed(2);
+ const downloadedMB = (data.downloaded / 1024 / 1024).toFixed(2);
+ const totalMB = (data.total / 1024 / 1024).toFixed(2);
+ if (progressSpeed) progressSpeed.textContent = `${speedMB} MB/s`;
+ if (progressSize) progressSize.textContent = `${downloadedMB} / ${totalMB} MB`;
+ }
+}
+
+function setupAnimations() {
+ document.body.style.opacity = '0';
+ document.body.style.transform = 'translateY(20px)';
+
+ setTimeout(() => {
+ document.body.style.transition = 'all 0.6s ease';
+ document.body.style.opacity = '1';
+ document.body.style.transform = 'translateY(0)';
+ }, 100);
+
+ const style = document.createElement('style');
+ style.textContent = `
+ @keyframes fadeInUp {
+ from {
+ opacity: 0;
+ transform: translateY(20px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+ }
+ `;
+ document.head.appendChild(style);
+}
+
+function setupFirstLaunchHandlers() {
+ console.log('Setting up first launch handlers...');
+
+ window.electronAPI.onFirstLaunchUpdate((data) => {
+ console.log('Received first launch update event:', data);
+ showFirstLaunchUpdateDialog(data);
+ });
+
+ window.electronAPI.onFirstLaunchWelcome(() => {
+ });
+
+ window.electronAPI.onFirstLaunchProgress((data) => {
+ showProgress();
+ updateProgress(data);
+ });
+
+ window.electronAPI.onLockPlayButton((locked) => {
+ lockPlayButton(locked);
+ });
+}
+
+function showFirstLaunchUpdateDialog(data) {
+ console.log('Creating first launch modal...');
+
+ const existingModal = document.querySelector('.first-launch-modal-overlay');
+ if (existingModal) {
+ existingModal.remove();
+ }
+
+ const modalOverlay = document.createElement('div');
+ modalOverlay.className = 'first-launch-modal-overlay';
+ modalOverlay.style.cssText = `
+ position: fixed !important;
+ top: 0 !important;
+ left: 0 !important;
+ right: 0 !important;
+ bottom: 0 !important;
+ background: rgba(0, 0, 0, 0.95) !important;
+ backdrop-filter: blur(10px) !important;
+ z-index: 999999 !important;
+ display: flex !important;
+ align-items: center !important;
+ justify-content: center !important;
+ pointer-events: all !important;
+ `;
+
+ const modalDialog = document.createElement('div');
+ modalDialog.className = 'first-launch-modal-dialog';
+ modalDialog.style.cssText = `
+ background: #1a1a1a !important;
+ border-radius: 12px !important;
+ padding: 0 !important;
+ width: 500px !important;
+ max-width: 90vw !important;
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.8) !important;
+ border: 1px solid rgba(147, 51, 234, 0.5) !important;
+ overflow: hidden !important;
+ animation: modalSlideIn 0.3s ease-out !important;
+ `;
+
+ modalDialog.innerHTML = `
+
+
+ 🔄 Game Update Required
+
+
+
+
+
+ An existing Hytale installation has been detected and must be updated to the latest version.
+
+
+ ✅ Your game saves and settings will be preserved
+
+
+
+
+
+ 📁 Location: ${data.existingGame.installPath}
+
+
+ 💾 UserData: ${data.existingGame.hasUserData ? '✅ Found (will be preserved)' : '❌ Not found'}
+
+
+
+
+
+ ⚠️ This update is mandatory and cannot be skipped
+
+
+
+
+
+
+ `;
+
+ modalOverlay.appendChild(modalDialog);
+
+ modalOverlay.onclick = (e) => {
+ if (e.target === modalOverlay) {
+ e.preventDefault();
+ e.stopPropagation();
+ return false;
+ }
+ };
+
+ document.addEventListener('keydown', function preventEscape(e) {
+ if (e.key === 'Escape') {
+ e.preventDefault();
+ e.stopPropagation();
+ return false;
+ }
+ });
+
+ document.body.appendChild(modalOverlay);
+
+ const updateBtn = document.getElementById('updateGameBtn');
+ updateBtn.onclick = () => {
+ acceptFirstLaunchUpdate();
+ };
+
+ window.firstLaunchExistingGame = data.existingGame;
+
+ console.log('First launch modal created and displayed');
+}
+
+function lockPlayButton(locked) {
+ const playButton = document.getElementById('homePlayBtn');
+
+ if (!playButton) {
+ console.warn('Play button not found');
+ return;
+ }
+
+ if (locked) {
+ playButton.style.opacity = '0.5';
+ playButton.style.pointerEvents = 'none';
+ playButton.style.cursor = 'not-allowed';
+ playButton.setAttribute('data-locked', 'true');
+
+ const spanElement = playButton.querySelector('span');
+ if (spanElement) {
+ if (!playButton.getAttribute('data-original-text')) {
+ playButton.setAttribute('data-original-text', spanElement.textContent);
+ }
+ spanElement.textContent = 'CHECKING...';
+ }
+
+ console.log('Play button locked');
+ } else {
+ playButton.style.opacity = '';
+ playButton.style.pointerEvents = '';
+ playButton.style.cursor = '';
+ playButton.removeAttribute('data-locked');
+
+ const spanElement = playButton.querySelector('span');
+ const originalText = playButton.getAttribute('data-original-text');
+ if (spanElement && originalText) {
+ spanElement.textContent = originalText;
+ playButton.removeAttribute('data-original-text');
+ }
+
+ console.log('Play button unlocked');
+ }
+}
+
+
+
+async function acceptFirstLaunchUpdate() {
+ const existingGame = window.firstLaunchExistingGame;
+
+ if (!existingGame) {
+ showNotification('Error: Game data not found', 'error');
+ return;
+ }
+
+ const modal = document.querySelector('.first-launch-modal-overlay');
+ if (modal) {
+ modal.style.pointerEvents = 'none';
+ const btn = document.getElementById('updateGameBtn');
+ if (btn) {
+ btn.style.opacity = '0.5';
+ btn.style.cursor = 'not-allowed';
+ btn.textContent = '🔄 Updating...';
+ }
+ }
+
+ try {
+ showProgress();
+ updateProgress({ message: 'Starting mandatory game update...', percent: 0 });
+
+ const result = await window.electronAPI.acceptFirstLaunchUpdate(existingGame);
+
+ window.electronAPI.markAsLaunched && window.electronAPI.markAsLaunched();
+
+ if (modal) {
+ modal.remove();
+ }
+
+ lockPlayButton(false);
+
+ if (result.success) {
+ hideProgress();
+ showNotification('Game updated successfully! 🎉', 'success');
+ } else {
+ hideProgress();
+ showNotification(`Update failed: ${result.error}`, 'error');
+ }
+ } catch (error) {
+ if (modal) {
+ modal.remove();
+ }
+ lockPlayButton(false);
+ hideProgress();
+ showNotification(`Update error: ${error.message}`, 'error');
+ }
+}
+
+function dismissFirstLaunchDialog() {
+ const modal = document.querySelector('.first-launch-modal-overlay');
+ if (modal) {
+ modal.remove();
+ }
+
+ lockPlayButton(false);
+ window.electronAPI.markAsLaunched && window.electronAPI.markAsLaunched();
+}
+
+function showNotification(message, type = 'info') {
+ const notification = document.createElement('div');
+ notification.className = `notification notification-${type}`;
+ notification.textContent = message;
+
+ document.body.appendChild(notification);
+
+ setTimeout(() => {
+ notification.classList.add('show');
+ }, 100);
+
+ setTimeout(() => {
+ notification.remove();
+ }, 5000);
+}
+
+function setupUI() {
+ progressOverlay = document.getElementById('progressOverlay');
+ progressBar = document.getElementById('progressBar');
+ progressBarFill = document.getElementById('progressBarFill');
+ progressText = document.getElementById('progressText');
+ progressPercent = document.getElementById('progressPercent');
+ progressSpeed = document.getElementById('progressSpeed');
+ progressSize = document.getElementById('progressSize');
+
+ lockPlayButton(true);
+
+ handleNavigation();
+ setupWindowControls();
+ setupSidebarLogo();
+ setupAnimations();
+ setupFirstLaunchHandlers();
+
+ document.body.focus();
+}
+
+window.LauncherUI = {
+ showPage,
+ setActiveNav,
+ showLauncherOrInstall,
+ showProgress,
+ hideProgress,
+ updateProgress
+};
+
+document.addEventListener('DOMContentLoaded', setupUI);
diff --git a/GUI/js/update.js b/GUI/js/update.js
new file mode 100644
index 0000000..00393b4
--- /dev/null
+++ b/GUI/js/update.js
@@ -0,0 +1,162 @@
+
+class ClientUpdateManager {
+ constructor() {
+ this.updatePopupVisible = false;
+ this.init();
+ }
+
+ init() {
+ window.electronAPI.onUpdatePopup((updateInfo) => {
+ this.showUpdatePopup(updateInfo);
+ });
+
+ this.checkForUpdatesOnDemand();
+ }
+
+ showUpdatePopup(updateInfo) {
+ if (this.updatePopupVisible) return;
+
+ this.updatePopupVisible = true;
+
+ const popupHTML = `
+
+ `;
+
+ document.body.insertAdjacentHTML('beforeend', popupHTML);
+
+ this.blockInterface();
+
+ const downloadBtn = document.getElementById('update-download-btn');
+ if (downloadBtn) {
+ downloadBtn.addEventListener('click', async (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+
+ downloadBtn.disabled = true;
+ downloadBtn.innerHTML = 'Opening GitHub...';
+
+ try {
+ await window.electronAPI.openDownloadPage();
+ console.log('✅ Download page opened, launcher will close...');
+
+ downloadBtn.innerHTML = 'Launcher closing...';
+
+ } catch (error) {
+ console.error('❌ Error opening download page:', error);
+ downloadBtn.disabled = false;
+ downloadBtn.innerHTML = 'Download Update';
+ }
+ });
+ }
+
+ const overlay = document.getElementById('update-popup-overlay');
+ if (overlay) {
+ overlay.addEventListener('click', (e) => {
+ if (e.target === overlay) {
+ e.preventDefault();
+ e.stopPropagation();
+ return false;
+ }
+ });
+ }
+
+ console.log('🔔 Update popup displayed with new style');
+ }
+
+ blockInterface() {
+ const mainContent = document.querySelector('.flex.w-full.h-screen');
+ if (mainContent) {
+ mainContent.classList.add('interface-blocked');
+ }
+
+ document.body.classList.add('no-select');
+
+ document.addEventListener('keydown', this.blockKeyEvents.bind(this), true);
+
+ document.addEventListener('contextmenu', this.blockContextMenu.bind(this), true);
+
+ console.log('🚫 Interface blocked for update');
+ }
+
+ blockKeyEvents(event) {
+ if (event.target.closest('#update-popup-overlay')) {
+ if ((event.key === 'Enter' || event.key === ' ') &&
+ event.target.id === 'update-download-btn') {
+ return;
+ }
+ if (event.key !== 'Tab') {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ return;
+ }
+
+ event.preventDefault();
+ event.stopPropagation();
+ event.stopImmediatePropagation();
+ return false;
+ }
+
+ blockContextMenu(event) {
+ if (!event.target.closest('#update-popup-overlay')) {
+ event.preventDefault();
+ event.stopPropagation();
+ return false;
+ }
+ }
+
+ async checkForUpdatesOnDemand() {
+ try {
+ const updateInfo = await window.electronAPI.checkForUpdates();
+ if (updateInfo.updateAvailable) {
+ this.showUpdatePopup(updateInfo);
+ }
+ return updateInfo;
+ } catch (error) {
+ console.error('Error checking for updates:', error);
+ return { updateAvailable: false, error: error.message };
+ }
+ }
+}
+
+document.addEventListener('DOMContentLoaded', () => {
+ window.updateManager = new ClientUpdateManager();
+});
+
+window.ClientUpdateManager = ClientUpdateManager;
\ No newline at end of file