// ============================================ // 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; } });