mirror of
https://github.com/amiayweb/Hytale-F2P.git
synced 2026-02-26 17:11:45 -03:00
Compare commits
227 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
46ed4cb2be | ||
|
|
6369fd7669 | ||
|
|
244fec0c77 | ||
|
|
ebc11de314 | ||
|
|
90448e200e | ||
|
|
e5b44341f1 | ||
|
|
ae6a7db80a | ||
|
|
48395fbff3 | ||
|
|
aae90a72e8 | ||
|
|
a6c61aef68 | ||
|
|
31653a37a7 | ||
|
|
1cb08f029a | ||
|
|
6a66ed831c | ||
|
|
78bb10588d | ||
|
|
d27663a1ce | ||
|
|
2db7d606bd | ||
|
|
39c12c0591 | ||
|
|
094bb938fc | ||
|
|
9c9b71bd4c | ||
|
|
c4bb15ce91 | ||
|
|
8719cd3138 | ||
|
|
611d436085 | ||
|
|
d5cc0868e9 | ||
|
|
da186333cb | ||
|
|
ae375f9b6e | ||
|
|
faf21b830b | ||
|
|
6f10b1390d | ||
|
|
c4a32ce1e0 | ||
|
|
7a9a67d8e8 | ||
|
|
4c854953fe | ||
|
|
4cd0539ce3 | ||
|
|
fa2d451f90 | ||
|
|
a4faa7138c | ||
|
|
d285dc7517 | ||
|
|
ceadd69eea | ||
|
|
6f0dd27c1d | ||
|
|
ba95187ee6 | ||
|
|
9e54e07b22 | ||
|
|
a8e7e57c86 | ||
|
|
d1ab58d51b | ||
|
|
8781025df9 | ||
|
|
81c52e9507 | ||
|
|
45314620e4 | ||
|
|
43d5d20351 | ||
|
|
72b4e0cba8 | ||
|
|
25d5131a7b | ||
|
|
ad3c73563d | ||
|
|
f0f19f690f | ||
|
|
b27860a655 | ||
|
|
9788d0e496 | ||
|
|
2a5780c2d4 | ||
|
|
8263b3f99b | ||
|
|
db56ef1624 | ||
|
|
35f900d6ab | ||
|
|
e1d1383ab7 | ||
|
|
8326deddb1 | ||
|
|
b11b78f7dc | ||
|
|
62a2d76e4a | ||
|
|
0ca8b4e02f | ||
|
|
c6a9d0ae07 | ||
|
|
f438d6c8e0 | ||
|
|
f07e4a2004 | ||
|
|
131580d3ba | ||
|
|
084347db03 | ||
|
|
589c5b457f | ||
|
|
790d4d3f29 | ||
|
|
52313910dc | ||
|
|
a3f4d8e9d8 | ||
|
|
86d617a4d3 | ||
|
|
0a97ac95fc | ||
|
|
b94b45681b | ||
|
|
4086612e9d | ||
|
|
e7fca5a4c7 | ||
|
|
e7bd20a1ec | ||
|
|
151b017653 | ||
|
|
da3e14c434 | ||
|
|
6302734eeb | ||
|
|
07191860be | ||
|
|
2f767f191e | ||
|
|
de9c7d81f5 | ||
|
|
4c3277392e | ||
|
|
f287cb55b9 | ||
|
|
d87db04653 | ||
|
|
67aa41aefe | ||
|
|
bd1dd146a9 | ||
|
|
c8d7707b70 | ||
|
|
127c38f98b | ||
|
|
f974d9c767 | ||
|
|
7e4a45e466 | ||
|
|
ea21fb15d6 | ||
|
|
3d54cea9e7 | ||
|
|
9f43a32779 | ||
|
|
9c8a12f25c | ||
|
|
a7d0523186 | ||
|
|
a6f716c61b | ||
|
|
ca8ed171d1 | ||
|
|
679799c074 | ||
|
|
87b168dd4c | ||
|
|
679f065e24 | ||
|
|
ecae7d2ee5 | ||
|
|
fa50fec34d | ||
|
|
c900129c1f | ||
|
|
6b75858515 | ||
|
|
61bcdf9413 | ||
|
|
411d7d8aaf | ||
|
|
8a87c7c4d9 | ||
|
|
34f93e962b | ||
|
|
d8393543df | ||
|
|
b62ffc126e | ||
|
|
3579d82776 | ||
|
|
b5c6c38d92 | ||
|
|
f932462578 | ||
|
|
e005b4293b | ||
|
|
e43897f816 | ||
|
|
3983fdb1bc | ||
|
|
b46ce93af7 | ||
|
|
2a87acfe46 | ||
|
|
a2e2d5e5fd | ||
|
|
34143d9872 | ||
|
|
08c2218cf8 | ||
|
|
032418b7f7 | ||
|
|
fc05725a43 | ||
|
|
203a56879f | ||
|
|
7a0065ea2b | ||
|
|
ac08eb50ff | ||
|
|
70fe4203ef | ||
|
|
f433120084 | ||
|
|
f4099acbed | ||
|
|
da843257c1 | ||
|
|
e4576042be | ||
|
|
71974e031f | ||
|
|
5bd52f09db | ||
|
|
1ba6b22b74 | ||
|
|
a1bc88b754 | ||
|
|
24c2371b50 | ||
|
|
4c6e1a616e | ||
|
|
b54eb4e834 | ||
|
|
a1c74e4175 | ||
|
|
260e6c1126 | ||
|
|
6eb628559b | ||
|
|
052b5dc7dc | ||
|
|
7e9b5046df | ||
|
|
204d6b21f6 | ||
|
|
740d516cfe | ||
|
|
ce052add0d | ||
|
|
d7a904c641 | ||
|
|
d5d2f60c97 | ||
|
|
61433bfeea | ||
|
|
9eb5d1759c | ||
|
|
68d697576a | ||
|
|
a8da559e93 | ||
|
|
828d05ca33 | ||
|
|
75f9403888 | ||
|
|
b61c94d348 | ||
|
|
c0109575d6 | ||
|
|
2a024b61dd | ||
|
|
1c39e8e4c6 | ||
|
|
753bd4fd61 | ||
|
|
cefb4c5575 | ||
|
|
1c779e0e41 | ||
|
|
bb474fe233 | ||
|
|
917f5f455b | ||
|
|
1dd42bdc79 | ||
|
|
7cfe3edd32 | ||
|
|
eb22758ab9 | ||
|
|
42fd51486a | ||
|
|
9ef05e8322 | ||
|
|
4ac12e0e24 | ||
|
|
72a151930e | ||
|
|
a9644b8c64 | ||
|
|
9fc238e103 | ||
|
|
b62e94a415 | ||
|
|
3e82e8fadb | ||
|
|
a355133ccf | ||
|
|
ff5acb5278 | ||
|
|
5d75ca80aa | ||
|
|
281aa6fcde | ||
|
|
158d0af820 | ||
|
|
503f304704 | ||
|
|
234e1e1008 | ||
|
|
d002e831cd | ||
|
|
048f2040f1 | ||
|
|
b05aeef66d | ||
|
|
30265549cf | ||
|
|
5a3efba1d6 | ||
|
|
479f24e86f | ||
|
|
611b7a7084 | ||
|
|
e472435927 | ||
|
|
f40d0105df | ||
|
|
96db9adf68 | ||
|
|
a0f49f126c | ||
|
|
f8333c09cd | ||
|
|
bcf0326763 | ||
|
|
0a5c3db710 | ||
|
|
905a9d754c | ||
|
|
99f032e9ab | ||
|
|
5e3506a9ac | ||
|
|
f2a05d2079 | ||
|
|
cece338609 | ||
|
|
261582a882 | ||
|
|
0f0f0fa308 | ||
|
|
5e6a07f0a6 | ||
|
|
713377fdc6 | ||
|
|
2671a59f38 | ||
|
|
8e9af9c768 | ||
|
|
e962a8880d | ||
|
|
c2d5536dd0 | ||
|
|
727be2ca5c | ||
|
|
64892c81e9 | ||
|
|
fffc730788 | ||
|
|
c475ec7879 | ||
|
|
7efa0d07b0 | ||
|
|
21f8527ed4 | ||
|
|
300616ba82 | ||
|
|
9fdd6f1f44 | ||
|
|
f0b2342c71 | ||
|
|
7dbc900338 | ||
|
|
bc31f58c9c | ||
|
|
7e5a1577a3 | ||
|
|
84f0c0ba71 | ||
|
|
7ede6c2f27 | ||
|
|
be1a24a992 | ||
|
|
9a751958b0 | ||
|
|
9fcf603e08 | ||
|
|
4bc1661587 | ||
|
|
512a53aee7 | ||
|
|
cabb5a57d2 |
61
.github/README1.md
vendored
61
.github/README1.md
vendored
@@ -1,61 +0,0 @@
|
||||
# GitHub Actions
|
||||
|
||||
## Build and Release Workflow
|
||||
|
||||
The `release.yml` workflow automatically builds the launcher for all platforms.
|
||||
|
||||
### Triggers
|
||||
|
||||
| Trigger | Builds | Creates Release |
|
||||
|---------|--------|-----------------|
|
||||
| Push to `main` | Yes | No |
|
||||
| Push tag `v*` | Yes | Yes |
|
||||
| Manual dispatch | Yes | No |
|
||||
|
||||
### Platforms
|
||||
|
||||
All builds run in parallel:
|
||||
|
||||
- **Linux** (ubuntu-latest): AppImage, deb
|
||||
- **Windows** (windows-latest): NSIS installer, portable exe
|
||||
- **macOS** (macos-latest): Universal DMG (Intel + Apple Silicon)
|
||||
|
||||
### Creating a Release
|
||||
|
||||
1. Update version in `package.json`
|
||||
2. Commit and push to `main`
|
||||
3. Create and push a version tag:
|
||||
|
||||
```bash
|
||||
git tag v2.0.1
|
||||
git push origin v2.0.1
|
||||
```
|
||||
|
||||
The workflow will:
|
||||
1. Build all platforms in parallel
|
||||
2. Upload artifacts to GitHub Release
|
||||
3. Generate release notes automatically
|
||||
|
||||
### Build Artifacts
|
||||
|
||||
After each build, artifacts are available in the Actions tab for 90 days:
|
||||
|
||||
- `linux-builds`: `.AppImage`, `.deb`
|
||||
- `windows-builds`: `.exe`
|
||||
- `macos-builds`: `.dmg`, `.zip`, `latest-mac.yml`
|
||||
|
||||
### Local Development
|
||||
|
||||
Build locally for your platform:
|
||||
|
||||
```bash
|
||||
npm run build:linux
|
||||
npm run build:win
|
||||
npm run build:mac
|
||||
```
|
||||
|
||||
Or build all platforms (requires appropriate OS):
|
||||
|
||||
```bash
|
||||
npm run build:all
|
||||
```
|
||||
92
.github/workflows/release.yml
vendored
92
.github/workflows/release.yml
vendored
@@ -1,92 +0,0 @@
|
||||
name: Build and Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
tags:
|
||||
- 'v*'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build-linux:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '22'
|
||||
cache: 'npm'
|
||||
- run: npm install
|
||||
- run: npm ci
|
||||
- run: npx electron-builder --linux --publish never
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: linux-builds
|
||||
path: |
|
||||
dist/*.AppImage
|
||||
dist/*.deb
|
||||
|
||||
build-windows:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '22'
|
||||
cache: 'npm'
|
||||
- run: npm install
|
||||
- run: npm ci
|
||||
- run: npx electron-builder --win --publish never
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: windows-builds
|
||||
path: |
|
||||
dist/*.exe
|
||||
|
||||
build-macos:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '22'
|
||||
cache: 'npm'
|
||||
- run: npm install
|
||||
- run: npm ci
|
||||
- run: npx electron-builder --mac --publish never
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: macos-builds
|
||||
path: |
|
||||
dist/*.dmg
|
||||
dist/*.zip
|
||||
dist/latest-mac.yml
|
||||
|
||||
release:
|
||||
needs: [build-linux, build-windows, build-macos]
|
||||
runs-on: ubuntu-latest
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: artifacts
|
||||
|
||||
- name: Display structure of downloaded files
|
||||
run: ls -R artifacts
|
||||
|
||||
- name: Create Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: |
|
||||
artifacts/linux-builds/*
|
||||
artifacts/windows-builds/*
|
||||
artifacts/macos-builds/*
|
||||
generate_release_notes: true
|
||||
draft: false
|
||||
prerelease: false
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,3 +0,0 @@
|
||||
dist/*
|
||||
node_modules/*
|
||||
package-lock.json
|
||||
54
BUILD.md
54
BUILD.md
@@ -1,54 +0,0 @@
|
||||
# Build Instructions
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Install dependencies:
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
## Building
|
||||
|
||||
### Build for current platform:
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Build for specific platform:
|
||||
|
||||
**Windows:**
|
||||
```bash
|
||||
npm run build:win
|
||||
```
|
||||
|
||||
**Linux:**
|
||||
```bash
|
||||
npm run build:linux
|
||||
```
|
||||
|
||||
**macOS:**
|
||||
```bash
|
||||
npm run build:mac
|
||||
```
|
||||
|
||||
### Build for all platforms:
|
||||
```bash
|
||||
npm run build:all
|
||||
```
|
||||
|
||||
## Output
|
||||
|
||||
Built executables will be in the `dist/` directory:
|
||||
|
||||
- **Windows**: `Hytale F2P Launcher Setup.exe` (NSIS installer) and `Hytale F2P Launcher.exe` (portable)
|
||||
- **Linux**: `Hytale F2P Launcher.AppImage` and `Hytale F2P Launcher.deb`
|
||||
- **macOS**: `Hytale F2P Launcher.dmg` and `Hytale F2P Launcher.zip`
|
||||
|
||||
## Notes
|
||||
|
||||
- Icons need to be placed in `build/` directory:
|
||||
- `icon.ico` for Windows
|
||||
- `icon.png` for Linux
|
||||
- `icon.icns` for macOS
|
||||
- To build for macOS on non-Mac systems, you'll need to run it on a Mac or use a CI/CD service
|
||||
|
||||
BIN
GUI/icon.png
BIN
GUI/icon.png
Binary file not shown.
|
Before Width: | Height: | Size: 69 KiB |
423
GUI/index.html
423
GUI/index.html
@@ -1,423 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Hytale F2P Launcher</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body class="bg-black text-white overflow-hidden font-sans select-none" tabindex="-1">
|
||||
<div class="absolute inset-0 z-0">
|
||||
<img src="https://i.imgur.com/Visrk66.png" alt="Background" class="w-full h-full object-cover" />
|
||||
<div class="absolute inset-0 bg-black/60"></div>
|
||||
<div class="absolute inset-0 bg-[url('data:image/svg+xml,%3Csvg viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg"%3E%3Cfilter id="noiseFilter"%3E%3CfeTurbulence type="fractalNoise" baseFrequency="0.65" numOctaves="3" stitchTiles="stitch"/%3E%3C/filter%3E%3Crect width="100%25" height="100%25" filter="url(%23noiseFilter)" opacity="0.1"/%3E%3C/svg%3E')] opacity-20"></div>
|
||||
</div>
|
||||
|
||||
<div class="flex w-full h-screen relative z-10">
|
||||
<nav class="sidebar">
|
||||
<div class="sidebar-logo">
|
||||
<img src="./icon.png" alt="Hytale Logo" />
|
||||
|
||||
</div>
|
||||
|
||||
<div class="sidebar-nav">
|
||||
<div class="nav-item active" data-page="play">
|
||||
<i class="fas fa-play"></i>
|
||||
<span class="nav-tooltip">Play</span>
|
||||
</div>
|
||||
<div class="nav-item" data-page="mods">
|
||||
<i class="fas fa-box"></i>
|
||||
<span class="nav-tooltip">Mods</span>
|
||||
</div>
|
||||
<div class="nav-item" data-page="news">
|
||||
<i class="fas fa-newspaper"></i>
|
||||
<span class="nav-tooltip">News</span>
|
||||
</div>
|
||||
<div class="nav-item" data-page="chat">
|
||||
<i class="fas fa-comments"></i>
|
||||
<span class="nav-tooltip">Players Chat</span>
|
||||
</div>
|
||||
<div class="nav-item" data-page="settings">
|
||||
<i class="fas fa-cog"></i>
|
||||
<span class="nav-tooltip">Settings</span>
|
||||
</div>
|
||||
<div class="nav-item" data-page="skins">
|
||||
<i class="fas fa-user"></i>
|
||||
<span class="nav-tooltip">Skins</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</nav>
|
||||
|
||||
<main class="main-content">
|
||||
<header class="header">
|
||||
<div id="playersOnlineCounter" class="players-counter">
|
||||
<i class="fas fa-users"></i>
|
||||
<span class="counter-label">Players:</span>
|
||||
<span id="onlineCount" class="counter-value">0</span>
|
||||
</div>
|
||||
|
||||
<div class="window-controls">
|
||||
<button class="control-btn minimize" onclick="window.electronAPI?.minimizeWindow()">
|
||||
<i class="fas fa-minus"></i>
|
||||
</button>
|
||||
<button class="control-btn close" onclick="window.electronAPI?.closeWindow()">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="game-title-section">
|
||||
<h1 class="game-title">
|
||||
HY<span class="title-accent">TALE</span>
|
||||
</h1>
|
||||
<div class="game-tags">
|
||||
<span class="tag">FREE TO PLAY</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content-pages">
|
||||
<div id="install-page" class="page install-page">
|
||||
<div class="install-content">
|
||||
<div class="install-header">
|
||||
<h1 class="install-title">
|
||||
HYTA<span class="title-accent">LE</span>
|
||||
</h1>
|
||||
<p class="install-subtitle">FREE TO PLAY LAUNCHER</p>
|
||||
</div>
|
||||
|
||||
<div class="install-form">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Player Name</label>
|
||||
<input type="text" id="installPlayerName" placeholder="Enter your name" class="form-input" value="Player" />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="checkbox-group">
|
||||
<input type="checkbox" id="installCustomCheck" class="custom-checkbox">
|
||||
<span class="checkbox-label">Custom Installation</span>
|
||||
</label>
|
||||
|
||||
<div id="installCustomOptions" class="custom-options">
|
||||
<div class="form-subgroup">
|
||||
<label class="form-label">Installation Folder</label>
|
||||
<div class="input-with-button">
|
||||
<input type="text" id="installPath" placeholder="Default location" class="form-input" readonly />
|
||||
<button onclick="browseInstallPath()" class="browse-btn">
|
||||
<i class="fas fa-folder-open"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button id="installBtn" class="install-button" onclick="installGame()">
|
||||
<i class="fas fa-download mr-2"></i>
|
||||
<span id="installText">INSTALL HYTALE</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="launcher-container" class="launcher-container" style="display: none;">
|
||||
<div id="play-page" class="page active">
|
||||
<div class="play-section">
|
||||
<div class="play-content">
|
||||
<div class="play-header">
|
||||
<h2 class="play-title">
|
||||
<i class="fas fa-play-circle mr-2"></i>
|
||||
READY TO PLAY
|
||||
</h2>
|
||||
<p class="play-subtitle">Launch Hytale and enter the adventure</p>
|
||||
</div>
|
||||
|
||||
<button id="homePlayBtn" class="home-play-button" onclick="launch()">
|
||||
<i class="fas fa-play"></i>
|
||||
<span>PLAY HYTALE</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="news-section">
|
||||
<div class="news-header">
|
||||
<h2 class="news-title">
|
||||
<i class="fas fa-star mr-2"></i>
|
||||
LATEST NEWS
|
||||
</h2>
|
||||
<button class="view-all-btn" onclick="navigateToPage('news')">
|
||||
VIEW ALL <i class="fas fa-arrow-right ml-1"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div id="newsGrid" class="news-grid-horizontal"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="mods-page" class="page">
|
||||
<div class="mods-header">
|
||||
<div class="mods-search-container">
|
||||
<i class="fas fa-search"></i>
|
||||
<input type="text" id="modsSearch" placeholder="Search mods..." class="mods-search" />
|
||||
</div>
|
||||
<div class="mods-actions">
|
||||
<button id="myModsBtn" class="mods-btn-primary">
|
||||
<i class="fas fa-box"></i>
|
||||
MY MODS
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="browseModsList" class="mods-browse-container">
|
||||
</div>
|
||||
|
||||
<div class="mods-pagination">
|
||||
<button id="prevPage" class="pagination-btn">
|
||||
<i class="fas fa-chevron-left"></i>
|
||||
PREVIOUS
|
||||
</button>
|
||||
<span class="pagination-info">
|
||||
Page <span id="currentPage">1</span> of <span id="totalPages">1</span>
|
||||
</span>
|
||||
<button id="nextPage" class="pagination-btn">
|
||||
NEXT
|
||||
<i class="fas fa-chevron-right"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="news-page" class="page">
|
||||
<div class="news-header">
|
||||
<h2 class="news-title">
|
||||
<i class="fas fa-newspaper mr-2"></i>
|
||||
ALL NEWS
|
||||
</h2>
|
||||
</div>
|
||||
<div id="allNewsGrid" class="news-grid-full"></div>
|
||||
</div>
|
||||
|
||||
<div id="chat-page" class="page">
|
||||
<div class="chat-container">
|
||||
<div class="chat-header">
|
||||
<h2 class="chat-title">
|
||||
<i class="fas fa-comments mr-2"></i>
|
||||
PLAYERS CHAT
|
||||
</h2>
|
||||
<div class="chat-online-badge">
|
||||
<i class="fas fa-circle"></i>
|
||||
<span id="chatOnlineCount">0</span> online
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="chat-body">
|
||||
<div id="chatMessages" class="chat-messages">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="chat-footer">
|
||||
<div class="chat-input-container">
|
||||
<textarea
|
||||
id="chatInput"
|
||||
class="chat-input"
|
||||
placeholder="Type your message... (Links are automatically censored)"
|
||||
rows="1"
|
||||
maxlength="500"
|
||||
></textarea>
|
||||
<button id="chatSendBtn" class="chat-send-btn">
|
||||
<i class="fas fa-paper-plane"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="chat-footer-info">
|
||||
<span class="chat-char-counter" id="chatCharCounter">0/500</span>
|
||||
<span class="chat-warning-text">
|
||||
<i class="fas fa-shield-alt"></i>
|
||||
Secure chat - Links are censored
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="settings-page" class="page">
|
||||
<div class="settings-container">
|
||||
<div class="settings-header">
|
||||
<h2 class="settings-title">
|
||||
<i class="fas fa-cog mr-2"></i>
|
||||
SETTINGS
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div class="settings-content">
|
||||
<div class="settings-section">
|
||||
<h3 class="settings-section-title">
|
||||
<i class="fas fa-coffee"></i>
|
||||
Java Runtime
|
||||
</h3>
|
||||
|
||||
<div class="settings-option">
|
||||
<label class="settings-checkbox">
|
||||
<input type="checkbox" id="customJavaCheck" />
|
||||
<span class="checkmark"></span>
|
||||
<div class="checkbox-content">
|
||||
<div class="checkbox-title">Use Custom Java Path</div>
|
||||
<div class="checkbox-description">Override the bundled Java runtime with your own installation</div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div id="customJavaOptions" class="custom-java-options" style="display: none;">
|
||||
<div class="settings-input-group">
|
||||
<label class="settings-input-label">Java Executable Path</label>
|
||||
<div class="settings-input-with-button">
|
||||
<input
|
||||
type="text"
|
||||
id="customJavaPath"
|
||||
class="settings-input"
|
||||
placeholder="Select Java path..."
|
||||
readonly
|
||||
/>
|
||||
<button id="browseJavaBtn" class="settings-browse-btn">
|
||||
<i class="fas fa-folder-open"></i>
|
||||
Browse
|
||||
</button>
|
||||
</div>
|
||||
<p class="settings-hint">
|
||||
<i class="fas fa-info-circle"></i>
|
||||
Select the Java installation folder (supports Windows, Mac, Linux)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-section">
|
||||
<h3 class="settings-section-title">
|
||||
<i class="fas fa-gamepad"></i>
|
||||
Game Options
|
||||
</h3>
|
||||
|
||||
<div class="settings-option">
|
||||
<div class="settings-input-group">
|
||||
<label class="settings-input-label">Player Name</label>
|
||||
<input
|
||||
type="text"
|
||||
id="settingsPlayerName"
|
||||
class="settings-input"
|
||||
placeholder="Enter your player name"
|
||||
maxlength="16"
|
||||
/>
|
||||
<p class="settings-hint">
|
||||
<i class="fas fa-user"></i>
|
||||
This name will be used in-game (1-16 characters)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="skins-page" class="page">
|
||||
<div class="placeholder-content">
|
||||
<i class="fas fa-user text-6xl mb-4 text-purple-500"></i>
|
||||
<h2>Skins</h2>
|
||||
<p>Skin customization coming soon...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<div id="myModsModal" class="mods-modal">
|
||||
<div class="mods-modal-content">
|
||||
<div class="mods-modal-header">
|
||||
<h2 class="mods-modal-title">
|
||||
<i class="fas fa-box mr-2"></i>
|
||||
MY MODS
|
||||
</h2>
|
||||
<button id="closeMyModsModal" class="mods-modal-close">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="mods-modal-body">
|
||||
<div id="installedModsList" class="installed-mods-list">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="progressOverlay" class="progress-overlay" style="display: none;">
|
||||
<div class="progress-content">
|
||||
<div class="progress-info">
|
||||
<span id="progressText">Initializing...</span>
|
||||
<span id="progressPercent">0%</span>
|
||||
</div>
|
||||
<div class="progress-bar-container">
|
||||
<div id="progressBarFill" class="progress-bar-fill"></div>
|
||||
</div>
|
||||
<div class="progress-details">
|
||||
<span id="progressSpeed"></span>
|
||||
<span id="progressSize"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="chatUsernameModal" class="chat-username-modal" style="display: none;">
|
||||
<div class="chat-username-modal-content">
|
||||
<div class="chat-username-modal-header">
|
||||
<h2 class="chat-username-modal-title">
|
||||
<i class="fas fa-comments mr-2"></i>
|
||||
Join Chat
|
||||
</h2>
|
||||
</div>
|
||||
<div class="chat-username-modal-body">
|
||||
<p class="chat-username-modal-description">
|
||||
Choose a username to join the Players Chat
|
||||
</p>
|
||||
<div class="chat-username-input-group">
|
||||
<label for="chatUsernameInput" class="chat-username-label">Username</label>
|
||||
<input
|
||||
type="text"
|
||||
id="chatUsernameInput"
|
||||
class="chat-username-input"
|
||||
placeholder="Enter your username..."
|
||||
maxlength="20"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<span class="chat-username-hint">3-20 characters, letters, numbers, - and _ only</span>
|
||||
<span id="chatUsernameError" class="chat-username-error"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chat-username-modal-footer">
|
||||
<button id="chatUsernameCancel" class="chat-username-btn-cancel">
|
||||
<i class="fas fa-times"></i>
|
||||
Cancel
|
||||
</button>
|
||||
<button id="chatUsernameSubmit" class="chat-username-btn-submit">
|
||||
<i class="fas fa-check"></i>
|
||||
Join Chat
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer class="fixed bottom-0 left-0 right-0 z-50 bg-black/80 backdrop-blur-sm px-4 py-2">
|
||||
<div class="flex items-center justify-center text-xs text-gray-400">
|
||||
<span>Made by <a href="https://github.com/amiayweb" target="_blank" class="text-blue-400 hover:text-blue-300 transition-colors">@amiayweb</a></span>
|
||||
<span class="mx-2">|</span>
|
||||
<span>Contributors:
|
||||
<a href="https://github.com/chasem-dev" target="_blank" class="text-blue-400 hover:text-blue-300 transition-colors">@chasem-dev</a>,
|
||||
<a href="https://github.com/crimera" target="_blank" class="text-blue-400 hover:text-blue-300 transition-colors">@crimera</a>,
|
||||
<a href="https://github.com/sanasol" target="_blank" class="text-blue-400 hover:text-blue-300 transition-colors">@sanasol</a>,
|
||||
<a href="https://github.com/Terromur" target="_blank" class="text-blue-400 hover:text-blue-300 transition-colors">@terromur</a>,
|
||||
<a href="https://github.com/ericiskoolbeans" target="_blank" class="text-blue-400 hover:text-blue-300 transition-colors">@ericiskoolbeans</a>
|
||||
</span>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script type="module" src="js/script.js"></script>
|
||||
<script type="module" src="js/update.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
358
GUI/js/chat.js
358
GUI/js/chat.js
@@ -1,358 +0,0 @@
|
||||
|
||||
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 = `
|
||||
<div class="message-header">
|
||||
<span class="message-username">${escapeHtml(username)}</span>
|
||||
<span class="message-time">${time}</span>
|
||||
</div>
|
||||
<div class="message-content">${message}</div>
|
||||
`;
|
||||
|
||||
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 = `
|
||||
<div class="message-content">
|
||||
<i class="fas fa-info-circle"></i> ${escapeHtml(message)}
|
||||
</div>
|
||||
`;
|
||||
|
||||
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()
|
||||
};
|
||||
@@ -1,194 +0,0 @@
|
||||
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();
|
||||
});
|
||||
@@ -1,235 +0,0 @@
|
||||
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);
|
||||
724
GUI/js/mods.js
724
GUI/js/mods.js
@@ -1,724 +0,0 @@
|
||||
|
||||
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 = `
|
||||
<div class=\"empty-installed-mods\">
|
||||
<i class=\"fas fa-box-open\"></i>
|
||||
<h4>No Mods Installed</h4>
|
||||
<p>Add mods from CurseForge or import local files</p>
|
||||
</div>
|
||||
`;
|
||||
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 `
|
||||
<div class="installed-mod-card" data-mod-id="${mod.id}">
|
||||
<div class="installed-mod-icon">
|
||||
<i class="fas fa-cube"></i>
|
||||
</div>
|
||||
|
||||
<div class="installed-mod-info">
|
||||
<div class="installed-mod-header">
|
||||
<h4 class="installed-mod-name">${mod.name}</h4>
|
||||
<span class="installed-mod-version">v${mod.version}</span>
|
||||
</div>
|
||||
<p class="installed-mod-description">${mod.description || 'No description available'}</p>
|
||||
</div>
|
||||
|
||||
<div class="installed-mod-actions">
|
||||
<div class="installed-mod-status ${statusClass}">
|
||||
<i class="fas fa-circle"></i>
|
||||
${statusText}
|
||||
</div>
|
||||
<div class="installed-mod-buttons">
|
||||
<button id="delete-installed-${mod.id}" class="installed-mod-btn-icon" title="Delete mod">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
<button id="toggle-installed-${mod.id}" class="installed-mod-btn-toggle ${toggleBtnClass}">
|
||||
<i class="fas ${toggleIcon}"></i>
|
||||
${toggleBtnText}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
async function loadBrowseMods() {
|
||||
const browseContainer = document.getElementById('browseModsList');
|
||||
if (!browseContainer) return;
|
||||
|
||||
browseContainer.innerHTML = '<div class=\"loading-mods\"><div class=\"loading-spinner\"></div><span>Loading mods from CurseForge...</span></div>';
|
||||
|
||||
try {
|
||||
if (!API_KEY || API_KEY.length < 10) {
|
||||
browseContainer.innerHTML = `
|
||||
<div class=\"empty-browse-mods\">
|
||||
<i class=\"fas fa-key\"></i>
|
||||
<h4>API Key Required</h4>
|
||||
<p>CurseForge API key is needed to browse mods</p>
|
||||
</div>
|
||||
`;
|
||||
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 = `
|
||||
<div class=\"empty-browse-mods error\">
|
||||
<i class=\"fas fa-exclamation-triangle\"></i>
|
||||
<h4>API Error</h4>
|
||||
<p>Failed to load mods from CurseForge</p>
|
||||
<small>${error.message}</small>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
function displayBrowseMods(mods) {
|
||||
const browseContainer = document.getElementById('browseModsList');
|
||||
if (!browseContainer) return;
|
||||
|
||||
if (mods.length === 0) {
|
||||
browseContainer.innerHTML = `
|
||||
<div class=\"empty-browse-mods\">
|
||||
<i class=\"fas fa-search\"></i>
|
||||
<h4>No Mods Found</h4>
|
||||
<p>Try adjusting your search</p>
|
||||
</div>
|
||||
`;
|
||||
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 `
|
||||
<div class=\"mod-card ${isInstalled ? 'installed' : ''}\" data-mod-id=\"${mod.id}\">
|
||||
<div class=\"mod-image\">
|
||||
${mod.thumbnailUrl ?
|
||||
`<img src=\"${mod.thumbnailUrl}\" alt=\"${mod.name}\" onerror=\"this.parentElement.innerHTML='<i class=\\\"fas fa-puzzle-piece\\\"></i>'\">` :
|
||||
`<i class=\"fas fa-puzzle-piece\"></i>`
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class=\"mod-info\">
|
||||
<div class=\"mod-header\">
|
||||
<h3 class=\"mod-name\">${mod.name}</h3>
|
||||
<span class=\"mod-version\">${mod.version}</span>
|
||||
</div>
|
||||
<p class=\"mod-description\">${mod.summary}</p>
|
||||
<div class=\"mod-meta\">
|
||||
<span class=\"mod-meta-item\">
|
||||
<i class=\"fas fa-user\"></i>
|
||||
${mod.author}
|
||||
</span>
|
||||
<span class=\"mod-meta-item\">
|
||||
<i class=\"fas fa-download\"></i>
|
||||
${formatNumber(mod.downloadCount)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class=\"mod-actions\">
|
||||
<button id=\"view-${mod.id}\" class=\"mod-btn-toggle bg-blue-600 text-white hover:bg-blue-700\" onclick=\"window.modsManager.viewModPage(${mod.id})\">
|
||||
<i class=\"fas fa-external-link-alt\"></i>
|
||||
VIEW
|
||||
</button>
|
||||
${!isInstalled ?
|
||||
`<button id=\"install-${mod.id}\" class=\"mod-btn-toggle bg-primary text-black hover:bg-primary/80\">
|
||||
<i class=\"fas fa-download\"></i>
|
||||
INSTALL
|
||||
</button>` :
|
||||
`<button class=\"mod-btn-toggle bg-white/10 text-white\" disabled>
|
||||
<i class=\"fas fa-check\"></i>
|
||||
INSTALLED
|
||||
</button>`
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
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 = `
|
||||
<div class="notification-content">
|
||||
<i class="fas ${icons[type]}"></i>
|
||||
<span>${message}</span>
|
||||
</div>
|
||||
<button class="notification-close" onclick="this.parentElement.remove()">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
`;
|
||||
|
||||
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 = `
|
||||
<div style="padding: 24px; border-bottom: 1px solid rgba(255,255,255,0.1);">
|
||||
<div style="display: flex; align-items: center; gap: 12px; color: #ef4444;">
|
||||
<i class="fas fa-exclamation-triangle" style="font-size: 24px;"></i>
|
||||
<h3 style="margin: 0; font-size: 1.2rem; font-weight: 600;">Confirm Deletion</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div style="padding: 24px; color: #e5e7eb;">
|
||||
<p style="margin: 0; line-height: 1.5; font-size: 1rem;">${message}</p>
|
||||
</div>
|
||||
<div style="padding: 20px 24px; display: flex; gap: 12px; justify-content: flex-end; border-top: 1px solid rgba(255,255,255,0.1);">
|
||||
<button class="mod-confirm-cancel" style="
|
||||
background: transparent;
|
||||
color: #9ca3af;
|
||||
border: 1px solid rgba(156, 163, 175, 0.3);
|
||||
padding: 10px 20px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s;
|
||||
">Cancel</button>
|
||||
<button class="mod-confirm-delete" style="
|
||||
background: #ef4444;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s;
|
||||
">Delete</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
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 = `
|
||||
<div class=\"empty-installed-mods error\">
|
||||
<i class=\"fas fa-exclamation-triangle\"></i>
|
||||
<h4>Error</h4>
|
||||
<p>${message}</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
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);
|
||||
124
GUI/js/news.js
124
GUI/js/news.js
@@ -1,124 +0,0 @@
|
||||
|
||||
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 => `
|
||||
<div class="news-item news-card" onclick="openNewsDetails(${article.id})">
|
||||
<div class="news-image" style="background-image: url('${article.image}');"></div>
|
||||
<div class="news-overlay">
|
||||
<span class="news-type">${article.type}</span>
|
||||
<span class="news-date">${article.date}</span>
|
||||
</div>
|
||||
<div class="news-content">
|
||||
<h3 class="news-title">${article.title}</h3>
|
||||
<p class="news-summary">${article.summary}</p>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
function displayFullNews(news) {
|
||||
const allNewsGrid = document.getElementById('allNewsGrid');
|
||||
if (!allNewsGrid) return;
|
||||
|
||||
allNewsGrid.innerHTML = news.map(article => `
|
||||
<div class="news-item news-card" onclick="openNewsDetails(${article.id})">
|
||||
<div class="news-image" style="background-image: url('${article.image}');"></div>
|
||||
<div class="news-overlay">
|
||||
<span class="news-type">${article.type}</span>
|
||||
<span class="news-date">${article.date}</span>
|
||||
</div>
|
||||
<div class="news-content">
|
||||
<h3 class="news-title">${article.title}</h3>
|
||||
<p class="news-summary">${article.summary}</p>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
function showErrorNews() {
|
||||
const newsGrid = document.getElementById('newsGrid');
|
||||
if (newsGrid) {
|
||||
newsGrid.innerHTML = `
|
||||
<div class="loading-news">
|
||||
<i class="fas fa-exclamation-triangle text-4xl mb-4 text-yellow-500"></i>
|
||||
<span>Unable to load news</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -1,154 +0,0 @@
|
||||
|
||||
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);
|
||||
@@ -1,9 +0,0 @@
|
||||
import './ui.js';
|
||||
import './install.js';
|
||||
import './launcher.js';
|
||||
import './news.js';
|
||||
import './mods.js';
|
||||
import './players.js';
|
||||
import './chat.js';
|
||||
import './settings.js';
|
||||
|
||||
@@ -1,143 +0,0 @@
|
||||
|
||||
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
|
||||
};
|
||||
469
GUI/js/ui.js
469
GUI/js/ui.js
@@ -1,469 +0,0 @@
|
||||
|
||||
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 = `
|
||||
<div style="background: linear-gradient(135deg, rgba(147, 51, 234, 0.2), rgba(59, 130, 246, 0.2)); padding: 25px; border-bottom: 1px solid rgba(255,255,255,0.1);">
|
||||
<h2 style="margin: 0; color: #fff; font-size: 1.5rem; font-weight: 600; text-align: center;">
|
||||
🔄 Game Update Required
|
||||
</h2>
|
||||
</div>
|
||||
<div style="padding: 30px; color: #e5e7eb; line-height: 1.6;">
|
||||
<div style="text-align: center; margin-bottom: 25px;">
|
||||
<p style="font-size: 1.1rem; margin-bottom: 15px;">
|
||||
An existing Hytale installation has been detected and must be updated to the latest version.
|
||||
</p>
|
||||
<p style="color: #10b981; font-weight: 500; margin-bottom: 20px;">
|
||||
✅ Your game saves and settings will be preserved
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div style="background: rgba(59, 130, 246, 0.1); padding: 20px; border-radius: 8px; border-left: 4px solid #3b82f6; margin: 20px 0;">
|
||||
<p style="margin: 8px 0; font-family: 'Courier New', monospace; font-size: 0.9em;">
|
||||
<strong>📁 Location:</strong> ${data.existingGame.installPath}
|
||||
</p>
|
||||
<p style="margin: 8px 0; font-family: 'Courier New', monospace; font-size: 0.9em;">
|
||||
<strong>💾 UserData:</strong> ${data.existingGame.hasUserData ? '✅ Found (will be preserved)' : '❌ Not found'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div style="background: rgba(234, 179, 8, 0.1); padding: 15px; border-radius: 8px; border-left: 4px solid #eab308; margin: 20px 0;">
|
||||
<p style="margin: 0; color: #fbbf24; font-weight: 500; font-size: 0.95em;">
|
||||
⚠️ This update is mandatory and cannot be skipped
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div style="padding: 25px; border-top: 1px solid rgba(255,255,255,0.1); text-align: center;">
|
||||
<button id="updateGameBtn" style="
|
||||
background: linear-gradient(135deg, #9333ea, #3b82f6) !important;
|
||||
color: white !important;
|
||||
border: none !important;
|
||||
padding: 15px 30px !important;
|
||||
border-radius: 8px !important;
|
||||
font-size: 1rem !important;
|
||||
font-weight: 600 !important;
|
||||
cursor: pointer !important;
|
||||
transition: all 0.2s ease !important;
|
||||
min-width: 200px !important;
|
||||
" onmouseover="this.style.transform='scale(1.05)'" onmouseout="this.style.transform='scale(1)'">
|
||||
🚀 Update Game Now
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
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);
|
||||
162
GUI/js/update.js
162
GUI/js/update.js
@@ -1,162 +0,0 @@
|
||||
|
||||
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 = `
|
||||
<div id="update-popup-overlay">
|
||||
<div class="update-popup-container update-popup-pulse">
|
||||
<div class="update-popup-header">
|
||||
<div class="update-popup-icon">
|
||||
<i class="fas fa-download"></i>
|
||||
</div>
|
||||
<h2 class="update-popup-title">
|
||||
NEW UPDATE AVAILABLE
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div class="update-popup-versions">
|
||||
<div class="version-row">
|
||||
<span class="version-label">Current Version:</span>
|
||||
<span class="version-current">${updateInfo.currentVersion}</span>
|
||||
</div>
|
||||
<div class="version-row">
|
||||
<span class="version-label">New Version:</span>
|
||||
<span class="version-new">${updateInfo.newVersion}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="update-popup-message">
|
||||
A new version of Hytale F2P Launcher is available.<br>
|
||||
Please download the latest version to continue using the launcher.
|
||||
</div>
|
||||
|
||||
<button id="update-download-btn" class="update-download-btn">
|
||||
<i class="fas fa-external-link-alt" style="margin-right: 0.5rem;"></i>
|
||||
Download Update
|
||||
</button>
|
||||
|
||||
<div class="update-popup-footer">
|
||||
This popup cannot be closed until you update the launcher
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
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 = '<i class="fas fa-spinner fa-spin" style="margin-right: 0.5rem;"></i>Opening GitHub...';
|
||||
|
||||
try {
|
||||
await window.electronAPI.openDownloadPage();
|
||||
console.log('✅ Download page opened, launcher will close...');
|
||||
|
||||
downloadBtn.innerHTML = '<i class="fas fa-check" style="margin-right: 0.5rem;"></i>Launcher closing...';
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Error opening download page:', error);
|
||||
downloadBtn.disabled = false;
|
||||
downloadBtn.innerHTML = '<i class="fas fa-external-link-alt" style="margin-right: 0.5rem;"></i>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;
|
||||
4189
GUI/style.css
4189
GUI/style.css
File diff suppressed because it is too large
Load Diff
@@ -1,9 +0,0 @@
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Name=Hytale-F2P
|
||||
Comment=A modern, cross-platform launcher for Hytale with automatic updates and multi-client support
|
||||
Exec=/opt/Hytale-F2P/hytale-f2p-launcher
|
||||
Categories=Game;
|
||||
Icon=Hytale-F2P
|
||||
Terminal=false
|
||||
StartupNotify=true
|
||||
31
PKGBUILD
31
PKGBUILD
@@ -1,31 +0,0 @@
|
||||
# Maintainer: Terromur <terromuroz@proton.me>
|
||||
pkgname=Hytale-F2P-git
|
||||
_pkgname=Hytale-F2P
|
||||
pkgver=2.0.0.r47.gebcfdc4
|
||||
pkgrel=1
|
||||
pkgdesc="HyLauncher - unofficial Hytale Launcher for free to play gamers"
|
||||
arch=('x86_64')
|
||||
url="https://github.com/amiayweb/Hytale-F2P"
|
||||
license=('custom')
|
||||
makedepends=('npm')
|
||||
source=("git+$url.git" "Hytale-F2P.desktop")
|
||||
sha256sums=('SKIP' '46488fada4775d9976d7b7b62f8d1f1f8d9a9a9d8f8aa9af4f2e2153019f6a30')
|
||||
|
||||
pkgver() {
|
||||
cd "$_pkgname"
|
||||
printf "2.0.0.r%s.g%s" "$(git rev-list --count HEAD)" "$(git rev-parse --short HEAD)"
|
||||
}
|
||||
|
||||
build() {
|
||||
cd "$_pkgname"
|
||||
npm install
|
||||
npm run build:linux
|
||||
}
|
||||
|
||||
package() {
|
||||
mkdir -p "$pkgdir/opt/$_pkgname"
|
||||
cp -r "$_pkgname/dist/linux-unpacked/"* "$pkgdir/opt/$_pkgname"
|
||||
install -Dm644 "$_pkgname.desktop" "$pkgdir/usr/share/applications/$_pkgname.desktop"
|
||||
install -Dm644 "$_pkgname/icon.png" "$pkgdir/usr/share/icons/hicolor/512x512/apps/$_pkgname.png"
|
||||
}
|
||||
|
||||
204
README.md
204
README.md
@@ -1,203 +1,7 @@
|
||||
# 🎮 Hytale F2P Launcher | Multiplayer Support
|
||||
This project has been taken down by poor hypixel studios.
|
||||
|
||||
<div align="center">
|
||||
|
||||

|
||||

|
||||

|
||||
[](https://discord.gg/MHkEjepMQ7)
|
||||
|
||||
**A modern, cross-platform offline launcher for Hytale with automatic updates and multiplayer support (windows users & non-premium only)**
|
||||
|
||||
[](https://github.com/amiayweb/Hytale-F2P/stargazers)
|
||||
[](https://github.com/amiayweb/Hytale-F2P/network/members)
|
||||
|
||||
⭐ **If you find this project useful, please give it a star!** ⭐
|
||||
🛑 **Found a problem? Open an issue! I’m on Windows, so I can’t test on macOS or Linux.** 🛑
|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
## 📸 Screenshots
|
||||
|
||||
<div align="center">
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
## ✨ Features
|
||||
|
||||
🎯 **Core Features**
|
||||
- 🔄 **Automatic Updates** - Smart version checking and seamless game updates
|
||||
- 💾 **Data Preservation** - Intelligent UserData backup and restoration during updates
|
||||
- 🌐 **Cross-Platform** - Full support for Windows, Linux (X11/Wayland), and macOS
|
||||
- ☕ **Java Management** - Automatic Java runtime detection and installation
|
||||
- 🎮 **Multiplayer Support** - Automatic multiplayer client installation (Windows)
|
||||
|
||||
🛡️ **Advanced Features**
|
||||
- 📁 **Custom Installation** - Choose your own installation directory
|
||||
- 🔍 **Smart Detection** - Automatic game and dependency detection
|
||||
- 🗂️ **Mod Support** - Built-in mod management system
|
||||
- 💬 **Player Chat** - Integrated chat system for community interaction
|
||||
- 📰 **News Feed** - Stay updated with the latest Hytale news
|
||||
- 🎨 **Modern UI** - Clean, responsive interface with dark theme
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
### 📥 Installation
|
||||
|
||||
#### Windows
|
||||
1. Download the latest `Hytale-F2P.exe` from [**Releases**](https://github.com/amiayweb/Hytale-F2P/releases)
|
||||
2. Run the installer
|
||||
3. Launch from desktop or start menu
|
||||
|
||||
#### Linux
|
||||
See [BUILD.md](BUILD.md) for detailed build instructions.
|
||||
|
||||
#### macOS
|
||||
See [BUILD.md](BUILD.md) for detailed build instructions.
|
||||
|
||||
#### 🖥️ How to create server (Windows Only)?
|
||||
1. Download the server files directly from: `http://3.10.208.30:3002/server`
|
||||
2. Replace the existing files in your `HytaleF2P` installation folder
|
||||
3. Run the server launcher (.bat) to start hosting your own Hytale server
|
||||
4. You will need a third party software like Radmin VPN (check on youtube how to use Radmin VPN).
|
||||
|
||||
### 🎮 Usage
|
||||
|
||||
1. **Enter your player name**
|
||||
2. **Click "PLAY"**
|
||||
3. **Automatic setup** - The launcher handles everything automatically
|
||||
4. **Game launches** - Enjoy playing Hytale!
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ Building from Source
|
||||
|
||||
See [BUILD.md](BUILD.md) for comprehensive build instructions.
|
||||
|
||||
---
|
||||
|
||||
## 📋 Changelog
|
||||
|
||||
### 🆕 v2.0.0 *(Latest)*
|
||||
- ✅ **Automatic Game Update System** - Smart version checking and seamless updates
|
||||
- ✅ **Partial Automatic Launcher Update System** - This will inform you when I release a new update.
|
||||
- 🛡️ **UserData Preservation** - Intelligent backup/restore of game saves during updates
|
||||
- 🐧 **Enhanced Linux Support** - Full Wayland and X11 compatibility
|
||||
- 🔄 **Multiplayer Auto-Install** - Automatic multiplayer client setup on updates (Windows)
|
||||
- 📡 **API Integration** - Real-time version checking and client management
|
||||
- 🎨 **UI Improvements** - Added contributor credits footer
|
||||
- 🔄 **Complete Launcher Overhaul** - Total redesign of the launcher architecture and interface
|
||||
- 🗂️ **Integrated Mod Manager** - Built-in mod installation, management
|
||||
- 💬 **Community Chat System** - Real-time chat for launcher users to connect and communicate
|
||||
|
||||
### 🔧 v1.0.1
|
||||
- 📁 **Custom Installation** - Choose installation directory with file browser
|
||||
- 🏠 **Always on Top** - Launcher stays visible during installation
|
||||
- 🧠 **Smart Detection** - Automatic game detection and UI adaptation
|
||||
- 🗑️ **Uninstall Feature** - Easy game removal with one click
|
||||
- 🔄 **Dynamic UI** - "INSTALL" vs "PLAY" button based on game state
|
||||
- 🛠️ **Path Management** - Proper custom directory handling
|
||||
- 💫 **UI Polish** - Improved layout and overflow prevention
|
||||
|
||||
### 🎉 v1.0.0 *(Initial Release)*
|
||||
- 🎮 **Offline Gameplay** - Play Hytale without internet connection
|
||||
- ⚡ **Auto Installation** - One-click game setup
|
||||
- ☕ **Java Management** - Automatic Java runtime handling
|
||||
- 🎨 **Modern Interface** - Clean, intuitive design
|
||||
- 🌟 **First Release** - Core launcher functionality
|
||||
|
||||
---
|
||||
|
||||
## 👥 Contributors
|
||||
|
||||
<div align="center">
|
||||
|
||||
**Made with ❤️ by the community**
|
||||
|
||||
[](https://github.com/amiayweb/Hytale-F2P/graphs/contributors)
|
||||
|
||||
</div>
|
||||
|
||||
### 🏆 Project Creator
|
||||
- [**@amiayweb**](https://github.com/amiayweb) - *Lead Developer & Project Creator*
|
||||
|
||||
### 🌟 Contributors
|
||||
- [**@chasem-dev**](https://github.com/chasem-dev) - *Issues fixer*
|
||||
- [**@crimera**](https://github.com/crimera) - *Issues fixer*
|
||||
- [**@sanasol**](https://github.com/sanasol) - *Issues fixer*
|
||||
- [**@Terromur**](https://github.com/Terromur) - *Issues fixer*
|
||||
- [**@Citeli-py**](https://github.com/Citeli-py) - *Issues fixer*
|
||||
- [**@ericiskoolbeans**](https://github.com/ericiskoolbeans) - *Beta Tester*
|
||||
|
||||
---
|
||||
|
||||
## 📊 GitHub Stats
|
||||
|
||||
<div align="center">
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
</div>
|
||||
|
||||
|
||||
## 📞 Support
|
||||
|
||||
<div align="center">
|
||||
|
||||
[](https://github.com/amiayweb/Hytale-F2P/issues)
|
||||
[](https://github.com/amiayweb/Hytale-F2P/discussions)
|
||||
|
||||
**Need help?** Open an [issue](https://github.com/amiayweb/Hytale-F2P/issues) or start a [discussion](https://github.com/amiayweb/Hytale-F2P/discussions)!
|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
## ⚖️ Legal Disclaimer
|
||||
|
||||
<div align="center">
|
||||
|
||||
⚠️ **Important Notice** ⚠️
|
||||
|
||||
</div>
|
||||
|
||||
This launcher is created for **educational purposes only**.
|
||||
|
||||
🏛️ **Not Official** - This is an independent fan project **not affiliated with, endorsed by, or associated with** Hypixel Studios or Hytale.
|
||||
|
||||
🛡️ **No Warranty** - This software is provided **"as is"** without any warranty of any kind.
|
||||
|
||||
📝 **Responsibility** - The authors take no responsibility for how this software is used.
|
||||
|
||||
🛑 **Takedown Policy** - If Hypixel Studios or Hytale requests removal, this project will be taken down immediately.
|
||||
|
||||
❤️ **Support Official** - Please support the official game by purchasing it when available.
|
||||
|
||||
---
|
||||
|
||||
## 📬 Contact
|
||||
|
||||
[](https://discord.com/users/1433515183606599873)
|
||||
|
||||
|
||||
<div align="center">
|
||||
|
||||
**⭐ Star this project if you found it helpful! ⭐**
|
||||
|
||||
*Made with ❤️ by [@amiayweb](https://github.com/amiayweb) and the amazing community*
|
||||
[](https://www.star-history.com/#amiayweb/Hytale-F2P&type=date&legend=top-left)
|
||||
</div>
|
||||
We still owning the HF2P community this is not illegal so cry.
|
||||
|
||||
https://discord.gg/mzdfCJy5J
|
||||
|
||||
https://t.me/hf2p_og
|
||||
|
||||
2216
backend/launcher.js
2216
backend/launcher.js
File diff suppressed because it is too large
Load Diff
@@ -1,73 +0,0 @@
|
||||
const axios = require('axios');
|
||||
|
||||
const UPDATE_CHECK_URL = 'http://3.10.208.30:3002/api/version_launcher';
|
||||
const CURRENT_VERSION = '2.0.0';
|
||||
const GITHUB_DOWNLOAD_URL = 'https://github.com/amiayweb/Hytale-F2P/';
|
||||
|
||||
class UpdateManager {
|
||||
constructor() {
|
||||
this.updateAvailable = false;
|
||||
this.remoteVersion = null;
|
||||
}
|
||||
|
||||
async checkForUpdates() {
|
||||
try {
|
||||
console.log('Checking for updates...');
|
||||
console.log(`Local version: ${CURRENT_VERSION}`);
|
||||
|
||||
const response = await axios.get(UPDATE_CHECK_URL, {
|
||||
timeout: 5000,
|
||||
headers: {
|
||||
'User-Agent': 'Hytale-F2P-Launcher'
|
||||
}
|
||||
});
|
||||
|
||||
if (response.data && response.data.launcher_version) {
|
||||
this.remoteVersion = response.data.launcher_version;
|
||||
console.log(`Remote version: ${this.remoteVersion}`);
|
||||
|
||||
if (this.remoteVersion !== CURRENT_VERSION) {
|
||||
this.updateAvailable = true;
|
||||
console.log('Update available!');
|
||||
return {
|
||||
updateAvailable: true,
|
||||
currentVersion: CURRENT_VERSION,
|
||||
newVersion: this.remoteVersion,
|
||||
downloadUrl: GITHUB_DOWNLOAD_URL
|
||||
};
|
||||
} else {
|
||||
console.log('Launcher is up to date');
|
||||
return {
|
||||
updateAvailable: false,
|
||||
currentVersion: CURRENT_VERSION,
|
||||
newVersion: this.remoteVersion
|
||||
};
|
||||
}
|
||||
} else {
|
||||
throw new Error('Invalid API response');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error checking for updates:', error.message);
|
||||
return {
|
||||
updateAvailable: false,
|
||||
error: error.message,
|
||||
currentVersion: CURRENT_VERSION
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
getDownloadUrl() {
|
||||
return GITHUB_DOWNLOAD_URL;
|
||||
}
|
||||
|
||||
getUpdateInfo() {
|
||||
return {
|
||||
updateAvailable: this.updateAvailable,
|
||||
currentVersion: CURRENT_VERSION,
|
||||
remoteVersion: this.remoteVersion,
|
||||
downloadUrl: this.getDownloadUrl()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = UpdateManager;
|
||||
566
main.js
566
main.js
@@ -1,566 +0,0 @@
|
||||
const { app, BrowserWindow, ipcMain, dialog, shell } = require('electron');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const { launchGame, launchGameWithVersionCheck, installGame, saveUsername, loadUsername, saveChatUsername, loadChatUsername, saveJavaPath, loadJavaPath, saveInstallPath, loadInstallPath, isGameInstalled, uninstallGame, getHytaleNews, handleFirstLaunchCheck, proposeGameUpdate, markAsLaunched } = require('./backend/launcher');
|
||||
const UpdateManager = require('./backend/updateManager');
|
||||
|
||||
let mainWindow;
|
||||
let updateManager;
|
||||
let discordRPC = null;
|
||||
|
||||
// Discord Rich Presence setup
|
||||
const DISCORD_CLIENT_ID = '1462244937868513373';
|
||||
|
||||
function initDiscordRPC() {
|
||||
try {
|
||||
const { Client } = require('discord-rpc');
|
||||
discordRPC = new Client({ transport: 'ipc' });
|
||||
|
||||
discordRPC.on('ready', () => {
|
||||
console.log('Discord RPC connected');
|
||||
setDiscordActivity();
|
||||
});
|
||||
|
||||
discordRPC.on('disconnected', () => {
|
||||
console.log('Discord RPC disconnected');
|
||||
});
|
||||
|
||||
discordRPC.login({ clientId: DISCORD_CLIENT_ID }).catch(err => {
|
||||
console.log('Failed to connect to Discord:', err.message);
|
||||
});
|
||||
} catch (error) {
|
||||
console.log('Discord RPC module not available:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
function setDiscordActivity() {
|
||||
if (!discordRPC) return;
|
||||
|
||||
try {
|
||||
discordRPC.setActivity({
|
||||
details: 'Using HytaleF2P',
|
||||
startTimestamp: Date.now(),
|
||||
largeImageKey: 'hytale_logo',
|
||||
largeImageText: 'Hytale F2P Launcher',
|
||||
buttons: [
|
||||
{
|
||||
label: 'GitHub',
|
||||
url: 'https://github.com/amiayweb/Hytale-F2P'
|
||||
}
|
||||
]
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to set Discord activity:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
function createWindow() {
|
||||
mainWindow = new BrowserWindow({
|
||||
width: 1280,
|
||||
height: 720,
|
||||
frame: false,
|
||||
resizable: false,
|
||||
alwaysOnTop: false,
|
||||
backgroundColor: '#090909',
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, 'preload.js'),
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
devTools: false,
|
||||
webSecurity: true
|
||||
}
|
||||
});
|
||||
|
||||
mainWindow.loadFile('GUI/index.html');
|
||||
|
||||
// Initialize Discord Rich Presence
|
||||
initDiscordRPC();
|
||||
|
||||
updateManager = new UpdateManager();
|
||||
setTimeout(async () => {
|
||||
const updateInfo = await updateManager.checkForUpdates();
|
||||
if (updateInfo.updateAvailable) {
|
||||
mainWindow.webContents.send('show-update-popup', updateInfo);
|
||||
}
|
||||
}, 3000);
|
||||
|
||||
|
||||
mainWindow.webContents.on('devtools-opened', () => {
|
||||
mainWindow.webContents.closeDevTools();
|
||||
});
|
||||
|
||||
mainWindow.webContents.on('before-input-event', (event, input) => {
|
||||
if (input.control && input.shift && input.key.toLowerCase() === 'i') {
|
||||
event.preventDefault();
|
||||
}
|
||||
if (input.control && input.shift && input.key.toLowerCase() === 'j') {
|
||||
event.preventDefault();
|
||||
}
|
||||
if (input.control && input.shift && input.key.toLowerCase() === 'c') {
|
||||
event.preventDefault();
|
||||
}
|
||||
if (input.key === 'F12') {
|
||||
event.preventDefault();
|
||||
}
|
||||
if (input.key === 'F5') {
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
mainWindow.webContents.on('context-menu', (e) => {
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
mainWindow.webContents.setIgnoreMenuShortcuts(true);
|
||||
}
|
||||
|
||||
app.whenReady().then(async () => {
|
||||
createWindow();
|
||||
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
console.log('Starting first launch check...');
|
||||
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
mainWindow.webContents.send('lock-play-button', true);
|
||||
}
|
||||
|
||||
const progressCallback = (message, percent, speed, downloaded, total) => {
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
mainWindow.webContents.send('first-launch-progress', { message, percent, speed, downloaded, total });
|
||||
}
|
||||
};
|
||||
|
||||
const firstLaunchResult = await handleFirstLaunchCheck(progressCallback);
|
||||
|
||||
console.log('First launch check result:', firstLaunchResult);
|
||||
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
if (firstLaunchResult.needsUpdate && firstLaunchResult.existingGame) {
|
||||
console.log('Sending show-first-launch-update event...');
|
||||
|
||||
setTimeout(() => {
|
||||
mainWindow.webContents.send('show-first-launch-update', {
|
||||
existingGame: firstLaunchResult.existingGame,
|
||||
isFirstLaunch: firstLaunchResult.isFirstLaunch
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
} else if (firstLaunchResult.isFirstLaunch && !firstLaunchResult.existingGame) {
|
||||
console.log('Sending show-first-launch-welcome event...');
|
||||
|
||||
setTimeout(() => {
|
||||
mainWindow.webContents.send('show-first-launch-welcome');
|
||||
}, 1000);
|
||||
} else {
|
||||
mainWindow.webContents.send('lock-play-button', false);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error during first launch check:', error);
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
mainWindow.webContents.send('lock-play-button', false);
|
||||
}
|
||||
}
|
||||
}, 3000);
|
||||
});
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
// Clean up Discord RPC connection
|
||||
if (discordRPC) {
|
||||
try {
|
||||
discordRPC.destroy();
|
||||
} catch (error) {
|
||||
console.log('Error cleaning up Discord RPC:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit();
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('launch-game', async (event, playerName, javaPath, installPath) => {
|
||||
try {
|
||||
const progressCallback = (message, percent, speed, downloaded, total) => {
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
const data = {
|
||||
message: message || null,
|
||||
percent: percent !== null && percent !== undefined ? Math.min(100, Math.max(0, percent)) : null,
|
||||
speed: speed !== null && speed !== undefined ? speed : null,
|
||||
downloaded: downloaded !== null && downloaded !== undefined ? downloaded : null,
|
||||
total: total !== null && total !== undefined ? total : null
|
||||
};
|
||||
mainWindow.webContents.send('progress-update', data);
|
||||
}
|
||||
};
|
||||
|
||||
const result = await launchGameWithVersionCheck(playerName, progressCallback, javaPath, installPath);
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('Launch error:', error);
|
||||
const errorMessage = error.message || error.toString();
|
||||
return { success: false, error: errorMessage };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('install-game', async (event, playerName, javaPath, installPath) => {
|
||||
try {
|
||||
const progressCallback = (message, percent, speed, downloaded, total) => {
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
const data = {
|
||||
message: message || null,
|
||||
percent: percent !== null && percent !== undefined ? Math.min(100, Math.max(0, percent)) : null,
|
||||
speed: speed !== null && speed !== undefined ? speed : null,
|
||||
downloaded: downloaded !== null && downloaded !== undefined ? downloaded : null,
|
||||
total: total !== null && total !== undefined ? total : null
|
||||
};
|
||||
mainWindow.webContents.send('progress-update', data);
|
||||
}
|
||||
};
|
||||
|
||||
const result = await installGame(playerName, progressCallback, javaPath, installPath);
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('Install error:', error);
|
||||
const errorMessage = error.message || error.toString();
|
||||
return { success: false, error: errorMessage };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('save-username', (event, username) => {
|
||||
saveUsername(username);
|
||||
return { success: true };
|
||||
});
|
||||
|
||||
ipcMain.handle('load-username', () => {
|
||||
return loadUsername();
|
||||
});
|
||||
ipcMain.handle('save-chat-username', async (event, chatUsername) => {
|
||||
saveChatUsername(chatUsername);
|
||||
});
|
||||
|
||||
ipcMain.handle('load-chat-username', async () => {
|
||||
return loadChatUsername();
|
||||
});
|
||||
ipcMain.handle('save-java-path', (event, javaPath) => {
|
||||
saveJavaPath(javaPath);
|
||||
return { success: true };
|
||||
});
|
||||
|
||||
ipcMain.handle('load-java-path', () => {
|
||||
return loadJavaPath();
|
||||
});
|
||||
|
||||
ipcMain.handle('save-install-path', (event, installPath) => {
|
||||
saveInstallPath(installPath);
|
||||
return { success: true };
|
||||
});
|
||||
|
||||
ipcMain.handle('load-install-path', () => {
|
||||
return loadInstallPath();
|
||||
});
|
||||
|
||||
ipcMain.handle('select-install-path', async () => {
|
||||
const result = await dialog.showOpenDialog(mainWindow, {
|
||||
properties: ['openDirectory'],
|
||||
title: 'Select Installation Folder'
|
||||
});
|
||||
|
||||
if (!result.canceled && result.filePaths.length > 0) {
|
||||
return result.filePaths[0];
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
ipcMain.handle('accept-first-launch-update', async (event, existingGame) => {
|
||||
try {
|
||||
const progressCallback = (message, percent, speed, downloaded, total) => {
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
const data = {
|
||||
message: message || null,
|
||||
percent: percent !== null && percent !== undefined ? Math.min(100, Math.max(0, percent)) : null,
|
||||
speed: speed !== null && speed !== undefined ? speed : null,
|
||||
downloaded: downloaded !== null && downloaded !== undefined ? downloaded : null,
|
||||
total: total !== null && total !== undefined ? total : null
|
||||
};
|
||||
mainWindow.webContents.send('first-launch-progress', data);
|
||||
}
|
||||
};
|
||||
|
||||
const result = await proposeGameUpdate(existingGame, progressCallback);
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('First launch update error:', error);
|
||||
const errorMessage = error.message || error.toString();
|
||||
return { success: false, error: errorMessage };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('mark-as-launched', async () => {
|
||||
try {
|
||||
markAsLaunched();
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
console.error('Mark as launched error:', error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('is-game-installed', () => {
|
||||
return isGameInstalled();
|
||||
});
|
||||
|
||||
ipcMain.handle('uninstall-game', async () => {
|
||||
try {
|
||||
await uninstallGame();
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
console.error('Uninstall error:', error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('get-hytale-news', async () => {
|
||||
try {
|
||||
const news = await getHytaleNews();
|
||||
return news;
|
||||
} catch (error) {
|
||||
console.error('News fetch error:', error);
|
||||
return [];
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('open-external', async (event, url) => {
|
||||
try {
|
||||
await shell.openExternal(url);
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
console.error('Failed to open external URL:', error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('browse-java-path', async () => {
|
||||
const isWindows = process.platform === 'win32';
|
||||
const isMac = process.platform === 'darwin';
|
||||
|
||||
let dialogOptions;
|
||||
|
||||
if (isWindows) {
|
||||
dialogOptions = {
|
||||
properties: ['openFile'],
|
||||
title: 'Select Java Executable',
|
||||
filters: [
|
||||
{ name: 'Java Executable', extensions: ['exe'] },
|
||||
{ name: 'All Files', extensions: ['*'] }
|
||||
]
|
||||
};
|
||||
} else if (isMac) {
|
||||
dialogOptions = {
|
||||
properties: ['openFile'],
|
||||
title: 'Select Java Executable',
|
||||
message: 'Select java executable (usually in /Library/Java/JavaVirtualMachines/*/Contents/Home/bin/java)',
|
||||
filters: [
|
||||
{ name: 'All Files', extensions: ['*'] }
|
||||
]
|
||||
};
|
||||
} else {
|
||||
dialogOptions = {
|
||||
properties: ['openFile'],
|
||||
title: 'Select Java Executable',
|
||||
message: 'Select java executable (usually /usr/bin/java or similar)',
|
||||
filters: [
|
||||
{ name: 'All Files', extensions: ['*'] }
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
const result = await dialog.showOpenDialog(mainWindow, dialogOptions);
|
||||
|
||||
if (!result.canceled && result.filePaths.length > 0) {
|
||||
return result.filePaths[0];
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
ipcMain.handle('save-settings', async (event, settings) => {
|
||||
try {
|
||||
if (settings.playerName) saveUsername(settings.playerName);
|
||||
if (settings.javaPath !== undefined) saveJavaPath(settings.javaPath);
|
||||
if (settings.installPath !== undefined) saveInstallPath(settings.installPath);
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
console.error('Save settings error:', error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('load-settings', async () => {
|
||||
try {
|
||||
return {
|
||||
playerName: loadUsername() || 'Player',
|
||||
javaPath: loadJavaPath() || '',
|
||||
installPath: loadInstallPath() || '',
|
||||
customInstall: false
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Load settings error:', error);
|
||||
return {
|
||||
playerName: 'Player',
|
||||
javaPath: '',
|
||||
installPath: '',
|
||||
customInstall: false
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
const { getModsPath, loadInstalledMods, downloadMod, uninstallMod, toggleMod } = require('./backend/launcher');
|
||||
const os = require('os');
|
||||
|
||||
ipcMain.handle('get-local-app-data', async () => {
|
||||
return process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local');
|
||||
});
|
||||
|
||||
ipcMain.handle('get-user-id', async () => {
|
||||
try {
|
||||
const { getOrCreatePlayerId } = require('./backend/launcher');
|
||||
return await getOrCreatePlayerId();
|
||||
} catch (error) {
|
||||
console.error('Error getting user ID:', error);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('load-installed-mods', async (event, modsPath) => {
|
||||
try {
|
||||
return await loadInstalledMods(modsPath);
|
||||
} catch (error) {
|
||||
console.error('Error loading installed mods:', error);
|
||||
return [];
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('openExternalLink', async (event, url) => {
|
||||
try {
|
||||
console.log('Opening external URL:', url);
|
||||
await shell.openExternal(url);
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
console.error('Error opening external link:', error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('download-mod', async (event, modInfo) => {
|
||||
try {
|
||||
return await downloadMod(modInfo);
|
||||
} catch (error) {
|
||||
console.error('Error downloading mod:', error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('uninstall-mod', async (event, modId, modsPath) => {
|
||||
try {
|
||||
return await uninstallMod(modId, modsPath);
|
||||
} catch (error) {
|
||||
console.error('Error uninstalling mod:', error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('toggle-mod', async (event, modId, modsPath) => {
|
||||
try {
|
||||
return await toggleMod(modId, modsPath);
|
||||
} catch (error) {
|
||||
console.error('Error toggling mod:', error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('get-mods-path', async () => {
|
||||
try {
|
||||
return await getModsPath();
|
||||
} catch (error) {
|
||||
console.error('Error getting mods path:', error);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('select-mod-files', async () => {
|
||||
const result = await dialog.showOpenDialog(mainWindow, {
|
||||
properties: ['openFile', 'multiSelections'],
|
||||
title: 'Select Mod Files',
|
||||
filters: [
|
||||
{ name: 'Mod Files', extensions: ['jar', 'zip'] },
|
||||
{ name: 'All Files', extensions: ['*'] }
|
||||
]
|
||||
});
|
||||
|
||||
if (!result.canceled && result.filePaths.length > 0) {
|
||||
return result.filePaths;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
ipcMain.handle('copy-mod-file', async (event, sourcePath, modsPath) => {
|
||||
try {
|
||||
const fileName = path.basename(sourcePath);
|
||||
const destPath = path.join(modsPath, fileName);
|
||||
|
||||
fs.copyFileSync(sourcePath, destPath);
|
||||
|
||||
return { success: true, fileName };
|
||||
} catch (error) {
|
||||
console.error('Error copying mod file:', error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('check-for-updates', async () => {
|
||||
try {
|
||||
return await updateManager.checkForUpdates();
|
||||
} catch (error) {
|
||||
console.error('Error checking for updates:', error);
|
||||
return { updateAvailable: false, error: error.message };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('open-download-page', async () => {
|
||||
try {
|
||||
await shell.openExternal(updateManager.getDownloadUrl());
|
||||
|
||||
setTimeout(() => {
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
mainWindow.close();
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
console.error('Error opening download page:', error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('get-update-info', async () => {
|
||||
return updateManager.getUpdateInfo();
|
||||
});
|
||||
|
||||
ipcMain.handle('window-close', () => {
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
mainWindow.close();
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('window-minimize', () => {
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
mainWindow.minimize();
|
||||
}
|
||||
});
|
||||
|
||||
1
package-lock.json
generated
1
package-lock.json
generated
@@ -1 +0,0 @@
|
||||
|
||||
121
package.json
121
package.json
@@ -1,121 +0,0 @@
|
||||
{
|
||||
"name": "hytale-f2p-launcher",
|
||||
"version": "2.0.0",
|
||||
"description": "A modern, cross-platform launcher for Hytale with automatic updates and multi-client support",
|
||||
"homepage": "https://github.com/amiayweb/Hytale-F2P",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"start": "electron .",
|
||||
"dev": "electron . --dev",
|
||||
"build": "electron-builder",
|
||||
"build:win": "electron-builder --win",
|
||||
"build:linux": "electron-builder --linux",
|
||||
"build:mac": "electron-builder --mac",
|
||||
"build:all": "electron-builder --win --linux --mac"
|
||||
},
|
||||
"keywords": [
|
||||
"hytale",
|
||||
"launcher",
|
||||
"game",
|
||||
"client",
|
||||
"cross-platform",
|
||||
"electron",
|
||||
"auto-update",
|
||||
"mod-manager",
|
||||
"chat"
|
||||
],
|
||||
"author": {
|
||||
"name": "AMIAY",
|
||||
"email": "support@amiay.dev"
|
||||
},
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"electron": "^40.0.0",
|
||||
"electron-builder": "^26.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"adm-zip": "^0.5.10",
|
||||
"axios": "^1.6.0",
|
||||
"discord-rpc": "^4.0.1",
|
||||
"tar": "6.2.1",
|
||||
"uuid": "^9.0.1"
|
||||
},
|
||||
"overrides": {
|
||||
"tar": "$tar"
|
||||
},
|
||||
"build": {
|
||||
"appId": "com.hytalef2p.launcher",
|
||||
"productName": "Hytale F2P",
|
||||
"directories": {
|
||||
"output": "dist"
|
||||
},
|
||||
"files": [
|
||||
"main.js",
|
||||
"preload.js",
|
||||
"backend/**/*",
|
||||
"GUI/**/*",
|
||||
"package.json"
|
||||
],
|
||||
"win": {
|
||||
"target": [
|
||||
{
|
||||
"target": "nsis",
|
||||
"arch": [
|
||||
"x64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "portable",
|
||||
"arch": [
|
||||
"x64"
|
||||
]
|
||||
}
|
||||
],
|
||||
"icon": "icon.ico"
|
||||
},
|
||||
"linux": {
|
||||
"target": [
|
||||
{
|
||||
"target": "AppImage",
|
||||
"arch": [
|
||||
"x64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "deb",
|
||||
"arch": [
|
||||
"x64"
|
||||
]
|
||||
}
|
||||
],
|
||||
"icon": "build/icon.png",
|
||||
"category": "Game",
|
||||
"description": "A modern, cross-platform launcher for Hytale with automatic updates and multi-client support"
|
||||
},
|
||||
"mac": {
|
||||
"target": [
|
||||
{
|
||||
"target": "dmg",
|
||||
"arch": [
|
||||
"universal"
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "zip",
|
||||
"arch": [
|
||||
"universal"
|
||||
]
|
||||
}
|
||||
],
|
||||
"icon": "build/icon.icns",
|
||||
"category": "public.app-category.games"
|
||||
},
|
||||
"nsis": {
|
||||
"oneClick": false,
|
||||
"allowToChangeInstallationDirectory": true,
|
||||
"createDesktopShortcut": true,
|
||||
"createStartMenuShortcut": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
58
preload.js
58
preload.js
@@ -1,58 +0,0 @@
|
||||
const { contextBridge, ipcRenderer } = require('electron');
|
||||
|
||||
contextBridge.exposeInMainWorld('electronAPI', {
|
||||
launchGame: (playerName, javaPath, installPath) => ipcRenderer.invoke('launch-game', playerName, javaPath, installPath),
|
||||
installGame: (playerName, javaPath, installPath) => ipcRenderer.invoke('install-game', playerName, javaPath, installPath),
|
||||
closeWindow: () => ipcRenderer.invoke('window-close'),
|
||||
minimizeWindow: () => ipcRenderer.invoke('window-minimize'),
|
||||
saveUsername: (username) => ipcRenderer.invoke('save-username', username),
|
||||
loadUsername: () => ipcRenderer.invoke('load-username'),
|
||||
saveChatUsername: (chatUsername) => ipcRenderer.invoke('save-chat-username', chatUsername),
|
||||
loadChatUsername: () => ipcRenderer.invoke('load-chat-username'),
|
||||
saveJavaPath: (javaPath) => ipcRenderer.invoke('save-java-path', javaPath),
|
||||
loadJavaPath: () => ipcRenderer.invoke('load-java-path'),
|
||||
saveInstallPath: (installPath) => ipcRenderer.invoke('save-install-path', installPath),
|
||||
loadInstallPath: () => ipcRenderer.invoke('load-install-path'),
|
||||
selectInstallPath: () => ipcRenderer.invoke('select-install-path'),
|
||||
browseJavaPath: () => ipcRenderer.invoke('browse-java-path'),
|
||||
isGameInstalled: () => ipcRenderer.invoke('is-game-installed'),
|
||||
uninstallGame: () => ipcRenderer.invoke('uninstall-game'),
|
||||
getHytaleNews: () => ipcRenderer.invoke('get-hytale-news'),
|
||||
openExternal: (url) => ipcRenderer.invoke('open-external', url),
|
||||
openExternalLink: (url) => ipcRenderer.invoke('openExternalLink', url),
|
||||
saveSettings: (settings) => ipcRenderer.invoke('save-settings', settings),
|
||||
loadSettings: () => ipcRenderer.invoke('load-settings'),
|
||||
getLocalAppData: () => ipcRenderer.invoke('get-local-app-data'),
|
||||
getModsPath: () => ipcRenderer.invoke('get-mods-path'),
|
||||
loadInstalledMods: (modsPath) => ipcRenderer.invoke('load-installed-mods', modsPath),
|
||||
downloadMod: (modInfo) => ipcRenderer.invoke('download-mod', modInfo),
|
||||
uninstallMod: (modId, modsPath) => ipcRenderer.invoke('uninstall-mod', modId, modsPath),
|
||||
toggleMod: (modId, modsPath) => ipcRenderer.invoke('toggle-mod', modId, modsPath),
|
||||
selectModFiles: () => ipcRenderer.invoke('select-mod-files'),
|
||||
copyModFile: (sourcePath, modsPath) => ipcRenderer.invoke('copy-mod-file', sourcePath, modsPath),
|
||||
onProgressUpdate: (callback) => {
|
||||
ipcRenderer.on('progress-update', (event, data) => callback(data));
|
||||
},
|
||||
getUserId: () => ipcRenderer.invoke('get-user-id'),
|
||||
checkForUpdates: () => ipcRenderer.invoke('check-for-updates'),
|
||||
openDownloadPage: () => ipcRenderer.invoke('open-download-page'),
|
||||
getUpdateInfo: () => ipcRenderer.invoke('get-update-info'),
|
||||
onUpdatePopup: (callback) => {
|
||||
ipcRenderer.on('show-update-popup', (event, data) => callback(data));
|
||||
},
|
||||
|
||||
acceptFirstLaunchUpdate: (existingGame) => ipcRenderer.invoke('accept-first-launch-update', existingGame),
|
||||
markAsLaunched: () => ipcRenderer.invoke('mark-as-launched'),
|
||||
onFirstLaunchUpdate: (callback) => {
|
||||
ipcRenderer.on('show-first-launch-update', (event, data) => callback(data));
|
||||
},
|
||||
onFirstLaunchWelcome: (callback) => {
|
||||
ipcRenderer.on('show-first-launch-welcome', () => callback());
|
||||
},
|
||||
onFirstLaunchProgress: (callback) => {
|
||||
ipcRenderer.on('first-launch-progress', (event, data) => callback(data));
|
||||
},
|
||||
onLockPlayButton: (callback) => {
|
||||
ipcRenderer.on('lock-play-button', (event, locked) => callback(locked));
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user