function updateGameLogic() { // =============================================== // 1. GESCHWINDIGKEIT // =============================================== // Wir nutzen den lokalen Score für die Geschwindigkeit let currentSpeed = BASE_SPEED + (currentTick / 1000.0) * 1.5; if (currentSpeed > 36.0) currentSpeed = 36.0; updateParticles(); player.prevY = player.y; obstacleBuffer.forEach(o => o.prevX = o.x); platformBuffer.forEach(p => p.prevX = p.x); // =============================================== // 2. SPIELER PHYSIK (CLIENT PREDICTION) // =============================================== const originalHeight = 50; const crouchHeight = 25; // Hitbox & Y-Pos anpassen player.h = isCrouching ? crouchHeight : originalHeight; let drawY = isCrouching ? player.y + (originalHeight - crouchHeight) : player.y; // Alte Position (für One-Way Check) const oldY = player.y; // Physik player.vy += GRAVITY; if (isCrouching && !player.grounded) player.vy += 2.0; let newY = player.y + player.vy; let landed = false; // --- PLATTFORMEN --- if (player.vy > 0) { for (let plat of platformBuffer) { // Nur relevante Plattformen prüfen if (plat.x < GAME_WIDTH + 100 && plat.x > -100) { if (player.x + 30 > plat.x && player.x < plat.x + plat.w) { // "Passed Check": Vorher drüber, jetzt drauf/drunter const feetOld = oldY + originalHeight; const feetNew = newY + originalHeight; if (feetOld <= plat.y && feetNew >= plat.y) { newY = plat.y - originalHeight; player.vy = 0; landed = true; sendPhysicsSync(newY, 0); break; } } } } } // --- BODEN --- if (!landed && newY + originalHeight >= GROUND_Y) { newY = GROUND_Y - originalHeight; player.vy = 0; landed = true; } if (currentTick % 10 === 0) { sendPhysicsSync(player.y, player.vy); } player.y = newY; player.grounded = landed; // =============================================== // 3. PUFFER BEWEGEN (STREAMING) // =============================================== obstacleBuffer.forEach(o => o.x -= currentSpeed); platformBuffer.forEach(p => p.x -= currentSpeed); // Aufräumen (Links raus) obstacleBuffer = obstacleBuffer.filter(o => o.x + (o.w||30) > -200); // Muss -200 sein platformBuffer = platformBuffer.filter(p => p.x + (p.w||100) > -200); // Muss -200 sein // =============================================== // 4. KOLLISION & TRANSFER (LOGIK + RENDER LISTE) // =============================================== obstacles = []; platforms = []; const RENDER_LIMIT = 900; // Hitbox definieren (für lokale Prüfung) const pHitbox = { x: player.x, y: drawY, w: player.w, h: player.h }; // --- HINDERNISSE --- obstacleBuffer.forEach(obs => { // Nur verarbeiten, wenn im Sichtbereich if (obs.x < RENDER_LIMIT) { // A. Metadaten laden (falls noch nicht da) if (!obs.def) { let baseDef = null; if(gameConfig && gameConfig.obstacles) { baseDef = gameConfig.obstacles.find(x => x.id === obs.id); } obs.def = { id: obs.id, type: obs.type || (baseDef ? baseDef.type : "obstacle"), width: obs.w || (baseDef ? baseDef.width : 30), height: obs.h || (baseDef ? baseDef.height : 30), color: obs.color || (baseDef ? baseDef.color : "red"), image: baseDef ? baseDef.image : null, imgScale: baseDef ? baseDef.imgScale : 1.0, imgOffsetX: baseDef ? baseDef.imgOffsetX : 0, imgOffsetY: baseDef ? baseDef.imgOffsetY : 0 }; } // B. Kollision prüfen (Nur wenn noch nicht eingesammelt) // Wir nutzen 'obs.collected' als Flag, damit wir Coins nicht doppelt zählen if (!obs.collected && !isGameOver) { if (checkCollision(pHitbox, obs)) { const type = obs.def.type; const id = obs.def.id; // 1. COIN if (type === "coin") { score += 2000; // Sofort addieren! obs.collected = true; // Markieren als "weg" playSound('coin'); spawnParticles(obs.x + 15, obs.y + 15, 'sparkle', 10); } // 2. POWERUP else if (type === "powerup") { if (id === "p_god") godModeLives = 3; if (id === "p_bat") hasBat = true; if (id === "p_boot") bootTicks = 600; // ca. 10 Sekunden playSound('powerup'); spawnParticles(obs.x + 15, obs.y + 15, 'sparkle', 20); // Mehr Partikel obs.collected = true; // Markieren als "weg" } // 3. GEGNER (Teacher/Obstacle) else { // Baseballschläger vs Lehrer if (hasBat && type === "teacher") { hasBat = false; obs.collected = true; // Wegschlagen playSound('hit'); spawnParticles(obs.x, obs.y, 'explosion', 5); // Effekt? } // Godmode (Schild) else if (godModeLives > 0) { godModeLives--; // Optional: Gegner entfernen oder durchlaufen lassen? // Hier entfernen wir ihn, damit man nicht 2 Leben im selben Objekt verliert obs.collected = true; } // TOT else { console.log("💥 Kollision!"); player.color = "darkred"; gameOver("Kollision"); playSound('hit'); spawnParticles(player.x + 15, player.y + 25, 'explosion', 50); // Riesige Explosion if (typeof sendInput === "function") sendInput("input", "DEATH"); } } } } // C. Zur Render-Liste hinzufügen (Nur wenn NICHT eingesammelt) if (!obs.collected) { obstacles.push(obs); } } }); // --- PLATTFORMEN --- platformBuffer.forEach(plat => { if (plat.x < RENDER_LIMIT) { platforms.push(plat); } }); } // Helper: Robuste Kollisionsprüfung function checkCollision(p, obs) { const def = obs.def || {}; const w = def.width || obs.w || 30; const h = def.height || obs.h || 30; // Kleines Padding, damit es fair ist const padX = 8; const padY = (def.type === "teacher" || def.type === "principal") ? 20 : 5; // Koordinaten const pL = p.x + padX; const pR = p.x + p.w - padX; const pT = p.y + padY; const pB = p.y + p.h - 5; const oL = obs.x + padX; const oR = obs.x + w - padX; const oT = obs.y + padY; const oB = obs.y + h - 5; return (pR > oL && pL < oR && pB > oT && pT < oB); }