/* FiveRP — loading screen behaviour. The game drives this page by posting messages at `window`. The only field that is a genuine 0..1 measure of progress is `loadProgress.loadFraction`; everything else is narration we turn into the phase and detail lines. */ (function () { 'use strict'; const elPct = document.getElementById('pct'); const elFill = document.getElementById('fill'); const elPhase = document.getElementById('phase'); const elDetail = document.getElementById('detail'); const elTip = document.getElementById('tip'); /* ------------------------------------------------------------- progress */ // Shown and target values are kept apart so the number eases toward the // truth instead of jumping. The game reports progress in coarse steps. let shown = 0; let target = 0; function setTarget(fraction) { const pct = Math.max(0, Math.min(100, fraction * 100)); // Never walk backwards: a later phase reporting a lower fraction would // otherwise make the bar visibly retreat. if (pct > target) target = pct; } function tick() { const gap = target - shown; if (gap > 0.05) { shown += Math.max(gap * 0.08, 0.05); if (shown > target) shown = target; elFill.style.width = shown.toFixed(2) + '%'; elPct.textContent = Math.floor(shown); } requestAnimationFrame(tick); } requestAnimationFrame(tick); /* ---------------------------------------------------------------- phases */ function phase(text) { if (elPhase.textContent !== text) elPhase.textContent = text; } function detail(text) { const clean = String(text || '').trim(); if (!clean) return; elDetail.textContent = clean.length > 88 ? clean.slice(0, 87) + '…' : clean; } // Init function names arrive as engine internals ("CPathServer::InitSession"). // Strip the noise so the line reads as a status, not a stack trace. function humanise(name) { return String(name || '') .replace(/^dlc_?/i, 'DLC ') .replace(/::.*$/, '') .replace(/^C(?=[A-Z])/, '') .replace(/([a-z])([A-Z])/g, '$1 $2'); } window.addEventListener('message', function (event) { const data = event.data || {}; switch (data.eventName) { case 'loadProgress': setTarget(Number(data.loadFraction) || 0); break; case 'startInitFunctionOrder': phase('Loading game'); break; case 'startInitFunction': phase('Loading game'); break; case 'initFunctionInvoking': phase('Loading game'); detail(humanise(data.name)); // The engine gives idx/count per order; use it when loadProgress is // quiet so the bar keeps creeping during long orders. if (data.count) setTarget(0.1 + 0.5 * (Number(data.idx) / Number(data.count))); break; case 'startDataFileEntries': phase('Streaming content'); detail('Mounting ' + (data.count || 0) + ' data files'); break; case 'performMappedDataFileEntry': phase('Streaming content'); detail(String(data.name || '').split('/').pop()); break; case 'onLogLine': detail(data.message); break; case 'onDataFileEntry': phase('Streaming content'); break; } }); /* ------------------------------------------------------------------ tips */ const TIPS = [ 'Your account and your character are separate — one login can hold your identity for good.', 'Names are issued once. Pick a first and last name you want to be known by.', 'Press F8 at any time to open the console if something looks wrong.', 'Your passport is proof of residency — officers may ask to see it.', 'Use a real email address. It is the only way to recover an account.', 'Speak in character. The city is more fun when everyone stays in the story.', 'Lost? The Legion Square spawn is the heart of downtown Los Santos.' ]; let tipIndex = 0; setInterval(function () { tipIndex = (tipIndex + 1) % TIPS.length; elTip.classList.add('is-swapping'); setTimeout(function () { elTip.textContent = TIPS[tipIndex]; elTip.classList.remove('is-swapping'); }, 450); }, 7000); /* Nothing has reported in yet — creep to a believable floor so the screen never sits at a dead 0% while the engine warms up. */ setTimeout(function () { setTarget(0.04); }, 400); })();