mirror of
https://git.sanhost.net/sanasol/hytale-f2p
synced 2026-02-27 00:31:47 -03:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0b1716c168 | ||
|
|
9628363455 | ||
|
|
4932a7a51c | ||
|
|
5170f453ea | ||
|
|
db3b2fc966 | ||
|
|
2f5820e850 | ||
|
|
4abb455e0f | ||
|
|
d5828463f9 |
3
.github/CODE_OF_CONDUCT.md
vendored
3
.github/CODE_OF_CONDUCT.md
vendored
@@ -36,7 +36,8 @@ This Code of Conduct applies within all community spaces, and also applies when
|
|||||||
|
|
||||||
## Enforcement
|
## Enforcement
|
||||||
|
|
||||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at [Discord Server, message Founders/Devs](https://discord.gg/Fhbb9Yk5WW). All complaints will be reviewed and investigated promptly and fairly.
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at [Community Chat, message Founders/Devs](https://chat.sanhost.net/invite/Tfz4jCK4).
|
||||||
|
<!-- Discord: https://discord.gg/Fhbb9Yk5WW --> All complaints will be reviewed and investigated promptly and fairly.
|
||||||
|
|
||||||
All community leaders are obligated to respect the privacy and security of the reporter of any incident.
|
All community leaders are obligated to respect the privacy and security of the reporter of any incident.
|
||||||
|
|
||||||
|
|||||||
2
.github/ISSUE_TEMPLATE/support_request.yml
vendored
2
.github/ISSUE_TEMPLATE/support_request.yml
vendored
@@ -22,7 +22,7 @@ body:
|
|||||||
value: |
|
value: |
|
||||||
If you need help or support with using the launcher, please fill out this support request.
|
If you need help or support with using the launcher, please fill out this support request.
|
||||||
Provide as much detail as possible so we can assist you effectively.
|
Provide as much detail as possible so we can assist you effectively.
|
||||||
**Need a quick assistance?** Please Open-A-Ticket in our [Discord Server](https://discord.gg/Fhbb9Yk5WW)!
|
**Need a quick assistance?** Join our [Community Chat](https://chat.sanhost.net/invite/Tfz4jCK4) or [Telegram](https://t.me/hf2p_og)!
|
||||||
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: question
|
id: question
|
||||||
|
|||||||
@@ -57,8 +57,8 @@
|
|||||||
<span class="nav-tooltip">Logs</span>
|
<span class="nav-tooltip">Logs</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="nav-item" onclick="openDiscordExternal()">
|
<div class="nav-item" onclick="openDiscordExternal()">
|
||||||
<i class="fab fa-discord"></i>
|
<i class="fas fa-comments"></i>
|
||||||
<span class="nav-tooltip">Discord</span>
|
<span class="nav-tooltip">Community Chat</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -199,6 +199,21 @@
|
|||||||
<i class="fas fa-play"></i>
|
<i class="fas fa-play"></i>
|
||||||
<span data-i18n="play.playButton">PLAY HYTALE</span>
|
<span data-i18n="play.playButton">PLAY HYTALE</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<div style="display: flex; justify-content: center; align-items: center; gap: 8px; margin-top: 12px; font-size: 12px;">
|
||||||
|
<span style="color: #93a3b8;">Telegram:</span>
|
||||||
|
<a href="#" onclick="window.electronAPI?.openExternal('https://t.me/sanhostnet'); return false;" style="color: #93a3b8; text-decoration: none; display: flex; align-items: center; gap: 4px; transition: color 0.2s;" onmouseover="this.style.color='#60a5fa'" onmouseout="this.style.color='#93a3b8'">
|
||||||
|
<i class="fas fa-users"></i> Group
|
||||||
|
</a>
|
||||||
|
<span style="color: #4b5563;">|</span>
|
||||||
|
<a href="#" onclick="window.electronAPI?.openExternal('https://t.me/hf2p_og'); return false;" style="color: #93a3b8; text-decoration: none; display: flex; align-items: center; gap: 4px; transition: color 0.2s;" onmouseover="this.style.color='#60a5fa'" onmouseout="this.style.color='#93a3b8'">
|
||||||
|
<i class="fab fa-telegram"></i> Channel
|
||||||
|
</a>
|
||||||
|
<span style="color: #4b5563;">|</span>
|
||||||
|
<a href="#" onclick="openDiscordExternal(); return false;" style="color: #93a3b8; text-decoration: none; display: flex; align-items: center; gap: 4px; transition: color 0.2s;" onmouseover="this.style.color='#60a5fa'" onmouseout="this.style.color='#93a3b8'">
|
||||||
|
<i class="fas fa-comments"></i> Community Chat
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -663,7 +678,11 @@
|
|||||||
<i class="fas fa-times"></i>
|
<i class="fas fa-times"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="mods-modal-body">
|
<div class="mods-modal-body" style="padding-top: 0;">
|
||||||
|
<div class="mods-search-container" style="margin: 1.5rem; margin-bottom: 1rem;">
|
||||||
|
<i class="fas fa-search"></i>
|
||||||
|
<input type="text" id="myModsSearch" placeholder="Search installed mods..." class="mods-search" />
|
||||||
|
</div>
|
||||||
<div id="installedModsList" class="installed-mods-list">
|
<div id="installedModsList" class="installed-mods-list">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -833,14 +852,14 @@
|
|||||||
<div class="modal-content discord-popup-modal">
|
<div class="modal-content discord-popup-modal">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<div class="discord-popup-header">
|
<div class="discord-popup-header">
|
||||||
<i class="fab fa-discord"></i>
|
<i class="fas fa-comments"></i>
|
||||||
<h2 class="modal-title">Join Our Discord Community</h2>
|
<h2 class="modal-title">Join Our Community</h2>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="discord-popup-body">
|
<div class="discord-popup-body">
|
||||||
<p class="discord-popup-text">
|
<p class="discord-popup-text">
|
||||||
Join our community of over <strong>5000 members</strong> and stay connected!
|
Join our community and stay connected!
|
||||||
</p>
|
</p>
|
||||||
<p class="discord-popup-text">
|
<p class="discord-popup-text">
|
||||||
Get the latest news, updates, and announcements about the launcher.
|
Get the latest news, updates, and announcements about the launcher.
|
||||||
@@ -851,8 +870,8 @@
|
|||||||
|
|
||||||
<div class="discord-popup-actions">
|
<div class="discord-popup-actions">
|
||||||
<button class="discord-popup-btn primary" onclick="joinDiscord()">
|
<button class="discord-popup-btn primary" onclick="joinDiscord()">
|
||||||
<i class="fab fa-discord"></i>
|
<i class="fas fa-comments"></i>
|
||||||
Join Discord
|
Join Community Chat
|
||||||
</button>
|
</button>
|
||||||
<button class="discord-popup-btn secondary" onclick="closeDiscordPopup()">
|
<button class="discord-popup-btn secondary" onclick="closeDiscordPopup()">
|
||||||
Maybe Later
|
Maybe Later
|
||||||
@@ -867,6 +886,25 @@
|
|||||||
<script src="js/featured.js"></script>
|
<script src="js/featured.js"></script>
|
||||||
<script type="module" src="js/settings.js"></script>
|
<script type="module" src="js/settings.js"></script>
|
||||||
<script type="module" src="js/update.js"></script>
|
<script type="module" src="js/update.js"></script>
|
||||||
|
|
||||||
|
<!-- Version Selection Modal (Isolated Container) -->
|
||||||
|
<div id="versionSelectModal" class="modal-overlay" style="display: none; position: fixed; inset: 0; z-index: 9999; background: rgba(0, 0, 0, 0.7); backdrop-filter: blur(5px); align-items: center; justify-content: center;">
|
||||||
|
<div class="glass-panel" style="width: 100%; max-width: 600px; max-height: 80vh; display: flex; flex-direction: column; border-radius: 12px; overflow: hidden; margin: 20px;">
|
||||||
|
<div class="modal-header" style="display: flex; justify-content: space-between; align-items: center; padding: 1.5rem; border-bottom: 1px solid rgba(255, 255, 255, 0.1);">
|
||||||
|
<h3 style="margin: 0; font-size: 1.25rem;">Select Version</h3>
|
||||||
|
<button id="closeVersionModal" class="modal-close" style="background: none; border: none; color: #a0a0a0; font-size: 1.25rem; cursor: pointer;"><i class="fas fa-times"></i></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body" style="padding: 1.5rem; overflow-y: auto;">
|
||||||
|
<div id="versionList" class="version-list-container">
|
||||||
|
<div class="loading-versions" style="display: flex; flex-direction: column; align-items: center; gap: 1rem; color: #a0a0a0;">
|
||||||
|
<i class="fas fa-spinner fa-spin fa-2x"></i>
|
||||||
|
<span>Loading versions...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- updater.js disabled - using update.js instead which has skip button and macOS handling -->
|
<!-- updater.js disabled - using update.js instead which has skip button and macOS handling -->
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|||||||
184
GUI/js/mods.js
184
GUI/js/mods.js
@@ -49,6 +49,18 @@ function setupModsEventListeners() {
|
|||||||
closeModalBtn.addEventListener('click', closeMyModsModal);
|
closeModalBtn.addEventListener('click', closeMyModsModal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const myModsSearchInput = document.getElementById('myModsSearch');
|
||||||
|
if (myModsSearchInput) {
|
||||||
|
let myModsSearchTimeout;
|
||||||
|
myModsSearchInput.addEventListener('input', (e) => {
|
||||||
|
const query = e.target.value.toLowerCase().trim();
|
||||||
|
clearTimeout(myModsSearchTimeout);
|
||||||
|
myModsSearchTimeout = setTimeout(() => {
|
||||||
|
filterInstalledMods(query);
|
||||||
|
}, 300);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const modal = document.getElementById('myModsModal');
|
const modal = document.getElementById('myModsModal');
|
||||||
if (modal) {
|
if (modal) {
|
||||||
modal.addEventListener('click', (e) => {
|
modal.addEventListener('click', (e) => {
|
||||||
@@ -78,12 +90,30 @@ function setupModsEventListeners() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const browseContainer = document.getElementById('browseModsList');
|
||||||
|
if (browseContainer) {
|
||||||
|
browseContainer.addEventListener('click', (e) => {
|
||||||
|
const installBtn = e.target.closest('[data-install-mod-id]');
|
||||||
|
if (installBtn) {
|
||||||
|
const modId = installBtn.getAttribute('data-install-mod-id');
|
||||||
|
const mod = browseMods.find(m => m.id == modId);
|
||||||
|
if (mod) {
|
||||||
|
openVersionSelectModal(mod);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function openMyModsModal() {
|
function openMyModsModal() {
|
||||||
const modal = document.getElementById('myModsModal');
|
const modal = document.getElementById('myModsModal');
|
||||||
if (modal) {
|
if (modal) {
|
||||||
modal.classList.add('active');
|
modal.classList.add('active');
|
||||||
|
const searchInput = document.getElementById('myModsSearch');
|
||||||
|
if (searchInput) {
|
||||||
|
searchInput.value = '';
|
||||||
|
}
|
||||||
loadInstalledMods();
|
loadInstalledMods();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -92,6 +122,10 @@ function closeMyModsModal() {
|
|||||||
const modal = document.getElementById('myModsModal');
|
const modal = document.getElementById('myModsModal');
|
||||||
if (modal) {
|
if (modal) {
|
||||||
modal.classList.remove('active');
|
modal.classList.remove('active');
|
||||||
|
const searchInput = document.getElementById('myModsSearch');
|
||||||
|
if (searchInput) {
|
||||||
|
searchInput.value = '';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,19 +147,39 @@ async function loadInstalledMods() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function filterInstalledMods(query) {
|
||||||
|
if (!query || query === '') {
|
||||||
|
displayInstalledMods(installedMods);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const filtered = installedMods.filter(mod => {
|
||||||
|
const nameMatch = mod.name?.toLowerCase().includes(query);
|
||||||
|
const fileNameMatch = mod.fileName?.toLowerCase().includes(query);
|
||||||
|
const descriptionMatch = mod.description?.toLowerCase().includes(query);
|
||||||
|
const authorMatch = mod.author?.toLowerCase().includes(query);
|
||||||
|
return nameMatch || fileNameMatch || descriptionMatch || authorMatch;
|
||||||
|
});
|
||||||
|
|
||||||
|
displayInstalledMods(filtered);
|
||||||
|
}
|
||||||
|
|
||||||
function displayInstalledMods(mods) {
|
function displayInstalledMods(mods) {
|
||||||
const modsContainer = document.getElementById('installedModsList');
|
const modsContainer = document.getElementById('installedModsList');
|
||||||
if (!modsContainer) return;
|
if (!modsContainer) return;
|
||||||
|
|
||||||
if (mods.length === 0) {
|
if (mods.length === 0) {
|
||||||
|
const searchInput = document.getElementById('myModsSearch');
|
||||||
|
const isSearching = searchInput && searchInput.value.trim() !== '';
|
||||||
|
|
||||||
modsContainer.innerHTML = `
|
modsContainer.innerHTML = `
|
||||||
<div class=\"empty-installed-mods\">
|
<div class=\"empty-installed-mods\">
|
||||||
<i class=\"fas fa-box-open\"></i>
|
<i class=\"fas fa-${isSearching ? 'search' : 'box-open'}\"></i>
|
||||||
<h4 data-i18n="mods.noModsInstalled">No Mods Installed</h4>
|
<h4 data-i18n="${isSearching ? 'mods.noModsFound' : 'mods.noModsInstalled'}">${isSearching ? 'No Mods Found' : 'No Mods Installed'}</h4>
|
||||||
<p data-i18n="mods.noModsInstalledDesc">Add mods from CurseForge or import local files</p>
|
<p data-i18n="${isSearching ? 'mods.noModsFoundDesc' : 'mods.noModsInstalledDesc'}">${isSearching ? 'Try a different search term' : 'Add mods from CurseForge or import local files'}</p>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
if (window.i18n) {
|
if (window.i18n && !isSearching) {
|
||||||
const container = modsContainer.querySelector('.empty-installed-mods');
|
const container = modsContainer.querySelector('.empty-installed-mods');
|
||||||
container.querySelector('h4').textContent = window.i18n.t('mods.noModsInstalled');
|
container.querySelector('h4').textContent = window.i18n.t('mods.noModsInstalled');
|
||||||
container.querySelector('p').textContent = window.i18n.t('mods.noModsInstalledDesc');
|
container.querySelector('p').textContent = window.i18n.t('mods.noModsInstalledDesc');
|
||||||
@@ -165,7 +219,7 @@ function createInstalledModCard(mod) {
|
|||||||
<div class="installed-mod-info">
|
<div class="installed-mod-info">
|
||||||
<div class="installed-mod-header">
|
<div class="installed-mod-header">
|
||||||
<h4 class="installed-mod-name">${mod.name}</h4>
|
<h4 class="installed-mod-name">${mod.name}</h4>
|
||||||
<span class="installed-mod-version">v${mod.version}</span>
|
<span class="installed-mod-version">${mod.fileName || 'v' + mod.version}</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="installed-mod-description">${mod.description || (window.i18n ? window.i18n.t('mods.noDescription') : 'No description available')}</p>
|
<p class="installed-mod-description">${mod.description || (window.i18n ? window.i18n.t('mods.noDescription') : 'No description available')}</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -295,13 +349,6 @@ function displayBrowseMods(mods) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
browseContainer.innerHTML = mods.map(mod => createBrowseModCard(mod)).join('');
|
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) {
|
function createBrowseModCard(mod) {
|
||||||
@@ -350,12 +397,12 @@ function createBrowseModCard(mod) {
|
|||||||
${window.i18n ? window.i18n.t('mods.view') : 'VIEW'}
|
${window.i18n ? window.i18n.t('mods.view') : 'VIEW'}
|
||||||
</button>
|
</button>
|
||||||
${!isInstalled ?
|
${!isInstalled ?
|
||||||
`<button id="install-${mod.id}" class="mod-btn-toggle bg-primary text-black hover:bg-primary/80">
|
`<button data-install-mod-id=\"${mod.id}\" class=\"mod-btn-toggle bg-primary text-black hover:bg-primary/80\">
|
||||||
<i class="fas fa-download"></i>
|
<i class=\"fas fa-download\"></i>
|
||||||
${window.i18n ? window.i18n.t('mods.install') : 'INSTALL'}
|
${window.i18n ? window.i18n.t('mods.install') : 'INSTALL'}
|
||||||
</button>` :
|
</button>` :
|
||||||
`<button class="mod-btn-toggle bg-white/10 text-white" disabled>
|
`<button class=\"mod-btn-toggle bg-white/10 text-white\" disabled>
|
||||||
<i class="fas fa-check"></i>
|
<i class=\"fas fa-check\"></i>
|
||||||
${window.i18n ? window.i18n.t('mods.installed') : 'INSTALLED'}
|
${window.i18n ? window.i18n.t('mods.installed') : 'INSTALLED'}
|
||||||
</button>`
|
</button>`
|
||||||
}
|
}
|
||||||
@@ -364,6 +411,104 @@ function createBrowseModCard(mod) {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let currentSelectedMod = null;
|
||||||
|
|
||||||
|
function openVersionSelectModal(mod) {
|
||||||
|
currentSelectedMod = mod;
|
||||||
|
const modal = document.getElementById('versionSelectModal');
|
||||||
|
const closeBtn = document.getElementById('closeVersionModal');
|
||||||
|
const versionList = document.getElementById('versionList');
|
||||||
|
|
||||||
|
if (modal) {
|
||||||
|
modal.style.display = 'flex';
|
||||||
|
modal.classList.add('active');
|
||||||
|
|
||||||
|
const closeHandler = () => {
|
||||||
|
modal.classList.remove('active');
|
||||||
|
setTimeout(() => {
|
||||||
|
modal.style.display = 'none';
|
||||||
|
}, 300);
|
||||||
|
currentSelectedMod = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (closeBtn) {
|
||||||
|
closeBtn.onclick = closeHandler;
|
||||||
|
}
|
||||||
|
modal.onclick = (e) => {
|
||||||
|
if (e.target === modal) closeHandler();
|
||||||
|
};
|
||||||
|
|
||||||
|
loadModVersions(mod.id, versionList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadModVersions(modId, container) {
|
||||||
|
container.innerHTML = `
|
||||||
|
<div class="loading-versions">
|
||||||
|
<i class="fas fa-spinner fa-spin fa-2x" style="margin-bottom: 10px; display: block;"></i>
|
||||||
|
<span>Loading versions...</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const versions = await window.electronAPI.getModFiles(modId);
|
||||||
|
|
||||||
|
if (!versions || versions.length === 0) {
|
||||||
|
container.innerHTML = `<div class="p-4 text-center text-gray-400" style="padding: 2rem;">No versions found for this mod.</div>`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort versions by date desc (API returns desc but ensure)
|
||||||
|
versions.sort((a, b) => new Date(b.fileDate) - new Date(a.fileDate));
|
||||||
|
|
||||||
|
container.innerHTML = versions.map(file => `
|
||||||
|
<div class="version-item">
|
||||||
|
<div class="version-info">
|
||||||
|
<div class="version-name">${file.displayName}</div>
|
||||||
|
<div class="version-meta">
|
||||||
|
<span><i class="fas fa-calendar"></i> ${new Date(file.fileDate).toLocaleDateString()}</span>
|
||||||
|
<span><i class="fas fa-download"></i> ${formatNumber(file.downloadCount)}</span>
|
||||||
|
<span><i class="fas fa-file-archive"></i> ${(file.fileLength / 1024 / 1024).toFixed(2)} MB</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="version-actions">
|
||||||
|
<button class="btn-install" data-file-id="${file.id}">
|
||||||
|
Install
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`).join('');
|
||||||
|
|
||||||
|
// Add event listeners securely
|
||||||
|
container.querySelectorAll('.btn-install').forEach((btn, index) => {
|
||||||
|
const file = versions[index]; // Map index to file data
|
||||||
|
btn.onclick = () => installVersion(file);
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading versions:', error);
|
||||||
|
container.innerHTML = `<div class="p-4 text-center text-red-400" style="padding: 2rem;">Error loading versions.<br><small>${error.message}</small></div>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function installVersion(file) {
|
||||||
|
if (!currentSelectedMod) return;
|
||||||
|
|
||||||
|
const modal = document.getElementById('versionSelectModal');
|
||||||
|
modal.style.display = 'none';
|
||||||
|
|
||||||
|
const modInfo = {
|
||||||
|
...currentSelectedMod,
|
||||||
|
fileId: file.id,
|
||||||
|
downloadUrl: file.downloadUrl,
|
||||||
|
fileName: file.fileName,
|
||||||
|
fileSize: file.fileLength
|
||||||
|
};
|
||||||
|
|
||||||
|
await downloadAndInstallMod(modInfo);
|
||||||
|
currentSelectedMod = null;
|
||||||
|
}
|
||||||
|
|
||||||
async function downloadAndInstallMod(modInfo) {
|
async function downloadAndInstallMod(modInfo) {
|
||||||
try {
|
try {
|
||||||
const downloadMsg = window.i18n ? window.i18n.t('notifications.modsDownloading').replace('{name}', modInfo.name) : `Downloading ${modInfo.name}...`;
|
const downloadMsg = window.i18n ? window.i18n.t('notifications.modsDownloading').replace('{name}', modInfo.name) : `Downloading ${modInfo.name}...`;
|
||||||
@@ -762,7 +907,10 @@ window.modsManager = {
|
|||||||
closeMyModsModal,
|
closeMyModsModal,
|
||||||
viewModPage,
|
viewModPage,
|
||||||
loadInstalledMods,
|
loadInstalledMods,
|
||||||
loadBrowseMods
|
loadBrowseMods,
|
||||||
|
openVersionSelectModal
|
||||||
};
|
};
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', initModsManager);
|
// Remove auto-init since we are now calling it from script.js explicitly
|
||||||
|
// which guarantees order and environment readiness
|
||||||
|
// document.addEventListener('DOMContentLoaded', initModsManager);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import './ui.js';
|
|||||||
import './install.js';
|
import './install.js';
|
||||||
import './launcher.js';
|
import './launcher.js';
|
||||||
import './news.js';
|
import './news.js';
|
||||||
import './mods.js';
|
import { initModsManager } from './mods.js';
|
||||||
import './players.js';
|
import './players.js';
|
||||||
import './settings.js';
|
import './settings.js';
|
||||||
import './logs.js';
|
import './logs.js';
|
||||||
@@ -15,6 +15,12 @@ let i18nInitialized = false;
|
|||||||
|
|
||||||
if (document.readyState === 'complete' || document.readyState === 'interactive') {
|
if (document.readyState === 'complete' || document.readyState === 'interactive') {
|
||||||
updateLanguageSelector();
|
updateLanguageSelector();
|
||||||
|
initModsManager();
|
||||||
|
} else {
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
updateLanguageSelector();
|
||||||
|
initModsManager();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
@@ -53,7 +59,8 @@ window.closeDiscordPopup = function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
window.joinDiscord = async function() {
|
window.joinDiscord = async function() {
|
||||||
await window.electronAPI?.openExternal('https://discord.gg/Fhbb9Yk5WW');
|
// await window.electronAPI?.openExternal('https://discord.gg/Fhbb9Yk5WW');
|
||||||
|
await window.electronAPI?.openExternal('https://chat.sanhost.net/invite/Tfz4jCK4');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await window.electronAPI?.saveConfig({ discordPopup: true });
|
await window.electronAPI?.saveConfig({ discordPopup: true });
|
||||||
|
|||||||
@@ -1103,7 +1103,8 @@ function getRetryContextMessage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
window.openDiscordExternal = function() {
|
window.openDiscordExternal = function() {
|
||||||
window.electronAPI?.openExternal('https://discord.gg/Fhbb9Yk5WW');
|
// window.electronAPI?.openExternal('https://discord.gg/Fhbb9Yk5WW');
|
||||||
|
window.electronAPI?.openExternal('https://chat.sanhost.net/invite/Tfz4jCK4');
|
||||||
};
|
};
|
||||||
|
|
||||||
window.toggleMaximize = toggleMaximize;
|
window.toggleMaximize = toggleMaximize;
|
||||||
|
|||||||
@@ -6610,3 +6610,85 @@ input[type="text"].uuid-input,
|
|||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Version Selection Styles */
|
||||||
|
.version-list-container::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.version-list-container::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.version-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 1rem;
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.version-item:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
transform: translateX(4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.version-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.version-name {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.version-meta {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: #a0a0a0;
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.version-meta span {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.35rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.version-meta i {
|
||||||
|
font-size: 0.8em;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.version-actions .btn-install {
|
||||||
|
padding: 0.5rem 1.25rem;
|
||||||
|
background: linear-gradient(135deg, #3b82f6, #2563eb);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
transition: all 0.2s;
|
||||||
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.version-actions .btn-install:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 6px 8px rgba(0, 0, 0, 0.3);
|
||||||
|
background: linear-gradient(135deg, #4f93f6, #3b82f6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-versions {
|
||||||
|
padding: 3rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
@@ -18,7 +18,8 @@
|
|||||||
|
|
||||||
### ⚠️ **WARNING: READ [QUICK START](#-quick-start) before Downloading & Installing the Launcher!** ⚠️
|
### ⚠️ **WARNING: READ [QUICK START](#-quick-start) before Downloading & Installing the Launcher!** ⚠️
|
||||||
|
|
||||||
#### 🛑 **Found a problem? [Join the HF2P Discord](https://discord.gg/Fhbb9Yk5WW) and head to `#-⚠️-community-help`** 🛑
|
#### 🛑 **Found a problem? [Join the Community Chat](https://chat.sanhost.net/invite/Tfz4jCK4) | [TG Channel](https://t.me/hf2p_og) | [TG Group](https://t.me/sanhostnet)** 🛑
|
||||||
|
<!-- #### 🛑 **Found a problem? [Join the HF2P Discord](https://discord.gg/Fhbb9Yk5WW) and head to `#-⚠️-community-help`** 🛑 -->
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
👍 If you like the project, <b>feel free to support us via Buy Me a Coffee!</b> ☕<br>
|
👍 If you like the project, <b>feel free to support us via Buy Me a Coffee!</b> ☕<br>
|
||||||
@@ -455,7 +456,8 @@ See [BUILD.md](docs/BUILD.md) for comprehensive build instructions.
|
|||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||
**Questions? Ads? Collaboration? Endorsement? Other business-related?**
|
**Questions? Ads? Collaboration? Endorsement? Other business-related?**
|
||||||
Message the founders at https://discord.gg/Fhbb9Yk5WW
|
Message the founders at https://chat.sanhost.net/invite/Tfz4jCK4 | [TG Channel](https://t.me/hf2p_og) | [TG Group](https://t.me/sanhostnet)
|
||||||
|
<!-- Message the founders at https://discord.gg/Fhbb9Yk5WW -->
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
Play with friends online! This guide covers both easy in-game hosting and advanced dedicated server setup.
|
Play with friends online! This guide covers both easy in-game hosting and advanced dedicated server setup.
|
||||||
|
|
||||||
### **DOWNLOAD SERVER FILES (JAR/RAR/SCRIPTS) HERE: https://discord.gg/Fhbb9Yk5WW**
|
### **DOWNLOAD SERVER FILES (JAR/RAR/SCRIPTS) HERE: https://chat.sanhost.net/invite/Tfz4jCK4**
|
||||||
|
<!-- ### **DOWNLOAD SERVER FILES (JAR/RAR/SCRIPTS) HERE: https://discord.gg/Fhbb9Yk5WW** -->
|
||||||
|
|
||||||
**Table of Contents**
|
**Table of Contents**
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# Hytale F2P Launcher - Troubleshooting Guide
|
# Hytale F2P Launcher - Troubleshooting Guide
|
||||||
|
|
||||||
This guide covers common issues and their solutions. If your issue isn't listed here, please check [existing issues](https://github.com/amiayweb/Hytale-F2P/issues) or join our [Discord](https://discord.gg/Fhbb9Yk5WW).
|
This guide covers common issues and their solutions. If your issue isn't listed here, please check [existing issues](https://github.com/amiayweb/Hytale-F2P/issues) or join our [Community Chat](https://chat.sanhost.net/invite/Tfz4jCK4) | [TG Channel](https://t.me/hf2p_og) | [TG Group](https://t.me/sanhostnet).
|
||||||
|
<!-- Discord: https://discord.gg/Fhbb9Yk5WW -->
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -437,7 +438,8 @@ Game sessions have a 10-hour TTL. This is by design for security.
|
|||||||
If your issue isn't resolved by this guide:
|
If your issue isn't resolved by this guide:
|
||||||
|
|
||||||
1. **Check existing issues:** [GitHub Issues](https://github.com/amiayweb/Hytale-F2P/issues)
|
1. **Check existing issues:** [GitHub Issues](https://github.com/amiayweb/Hytale-F2P/issues)
|
||||||
2. **Join Discord:** [discord.gg/Fhbb9Yk5WW](https://discord.gg/Fhbb9Yk5WW)
|
2. **Join Community:** [Chat](https://chat.sanhost.net/invite/Tfz4jCK4) | [Telegram](https://t.me/hf2p_og)
|
||||||
|
<!-- Discord: https://discord.gg/Fhbb9Yk5WW -->
|
||||||
3. **Open a new issue** with:
|
3. **Open a new issue** with:
|
||||||
- Your operating system and version
|
- Your operating system and version
|
||||||
- Launcher version
|
- Launcher version
|
||||||
|
|||||||
@@ -863,7 +863,7 @@ function checkLaunchReady() {
|
|||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
const DEFAULT_WRAPPER_CONFIG = {
|
const DEFAULT_WRAPPER_CONFIG = {
|
||||||
stripFlags: ['-XX:+UseCompactObjectHeaders'],
|
stripFlags: [],
|
||||||
injectArgs: [
|
injectArgs: [
|
||||||
{ arg: '--disable-sentry', condition: 'server' }
|
{ arg: '--disable-sentry', condition: 'server' }
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -84,7 +84,8 @@ const {
|
|||||||
loadInstalledMods,
|
loadInstalledMods,
|
||||||
downloadMod,
|
downloadMod,
|
||||||
uninstallMod,
|
uninstallMod,
|
||||||
toggleMod
|
toggleMod,
|
||||||
|
getModFiles
|
||||||
} = require('./managers/modManager');
|
} = require('./managers/modManager');
|
||||||
|
|
||||||
// Services
|
// Services
|
||||||
@@ -187,6 +188,7 @@ module.exports = {
|
|||||||
downloadMod,
|
downloadMod,
|
||||||
uninstallMod,
|
uninstallMod,
|
||||||
toggleMod,
|
toggleMod,
|
||||||
|
getModFiles,
|
||||||
saveModsToConfig,
|
saveModsToConfig,
|
||||||
loadModsFromConfig,
|
loadModsFromConfig,
|
||||||
|
|
||||||
|
|||||||
@@ -285,6 +285,27 @@ async function toggleMod(modId, modsPath) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function getModFiles(modId) {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`https://api.curseforge.com/v1/mods/${modId}/files`, {
|
||||||
|
headers: {
|
||||||
|
'x-api-key': API_KEY,
|
||||||
|
'Accept': 'application/json'
|
||||||
|
},
|
||||||
|
params: {
|
||||||
|
pageSize: 20,
|
||||||
|
sortOrder: 'desc'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching mod files:', error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function syncModsForCurrentProfile() {
|
async function syncModsForCurrentProfile() {
|
||||||
try {
|
try {
|
||||||
const activeProfile = profileManager.getActiveProfile();
|
const activeProfile = profileManager.getActiveProfile();
|
||||||
@@ -455,5 +476,6 @@ module.exports = {
|
|||||||
syncModsForCurrentProfile,
|
syncModsForCurrentProfile,
|
||||||
generateModId,
|
generateModId,
|
||||||
extractModName,
|
extractModName,
|
||||||
extractVersion
|
extractVersion,
|
||||||
|
getModFiles
|
||||||
};
|
};
|
||||||
16
main.js
16
main.js
@@ -88,8 +88,9 @@ function setDiscordActivity() {
|
|||||||
url: 'https://git.sanhost.net/sanasol/hytale-f2p/releases'
|
url: 'https://git.sanhost.net/sanasol/hytale-f2p/releases'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Discord',
|
label: 'Community',
|
||||||
url: 'https://discord.gg/Fhbb9Yk5WW'
|
// url: 'https://discord.gg/Fhbb9Yk5WW'
|
||||||
|
url: 'https://chat.sanhost.net/invite/Tfz4jCK4'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
@@ -1068,7 +1069,7 @@ ipcMain.handle('load-settings', async () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const { getModsPath, loadInstalledMods, downloadMod, uninstallMod, toggleMod, getCurrentUuid, getAllUuidMappings, setUuidForUser, generateNewUuid, deleteUuidForUser, resetCurrentUserUuid } = require('./backend/launcher');
|
const { getModsPath, loadInstalledMods, downloadMod, uninstallMod, toggleMod, getModFiles, getCurrentUuid, getAllUuidMappings, setUuidForUser, generateNewUuid, deleteUuidForUser, resetCurrentUserUuid } = require('./backend/launcher');
|
||||||
const os = require('os');
|
const os = require('os');
|
||||||
|
|
||||||
ipcMain.handle('get-local-app-data', async () => {
|
ipcMain.handle('get-local-app-data', async () => {
|
||||||
@@ -1118,6 +1119,15 @@ ipcMain.handle('download-mod', async (event, modInfo) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('get-mod-files', async (event, modId) => {
|
||||||
|
try {
|
||||||
|
return await getModFiles(modId);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error getting mod files:', error);
|
||||||
|
return { success: false, error: error.message };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
ipcMain.handle('uninstall-mod', async (event, modId, modsPath) => {
|
ipcMain.handle('uninstall-mod', async (event, modId, modsPath) => {
|
||||||
try {
|
try {
|
||||||
return await uninstallMod(modId, modsPath);
|
return await uninstallMod(modId, modsPath);
|
||||||
|
|||||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "hytale-f2p-launcher",
|
"name": "hytale-f2p-launcher",
|
||||||
"version": "2.2.0",
|
"version": "2.4.2",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "hytale-f2p-launcher",
|
"name": "hytale-f2p-launcher",
|
||||||
"version": "2.2.0",
|
"version": "2.4.2",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"adm-zip": "^0.5.10",
|
"adm-zip": "^0.5.10",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "hytale-f2p-launcher",
|
"name": "hytale-f2p-launcher",
|
||||||
"version": "2.4.1",
|
"version": "2.4.3",
|
||||||
"description": "A modern, cross-platform launcher for Hytale with automatic updates and multi-client support",
|
"description": "A modern, cross-platform launcher for Hytale with automatic updates and multi-client support",
|
||||||
"homepage": "https://git.sanhost.net/sanasol/hytale-f2p",
|
"homepage": "https://git.sanhost.net/sanasol/hytale-f2p",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
@@ -26,7 +26,6 @@
|
|||||||
"electron",
|
"electron",
|
||||||
"auto-update",
|
"auto-update",
|
||||||
"mod-manager"
|
"mod-manager"
|
||||||
|
|
||||||
],
|
],
|
||||||
"maintainers": [
|
"maintainers": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
|||||||
loadInstalledMods: (modsPath) => ipcRenderer.invoke('load-installed-mods', modsPath),
|
loadInstalledMods: (modsPath) => ipcRenderer.invoke('load-installed-mods', modsPath),
|
||||||
downloadMod: (modInfo) => ipcRenderer.invoke('download-mod', modInfo),
|
downloadMod: (modInfo) => ipcRenderer.invoke('download-mod', modInfo),
|
||||||
uninstallMod: (modId, modsPath) => ipcRenderer.invoke('uninstall-mod', modId, modsPath),
|
uninstallMod: (modId, modsPath) => ipcRenderer.invoke('uninstall-mod', modId, modsPath),
|
||||||
|
getModFiles: (modId) => ipcRenderer.invoke('get-mod-files', modId),
|
||||||
toggleMod: (modId, modsPath) => ipcRenderer.invoke('toggle-mod', modId, modsPath),
|
toggleMod: (modId, modsPath) => ipcRenderer.invoke('toggle-mod', modId, modsPath),
|
||||||
selectModFiles: () => ipcRenderer.invoke('select-mod-files'),
|
selectModFiles: () => ipcRenderer.invoke('select-mod-files'),
|
||||||
copyModFile: (sourcePath, modsPath) => ipcRenderer.invoke('copy-mod-file', sourcePath, modsPath),
|
copyModFile: (sourcePath, modsPath) => ipcRenderer.invoke('copy-mod-file', sourcePath, modsPath),
|
||||||
|
|||||||
@@ -128,6 +128,7 @@ your-folder/
|
|||||||
| Out of memory | Set more RAM: `JVM_XMX=4G ./start.sh` |
|
| Out of memory | Set more RAM: `JVM_XMX=4G ./start.sh` |
|
||||||
| Friends can't connect | Forward port 5520 (TCP+UDP) on your router, or use [playit.gg](https://playit.gg) if you can't port forward |
|
| Friends can't connect | Forward port 5520 (TCP+UDP) on your router, or use [playit.gg](https://playit.gg) if you can't port forward |
|
||||||
|
|
||||||
## Discord
|
## Community
|
||||||
|
|
||||||
Need help? Join the community: https://discord.gg/Fhbb9Yk5WW
|
Need help? Join the community: https://chat.sanhost.net/invite/Tfz4jCK4 | TG Channel: https://t.me/hf2p_og | TG Group: https://t.me/sanhostnet
|
||||||
|
<!-- Discord: https://discord.gg/Fhbb9Yk5WW -->
|
||||||
|
|||||||
Reference in New Issue
Block a user