2026-04-15 19:30:01 +02:00

497 lines
15 KiB
JavaScript

// ============================================
// MercyV Loading Screen - Premium Edition
// ============================================
// DOM Elements
const progressFill = document.getElementById('progress-fill');
const loadingPercent = document.querySelector('.loading-percent');
const loadingStatus = document.getElementById('loading-status');
const backgroundMusic = document.getElementById('background-music');
const playBtn = document.getElementById('play-btn');
const playIcon = document.getElementById('play-icon');
const prevBtn = document.getElementById('prev-btn');
const nextBtn = document.getElementById('next-btn');
const volumeBtn = document.getElementById('volume-btn');
const volumeIcon = document.getElementById('volume-icon');
const volumeSlider = document.getElementById('volume-slider');
const trackName = document.getElementById('track-name');
const visualizer = document.querySelector('.audio-visualizer');
// Config references
const loadingMessages = CONFIG.loadingMessages;
const tracks = CONFIG.tracks;
let currentTrack = 0;
let isPlaying = false;
let isMuted = false;
let lastVolume = 50;
// ============================================
// Particle System
// ============================================
class ParticleSystem {
constructor(canvas) {
this.canvas = canvas;
this.ctx = canvas.getContext('2d');
this.particles = [];
this.resize();
window.addEventListener('resize', () => this.resize());
this.init(35);
this.animate();
}
resize() {
this.canvas.width = window.innerWidth;
this.canvas.height = window.innerHeight;
}
init(count) {
const colors = ['255,107,53', '255,154,86', '255,193,7', '255,180,120'];
for (let i = 0; i < count; i++) {
this.particles.push({
x: Math.random() * this.canvas.width,
y: Math.random() * this.canvas.height,
radius: Math.random() * 2.5 + 1,
color: colors[Math.floor(Math.random() * colors.length)],
alpha: Math.random() * 0.25 + 0.05,
vx: (Math.random() - 0.5) * 0.3,
vy: -(Math.random() * 0.4 + 0.1),
pulse: Math.random() * Math.PI * 2
});
}
}
animate() {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
for (const p of this.particles) {
p.x += p.vx;
p.y += p.vy;
p.pulse += 0.02;
const alpha = p.alpha * (0.7 + 0.3 * Math.sin(p.pulse));
if (p.y < -10) p.y = this.canvas.height + 10;
if (p.x < -10) p.x = this.canvas.width + 10;
if (p.x > this.canvas.width + 10) p.x = -10;
this.ctx.beginPath();
this.ctx.arc(p.x, p.y, p.radius, 0, Math.PI * 2);
this.ctx.fillStyle = `rgba(${p.color},${alpha})`;
this.ctx.shadowBlur = 15;
this.ctx.shadowColor = `rgba(${p.color},${alpha * 0.8})`;
this.ctx.fill();
}
this.ctx.shadowBlur = 0;
requestAnimationFrame(() => this.animate());
}
}
// ============================================
// Mouse Parallax
// ============================================
function initParallax() {
const logo = document.querySelector('.logo');
const glow = document.querySelector('.ambient-glow');
document.addEventListener('mousemove', (e) => {
const x = (e.clientX / window.innerWidth - 0.5) * 2;
const y = (e.clientY / window.innerHeight - 0.5) * 2;
logo.style.transform = `translate(${x * 5}px, ${y * 3}px)`;
glow.style.transform = `translate(calc(-50% + ${x * 15}px), calc(-60% + ${y * 10}px))`;
});
}
// ============================================
// Progress Enhancements
// ============================================
let displayedPercent = 0;
let percentAnimFrame = null;
function animatePercentage(target) {
const targetFloor = Math.floor(target);
if (displayedPercent === targetFloor) return;
if (percentAnimFrame) cancelAnimationFrame(percentAnimFrame);
const step = () => {
if (displayedPercent < targetFloor) {
displayedPercent++;
loadingPercent.textContent = displayedPercent + '%';
percentAnimFrame = requestAnimationFrame(step);
} else {
percentAnimFrame = null;
}
};
percentAnimFrame = requestAnimationFrame(step);
}
// ============================================
// Initialize
// ============================================
document.addEventListener('DOMContentLoaded', () => {
initAudio();
initFiveMHandlers();
initParallax();
initRotatingContent();
initClock();
// Particle System
const particleCanvas = document.getElementById('particles');
if (particleCanvas) {
new ParticleSystem(particleCanvas);
}
// Volume slider fill
volumeSlider.style.setProperty('--volume-pct', volumeSlider.value + '%');
});
// ============================================
// Audio
// ============================================
function initAudio() {
backgroundMusic.volume = volumeSlider.value / 100;
if (!backgroundMusic.paused) {
isPlaying = true;
playIcon.classList.remove('fa-play');
playIcon.classList.add('fa-pause');
if (visualizer) visualizer.classList.remove('paused');
} else {
if (visualizer) visualizer.classList.add('paused');
backgroundMusic.play().then(() => {
isPlaying = true;
playIcon.classList.remove('fa-play');
playIcon.classList.add('fa-pause');
if (visualizer) visualizer.classList.remove('paused');
}).catch(() => {});
}
}
function loadTrack(index) {
if (index >= 0 && index < tracks.length) {
currentTrack = index;
backgroundMusic.src = tracks[currentTrack].src;
trackName.textContent = tracks[currentTrack].name;
if (isPlaying) {
backgroundMusic.play().catch(() => {});
}
}
}
// Play/Pause Toggle
playBtn.addEventListener('click', () => {
if (isPlaying) {
pauseMusic();
} else {
playMusic();
}
});
function playMusic() {
backgroundMusic.play().then(() => {
isPlaying = true;
playIcon.classList.remove('fa-play');
playIcon.classList.add('fa-pause');
if (visualizer) visualizer.classList.remove('paused');
}).catch(error => {
console.log('Autoplay blocked:', error);
});
}
function pauseMusic() {
backgroundMusic.pause();
isPlaying = false;
playIcon.classList.remove('fa-pause');
playIcon.classList.add('fa-play');
if (visualizer) visualizer.classList.add('paused');
}
// Previous Track
prevBtn.addEventListener('click', () => {
let newIndex = currentTrack - 1;
if (newIndex < 0) newIndex = tracks.length - 1;
loadTrack(newIndex);
});
// Next Track
nextBtn.addEventListener('click', () => {
let newIndex = currentTrack + 1;
if (newIndex >= tracks.length) newIndex = 0;
loadTrack(newIndex);
});
// Track ended - play next
backgroundMusic.addEventListener('ended', () => {
let newIndex = currentTrack + 1;
if (newIndex >= tracks.length) newIndex = 0;
loadTrack(newIndex);
if (isPlaying) {
backgroundMusic.play().catch(() => {});
}
});
// Volume Control
volumeSlider.addEventListener('input', (e) => {
const volume = e.target.value;
backgroundMusic.volume = volume / 100;
lastVolume = volume;
updateVolumeIcon(volume);
e.target.style.setProperty('--volume-pct', volume + '%');
if (volume > 0 && isMuted) {
isMuted = false;
}
});
volumeBtn.addEventListener('click', () => {
if (isMuted) {
backgroundMusic.volume = lastVolume / 100;
volumeSlider.value = lastVolume;
isMuted = false;
updateVolumeIcon(lastVolume);
volumeSlider.style.setProperty('--volume-pct', lastVolume + '%');
} else {
lastVolume = volumeSlider.value;
backgroundMusic.volume = 0;
volumeSlider.value = 0;
isMuted = true;
updateVolumeIcon(0);
volumeSlider.style.setProperty('--volume-pct', '0%');
}
});
function updateVolumeIcon(volume) {
volumeIcon.classList.remove('fa-volume-up', 'fa-volume-down', 'fa-volume-mute', 'fa-volume-off');
if (volume == 0) {
volumeIcon.classList.add('fa-volume-mute');
} else if (volume < 30) {
volumeIcon.classList.add('fa-volume-off');
} else if (volume < 70) {
volumeIcon.classList.add('fa-volume-down');
} else {
volumeIcon.classList.add('fa-volume-up');
}
}
// ============================================
// FiveM Loading Screen Handlers
// ============================================
function initFiveMHandlers() {
// FiveM sendet message events - nutze diese um Audio zu starten
window.addEventListener('message', () => {
if (!isPlaying) {
backgroundMusic.play().then(() => {
isPlaying = true;
playIcon.classList.remove('fa-play');
playIcon.classList.add('fa-pause');
if (visualizer) visualizer.classList.remove('paused');
}).catch(() => {});
}
}, { once: true });
// Handler fuer Ladefortschritt
const handlers = {
startInitFunction(data) {
loadingStatus.textContent = data.type === 'INIT_SESSION' ? 'Session wird initialisiert...' : 'Lade ' + (data.type || 'Daten') + '...';
},
startInitFunctionOrder() {},
initFunctionInvoking(data) {
loadingStatus.textContent = 'Lade ' + (data.name || 'Module') + '...';
},
initFunctionInvoked() {},
startDataFileEntries(data) {
loadingStatus.textContent = 'Lade Daten: ' + (data.count || 0) + ' Einträge...';
},
onDataFileEntry() {},
endDataFileEntries() {
loadingStatus.textContent = 'Daten geladen...';
},
performMapLoadFunction(data) {
loadingStatus.textContent = 'Lade Map: ' + (data.idx + 1) + '/' + data.count;
},
onLogLine() {}
};
window.addEventListener('message', (event) => {
const handler = handlers[event.data.eventName];
if (handler) {
handler(event.data);
}
});
// Ladefortschritt ueber loadProgress Event
let loadProgress = 0;
const updateProgress = (progress) => {
loadProgress = Math.min(Math.max(loadProgress, progress), 100);
progressFill.style.width = `${loadProgress}%`;
animatePercentage(loadProgress);
const messageIndex = Math.min(Math.floor(loadProgress / 10), loadingMessages.length - 1);
if (messageIndex >= 0) {
loadingStatus.textContent = loadingMessages[messageIndex];
}
if (loadProgress >= 100) {
loadingStatus.textContent = 'Bereit zum Spielen!';
document.querySelector('.loading-section').classList.add('complete');
}
};
// FiveM loadProgress Handler
window.addEventListener('message', (event) => {
if (event.data.eventName === 'loadProgress') {
updateProgress(event.data.loadFraction * 100);
}
});
// Funktion, um die Spielerzahl direkt vom Server abzufragen
async function fetchPlayerCount() {
try {
// Ersetze dies mit deiner Server-IP und dem Port (Standard ist 30120)
// Wenn der Loadingscreen lokal auf dem Server läuft, klappt oft auch ein relativer Pfad oder die Server-Domain
const response = await fetch('http://DEINE_SERVER_IP:30120/dynamic.json');
const data = await response.json();
const el = document.querySelector('.player-count');
if (el) {
// data.clients gibt die aktuelle Spielerzahl aus der dynamic.json zurück
const maxPlayers = data.sv_maxclients || CONFIG.maxPlayers || 200;
el.textContent = `${data.clients}/${maxPlayers} Spieler Online`;
}
} catch (error) {
console.error("Fehler beim Abrufen der Spielerzahl:", error);
}
}
// Führe die Funktion aus, sobald der Loadingscreen lädt
fetchPlayerCount();
// Optional: Alle 10 Sekunden aktualisieren
setInterval(fetchPlayerCount, 10000);
// Expose for testing in browser console
window.updateProgress = updateProgress;
}
// ============================================
// Rotating Tips
// ============================================
function initRotatingContent() {
const changelogText = document.querySelector('.changelog-text');
let changelogIndex = 0;
// Set initial player count from config
const playerCount = document.querySelector('.player-count');
if (playerCount) {
playerCount.textContent = `0/${CONFIG.maxPlayers} Spieler Online`;
}
if (changelogText && CONFIG.changelogs.length > 0) {
setInterval(() => {
changelogText.classList.add('fade-out');
setTimeout(() => {
changelogIndex = (changelogIndex + 1) % CONFIG.changelogs.length;
changelogText.textContent = CONFIG.changelogs[changelogIndex];
changelogText.classList.remove('fade-out');
}, 400);
}, CONFIG.changelogInterval);
}
}
// ============================================
// Clock
// ============================================
function initClock() {
const clockEl = document.getElementById('clock');
if (!clockEl) return;
const update = () => {
const now = new Date();
const h = String(now.getHours()).padStart(2, '0');
const m = String(now.getMinutes()).padStart(2, '0');
clockEl.textContent = `${h}:${m}`;
};
update();
setInterval(update, 1000);
}
// ============================================
// URL Handler (FiveM compatible)
// ============================================
function openUrl(url) {
try {
if (typeof invokeNative === 'function') {
invokeNative('openUrl', url);
} else {
window.open(url, '_blank');
}
} catch (e) {
window.open(url, '_blank');
}
}
// ============================================
// Handle visibility change
// ============================================
document.addEventListener('visibilitychange', () => {
if (document.hidden && isPlaying) {
// Optional: pause when tab is hidden
}
});
// ============================================
// Keyboard shortcuts
// ============================================
document.addEventListener('keydown', (e) => {
switch(e.code) {
case 'Space':
e.preventDefault();
playBtn.click();
break;
case 'ArrowRight':
nextBtn.click();
break;
case 'ArrowLeft':
prevBtn.click();
break;
case 'ArrowUp':
e.preventDefault();
volumeSlider.value = Math.min(100, parseInt(volumeSlider.value) + 10);
volumeSlider.dispatchEvent(new Event('input'));
break;
case 'ArrowDown':
e.preventDefault();
volumeSlider.value = Math.max(0, parseInt(volumeSlider.value) - 10);
volumeSlider.dispatchEvent(new Event('input'));
break;
case 'KeyM':
volumeBtn.click();
break;
}
});