import { t, setLanguage, currentLang } from './i18n.js'; import { Assets, Sprite, NineSliceSprite, Container, TilingSprite, Text, wordWrap, fragmentGPUTemplate, Graphics } from 'pixi.js'; import { loginUser, registerUser, verifyEmailUser, resendOTPCode, requestPasswordResetUser, resetPasswordUser, refreshAccessTokenUser } from './api/auth.js'; import { initDropdowns, updateTranslations } from './ui/dropdowns.js'; import { initForms, formState, setOtpMode, setResetPasswordMode, startVisualCountdown, setRegisterMode } from './ui/forms.js'; import { fetchSectorsList, joinSectorUser } from './api/sectors.js'; import { app, initPixi, setupResize, triggerResize } from './core/pixi-app.js'; import { buildLoginScene, LOGIN_COLORS } from './scenes/login-scene.js'; import { buildLobbyBase, createSectorTabPixi, createSectorCardPixi, buildOnboardingModalPixi } from './scenes/lobby-scene.js'; let globalSheet = null; let background = null; let uiContainer = null; let lobbyPixiContainer = null; let sectorPixiContainer = null; let sectorTabsPixiContainer = null; let footerBackground = null; let footerPattern = null; let footerBackgroundContainer = null; let onboardingPixiContainer = null; let onboardingPixiRefs = null; let selectedSectorId = null; let selectedOnboardingGender = 'male'; async function initGame() { await initPixi('pixi-container'); try { const assetsToLoad = { background: 'src/raw-assets/backgrounds/Background_A.png', uiAtlas: 'src/assets/ui-atlas.json' }; const backgroundTexture = await Assets.load(assetsToLoad.background); const uiSpritesheet = await Assets.load(assetsToLoad.uiAtlas); await document.fonts.load('16px "UI Font"'); await document.fonts.load('16px "Logo Font"'); updateTranslations(); buildScene(backgroundTexture, uiSpritesheet); document.getElementById('username').placeholder = t('ph_username_reg'); document.getElementById('email').placeholder = t('ph_email'); document.getElementById('password').placeholder = t('ph_password'); document.getElementById('btn-register').textContent = t('btn_register'); await checkAuthOnStartup(); } catch (error) { console.error("Fehler beim Laden der Assets:", error); } } function buildScene(bgTexture, sheet) { globalSheet = sheet; // --- Hintergrund --- background = new Sprite(bgTexture); app.stage.addChild(background); // --- Haupt-UI-Container --- uiContainer = new Container(); app.stage.addChild(uiContainer); lobbyPixiContainer = new Container(); lobbyPixiContainer.visible = false; app.stage.addChild(lobbyPixiContainer); sectorPixiContainer = buildLobbyBase(lobbyPixiContainer, sheet); const htmlSectorList = document.getElementById('sector-list-container'); if (htmlSectorList) { htmlSectorList.addEventListener('scroll', (e) => { sectorPixiContainer.y = -e.target.scrollTop; }); } // --- Footer Hintergund --- footerBackgroundContainer = new Container(); footerBackground = new TilingSprite({ texture: sheet.textures['SPR_SciFiMenus_Background_01'], height: 40, }); footerBackground.tileScale.set(1.0); footerBackground.tint = 0x000000; footerBackground.alpha = 1.0; footerBackgroundContainer.addChild(footerBackground); footerPattern = new TilingSprite({ texture: sheet.textures['SPR_SciFiMenus_Background_Hexagons_01'], height: 40, }); footerPattern.tileScale.set(0.3); footerPattern.tint = LOGIN_COLORS.PATTERN; footerPattern.alpha = 0.01; footerBackgroundContainer.addChild(footerPattern); app.stage.addChild(footerBackgroundContainer); onboardingPixiRefs = buildOnboardingModalPixi(app.stage, sheet); onboardingPixiContainer = onboardingPixiRefs.container; const loginRefs = buildLoginScene(uiContainer, sheet); initForms({ tabLogin: loginRefs.tabLogin, tabRegister: loginRefs.tabRegister, inputUser: loginRefs.inputUser, inputEmail: loginRefs.inputEmail, inputLock: loginRefs.inputLock, btnRegister: loginRefs.btnRegister, discordIcon: loginRefs.discordIcon, discordText: loginRefs.discordText, COLOR_TAB_ACTIVE: LOGIN_COLORS.TAB_ACTIVE, COLOR_TAB_INACTIVE: LOGIN_COLORS.TAB_INACTIVE, COLOR_TAB_HOVER: LOGIN_COLORS.TAB_HOVER, COLOR_BTN_HOVER: LOGIN_COLORS.BTN_HOVER, COLOR_BTN_DEFAULT: LOGIN_COLORS.BTN_DEFAULT }); setupResize({ background, discordContainer: loginRefs.discordContainer, logoContainer: loginRefs.logoContainer, uiContainer, panelFrame: loginRefs.panelFrame, lobbyPixiContainer, footerBackground, footerPattern, footerBackgroundContainer, onboardingPixiContainer }); formState.isLoginMode = true; setRegisterMode(); } const form = document.getElementById('register-form'); const msgBox = document.getElementById('msg-box'); form.addEventListener('submit', async (e) => { e.preventDefault(); const usernameVal = document.getElementById('username').value; const emailVal = document.getElementById('email').value; const passwordVal = document.getElementById('password').value; msgBox.textContent = '...'; msgBox.style.color = 'var(--color-text-main)'; if (formState.isForgotPasswordMode) { try { const result = await requestPasswordResetUser(emailVal); if (!result.ok) { msgBox.style.color = 'var(--color-error)'; msgBox.textContent = t(result.data.error) || t('ERR_INTERNAL_SERVER'); return; } msgBox.style.color = 'var(--color-green-bright)'; msgBox.textContent = t(result.data.message); setTimeout(() => { setResetPasswordMode(emailVal); }, 1500); } catch (error) { msgBox.style.color = 'var(--color-error)'; msgBox.textContent = t('ERR_INTERNAL_SERVER'); } return; } if (formState.isResetPasswordMode) { try { const result = await resetPasswordUser(formState.resetEmail, emailVal, passwordVal); if (!result.ok) { msgBox.style.color = 'var(--color-error)'; msgBox.textContent = t(result.data.error) || t('ERR_INTERNAL_SERVER'); return; } msgBox.style.color = 'var(--color-green-bright)'; msgBox.textContent = t(result.data.message); setTimeout(() => { window.location.reload(); }, 1500); } catch (error) { msgBox.style.color = 'var(--color-error)'; msgBox.textContent = t('ERR_INTERNAL_SERVER'); } return; } if (formState.isOtpMode) { try { const result = await verifyEmailUser(formState.pendingVerificationEmail, passwordVal); if (!result.ok) { msgBox.style.color = 'var(--color-error)'; msgBox.textContent = t(result.data.error) || t('ERR_INTERNAL_SERVER'); return; } msgBox.style.color = 'var(--color-green-bright)'; msgBox.textContent = t('msg_verify_success'); setTimeout(() => { window.location.reload(); }, 1500); } catch (error) { msgBox.style.color = 'var(--color-error)'; msgBox.textContent = t('ERR_INTERNAL_SERVER'); } return; } if (formState.isLoginMode) { try { const result = await loginUser(usernameVal, passwordVal); if (!result.ok) { msgBox.style.color = 'var(--color-error)'; msgBox.textContent = t(result.data.error) || t('ERR_INTERNAL_SERVER'); if (result.data.error === 'ERR_EMAIL_NOT_VERIFIED') { setTimeout(() => { setOtpMode(result.data.email); }, 1500); } return; } msgBox.style.color = 'var(--color-green-bright)'; msgBox.textContent = t(result.data.message); const rememberMe = document.getElementById('remember').checked; sessionStorage.setItem('void_access_token', result.data.accessToken); sessionStorage.setItem('void_user', JSON.stringify(result.data.user)); if (rememberMe) { localStorage.setItem('void_refresh_token', result.data.refreshToken); sessionStorage.removeItem('void_refresh_token'); } else { sessionStorage.setItem('void_refresh_token', result.data.refreshToken); localStorage.removeItem('void_refresh_token'); } transitionToLobby(); } catch (error) { msgBox.style.color = 'var(--color-error)'; msgBox.textContent = t('ERR_INTERNAL_SERVER'); } } else { // Register Mode try { const result = await registerUser(usernameVal, emailVal, passwordVal); if (!result.ok) { msgBox.style.color = 'var(--color-error)'; msgBox.textContent = t(result.data.error)|| t('ERR_INTERNAL_SERVER'); return; } msgBox.style.color = 'var(--color-green-bright)'; msgBox.textContent = t(result.data.message); setTimeout(() => { setOtpMode(emailVal); }, 1500); } catch (error) { msgBox.style.color = 'var(--color-error)'; msgBox.textContent = t('ERR_INTERNAL_SERVER'); } } }); const resendOtpLink = document.getElementById('resend-otp-link'); if (resendOtpLink) { resendOtpLink.addEventListener('click', async (e) => { e.preventDefault(); if (!pendingVerificationEmail) return; msgBox.textContent = '...'; msgBox.style.color = 'var(--color-text-main)'; try { const result = await resendOTPCode(pendingVerificationEmail); if (!result.ok) { msgBox.style.color = 'var(--color-error)'; msgBox.textContent = t(result.data.error) || t('ERR_INTERNAL_SERVER'); return; } msgBox.style.color = 'var(--color-green-bright)'; msgBox.textContent = t(result.data.message); startVisualCountdown(resendOtpLink, 'link_resend_otp', 120); } catch (error) { msgBox.style.color = 'var(--color-error)'; msgBox.textContent = t('ERR_INTERNAL_SERVER'); } }); } /** * Diese Funktion wird aufgerufenm wenn der Login erfolgreich war und die Tokens * sicher gespeichert sind, Sie baut das HTML-Formulat ab und bereitet PixiJS * auf die Lobby-Ansicht vor */ async function transitionToLobby() { const formElement = document.getElementById('register-form'); if (formElement) { formElement.style.display = 'none'; } uiContainer.visible = false; lobbyPixiContainer.visible = true; try { const lobbyBGPath = 'src/raw-assets/backgrounds/SPR_SciFiMenus_Example_Image_SpookyComputer_01.png'; const lobbyBGTexture = await Assets.load(lobbyBGPath); background.texture = lobbyBGTexture; triggerResize(); buildLobbyUI(); } catch (error) { console.error("Fehler beim Laden des Lobby-Hintergrunds:", error); } } /** * Blendet die Top-Bar ein, setzt den Spilernamen und aktiviert den Logout */ async function buildLobbyUI() { const userDropdown = document.getElementById('user-dropdown'); if (userDropdown) { userDropdown.style.display = 'block'; } const userDataStr = sessionStorage.getItem('void_user'); if (userDataStr) { try { const userData = JSON.parse(userDataStr); const usernameEl = document.getElementById('lobby-username'); if (usernameEl && userData.username) { usernameEl.textContent = userData.username; } const emailEl = document.getElementById('lobby-email'); if (emailEl && userData.email) { emailEl.textContent = userData.email; } const premiumEl = document.getElementById('lobby-premium'); if (premiumEl && userData.res_premium !== undefined) { premiumEl.textContent = userData.res_premium; } } catch (e) { console.error("Fehler beim Auslesen der User-Daten:", e); } } await loadAndRenderSectorBrowser(); } /** * Läd die Sektordaten von der API und steuert die Tab-Sichtbarkeiten */ async function loadAndRenderSectorBrowser() { const token = sessionStorage.getItem('void_access_token') || localStorage.getItem('void_access_token'); if (!token) return; try { const result = await fetchSectorsList(token); if (!result.ok || !result.data.success) { console.error("Fehler beim Laden der Sektoren:", result.data.error); return; } const data = result.data; const layer = document.getElementById('sector-browser-layer'); layer.style.display = 'block'; const tabLastSector = document.getElementById('tab-last-sector'); const tabMySectors = document.getElementById('tab-my-sectors'); const tabNewSectors = document.getElementById('tab-new-sectors'); if (sectorTabsPixiContainer) { lobbyPixiContainer.removeChild(sectorTabsPixiContainer); sectorTabsPixiContainer.destroy({ children: true }); } sectorTabsPixiContainer = new Container(); lobbyPixiContainer.addChild(sectorTabsPixiContainer); const isMobilePortrait = window.innerWidth <= 800 && window.innerHeight > window.innerWidth; const centerPos = isMobilePortrait ? 325 : 620; if (data.mySectors.length === 0) { // Szenario A: Keine eigenen Sektoren tabLastSector.style.display = 'none'; tabMySectors.style.display = 'none'; tabNewSectors.classList.add('active'); const singleTabX = centerPos - 90; const tabNewBg = createSectorTabPixi(globalSheet, singleTabX, 20, LOGIN_COLORS.TAB_ACTIVE, 0.6); // ACHTUNG: Hier jetzt .container verwenden! sectorTabsPixiContainer.addChild(tabNewBg.container); renderSectorList(data.newSectors, 'new'); } else { // Szenario B: Eigene Sektoren vorhanden tabLastSector.style.display = 'inline-block'; tabMySectors.style.display = 'inline-block'; tabNewSectors.style.display = 'inline-block'; // --- NEU: Daten für die Tabs filtern und aufteilen --- // 1. "Zuletzt gespielt" // Da das Backend bereits nach Datum absteigend sortiert, ist das Element // an Index 0 garantiert der Sektor, in dem der Spieler zuletzt aktiv war. // Wir packen ihn in ein neues Array, da unsere renderSectorList-Funktion ein Array erwartet. const lastPlayedSectorArray = [data.mySectors[0]]; // 2. "Meine Sektoren" (Der Rest) // Wir nutzen die JavaScript-Funktion 'slice(1)', um alle Sektoren ab dem zweiten Element (Index 1) // bis zum Ende des Arrays zu kopieren. // Hinweis: Wenn der Spieler insgesamt nur 1 Sektor besitzt, gibt slice(1) automatisch // ein leeres Array [] zurück, was perfekt ist! const otherMySectorsArray = data.mySectors.slice(1); // --- PixiJS-Hintergründe berechnen (unverändert) --- const leftTabX = centerPos - 290; const midTabX = centerPos - 90; const rightTabX = centerPos + 110; const tabLastBg = createSectorTabPixi(globalSheet, leftTabX, 20, LOGIN_COLORS.TAB_ACTIVE, 0.6); const tabMyBg = createSectorTabPixi(globalSheet, midTabX, 20, LOGIN_COLORS.TAB_INACTIVE, 0.4); const tabNewBg = createSectorTabPixi(globalSheet, rightTabX, 20, LOGIN_COLORS.TAB_INACTIVE, 0.4); sectorTabsPixiContainer.addChild(tabLastBg.container, tabMyBg.container, tabNewBg.container); const updateTabVisuals = (activeHtmlId) => { tabLastSector.classList.remove('active'); tabMySectors.classList.remove('active'); tabNewSectors.classList.remove('active'); document.getElementById(activeHtmlId).classList.add('active'); tabLastBg.fill.tint = LOGIN_COLORS.TAB_INACTIVE; tabLastBg.fill.alpha = 0.4; tabMyBg.fill.tint = LOGIN_COLORS.TAB_INACTIVE; tabMyBg.fill.alpha = 0.4; tabNewBg.fill.tint = LOGIN_COLORS.TAB_INACTIVE; tabNewBg.fill.alpha = 0.4; if (activeHtmlId === 'tab-last-sector') { tabLastBg.fill.tint = LOGIN_COLORS.TAB_ACTIVE; tabLastBg.fill.alpha = 0.6; } if (activeHtmlId === 'tab-my-sectors') { tabMyBg.fill.tint = LOGIN_COLORS.TAB_ACTIVE; tabMyBg.fill.alpha = 0.6; } if (activeHtmlId === 'tab-new-sectors') { tabNewBg.fill.tint = LOGIN_COLORS.TAB_ACTIVE; tabNewBg.fill.alpha = 0.6; } }; // --- ÄNDERUNG: Klick-Events mit den gefilterten Arrays versorgen --- tabLastSector.onclick = () => { updateTabVisuals('tab-last-sector'); // Wir übergeben hier ausschließlich das Array mit dem EINEN, letzten Sektor renderSectorList(lastPlayedSectorArray, 'my'); }; tabMySectors.onclick = () => { updateTabVisuals('tab-my-sectors'); // Wir übergeben hier alle Sektoren AUSSER dem letzten renderSectorList(otherMySectorsArray, 'my'); }; tabNewSectors.onclick = () => { updateTabVisuals('tab-new-sectors'); // Neue Sektoren bleiben natürlich unangetastet renderSectorList(data.newSectors, 'new'); }; const setupTabHover = (htmlEl, pixiRef) => { htmlEl.onmouseenter = () => { if (!htmlEl.classList.contains('active')) { pixiRef.fill.tint = LOGIN_COLORS.TAB_HOVER; pixiRef.fill.alpha = 0.8; } }; htmlEl.onmouseleave = () => { if (!htmlEl.classList.contains('active')) { pixiRef.fill.tint = LOGIN_COLORS.TAB_INACTIVE; pixiRef.fill.alpha = 0.4; } }; }; setupTabHover(tabLastSector, tabLastBg); setupTabHover(tabMySectors, tabMyBg); setupTabHover(tabNewSectors, tabNewBg); // --- ÄNDERUNG: Initialer Aufruf --- // Wenn die Lobby lädt, starten wir standardmäßig im "Zuletzt gespielt"-Tab, // also übergeben wir auch hier das gefilterte 1-Element-Array. renderSectorList(lastPlayedSectorArray, 'my'); } } catch (error) { console.error("Netzwerkfehler beim Sektor-Abruf:", error); } } /** * Rendert die HTML-Karte und die dazugehöhrigen PixiJS-Hintergrundboxen */ function renderSectorList(sectors, type) { const listContainer = document.getElementById('sector-list-container'); listContainer.innerHTML = ''; if (sectorPixiContainer) { sectorPixiContainer.removeChildren(); sectorPixiContainer.y = 0; } const htmlSectorList = document.getElementById('sector-list-container'); if (htmlSectorList) htmlSectorList.scrollTop = 0; if (sectors.length === 0) { listContainer.innerHTML = '
Keine Sektoren in dieser Kategorie verfügbar.
'; return; } sectors.forEach((sector, index) => { const cardLink = document.createElement('a'); cardLink.className = 'sector-card-link'; cardLink.href = `#`; // --- Temporäre Platzhalter für das neue UI (ersetzen wir später durch echte DB-Werte) --- const ageDays = sector.age_days || Math.floor(Math.random() * 30) + 1; const totalPlayers = sector.total_players || Math.floor(Math.random() * 500) + 150; let bottomRowHtml = ''; if (type === 'my' && sector.local_username) { bottomRowHtml = `