function updateGameLogic() { 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); const originalHeight = 50; const crouchHeight = 25; player.h = isCrouching ? crouchHeight : originalHeight; let drawY = isCrouching ? player.y + (originalHeight - crouchHeight) : player.y; const oldY = player.y; player.vy += GRAVITY; if (isCrouching && !player.grounded) player.vy += 2.0; let newY = player.y + player.vy; let landed = false; if (player.vy > 0) { for (let plat of platformBuffer) { if (plat.x < GAME_WIDTH + 100 && plat.x > -100) { if (player.x + 30 > plat.x && player.x < plat.x + plat.w) { 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; } } } } } 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; obstacleBuffer.forEach(o => o.x -= currentSpeed); platformBuffer.forEach(p => p.x -= currentSpeed); 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 obstacles = []; platforms = []; const RENDER_LIMIT = 900; const pHitbox = { x: player.x, y: drawY, w: player.w, h: player.h }; obstacleBuffer.forEach(obs => { if (obs.x < RENDER_LIMIT) { 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 }; } 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; obs.collected = true; 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; playSound('powerup'); spawnParticles(obs.x + 15, obs.y + 15, 'sparkle', 20); // Mehr Partikel obs.collected = true; } else { if (hasBat && type === "teacher") { hasBat = false; obs.collected = true; playSound('hit'); spawnParticles(obs.x, obs.y, 'explosion', 5); } // Godmode (Schild) else if (godModeLives > 0) { godModeLives--; playSound('hit'); spawnParticles(obs.x, obs.y, 'explosion', 5); 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"); } } } } if (!obs.collected) { obstacles.push(obs); } } }); platformBuffer.forEach(plat => { if (plat.x < RENDER_LIMIT) { platforms.push(plat); } }); } function checkCollision(p, obs) { const def = obs.def || {}; const w = def.width || obs.w || 30; const h = def.height || obs.h || 30; const padX = 8; const padY = (def.type === "teacher" || def.type === "principal") ? 20 : 5; 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); }