function updateGameLogic() { // 1. Input Logging (Ducken) if (isCrouching) { inputLog.push({ t: currentTick - lastSentTick, act: "DUCK" }); } // 2. Geschwindigkeit (Basiert auf ZEIT/Ticks, nicht Score!) let currentSpeed = 5 + (currentTick / 3000.0) * 0.5; if (currentSpeed > 12.0) currentSpeed = 12.0; // 3. Spieler Physik & Größe const originalHeight = 50; const crouchHeight = 25; player.h = isCrouching ? crouchHeight : originalHeight; let drawY = isCrouching ? player.y + (originalHeight - crouchHeight) : player.y; player.vy += GRAVITY; if (isCrouching && !player.grounded) player.vy += 2.0; // Fast Fall player.y += player.vy; if (player.y + originalHeight >= GROUND_Y) { player.y = GROUND_Y - originalHeight; player.vy = 0; player.grounded = true; } else { player.grounded = false; } // 4. Hindernisse Bewegen & Kollision let nextObstacles = []; for (let obs of obstacles) { obs.x -= currentSpeed; // Aufräumen, wenn links raus if (obs.x + obs.def.width < -50.0) continue; // --- PASSED CHECK (Wichtig!) --- // Wenn das Hindernis den Spieler schon passiert hat, ignorieren wir Kollisionen. // Das verhindert "Geister-Treffer" von hinten durch CCD. const paddingX = 10; const realRightEdge = obs.x + obs.def.width - paddingX; // Spieler ist bei 50. Wir geben 5px Puffer. if (realRightEdge < 55) { nextObstacles.push(obs); // Behalten, aber keine Kollisionsprüfung mehr continue; } // ------------------------------- // Kollisionsprüfung const playerHitbox = { x: player.x, y: drawY, w: player.w, h: player.h }; if (checkCollision(playerHitbox, obs)) { // A. COIN if (obs.def.type === "coin") { score += 2000; continue; // Entfernen } // B. POWERUP else if (obs.def.type === "powerup") { if (obs.def.id === "p_god") godModeLives = 3; if (obs.def.id === "p_bat") hasBat = true; if (obs.def.id === "p_boot") bootTicks = 600; lastPowerupTick = currentTick; // Für Sync merken continue; // Entfernen } // C. GEGNER else { if (hasBat && obs.def.type === "teacher") { hasBat = false; continue; // Zerstört } if (godModeLives > 0) { godModeLives--; continue; // Geschützt } player.color = "darkred"; if (!isGameOver) { sendChunk(); gameOver("Kollision"); } } } nextObstacles.push(obs); } obstacles = nextObstacles; // 5. Spawning (Zeitbasiert & Synchron) // Fallback für Init if (typeof nextSpawnTick === 'undefined' || nextSpawnTick === 0) { nextSpawnTick = currentTick + 50; } if (currentTick >= nextSpawnTick && gameConfig) { // A. Nächsten Termin berechnen const gapPixel = Math.floor(400 + rng.nextRange(0, 500)); const ticksToWait = Math.floor(gapPixel / currentSpeed); nextSpawnTick = currentTick + ticksToWait; // B. Position setzen (Fix rechts außen) let spawnX = GAME_WIDTH + 50; // C. Objekt auswählen const isBossPhase = (currentTick % 1500) > 1200; let possibleObs = []; gameConfig.obstacles.forEach(def => { if (isBossPhase) { if (def.id === "principal" || def.id === "trashcan") possibleObs.push(def); } else { if (def.id === "principal") return; // Eraser erst ab Tick 3000 if (def.id === "eraser" && currentTick < 3000) return; possibleObs.push(def); } }); let def = rng.pick(possibleObs); // RNG Sync: Speech let speech = null; if (def && def.canTalk) { if (rng.nextFloat() > 0.7) speech = rng.pick(def.speechLines); } // RNG Sync: Powerup Rarity if (def && def.type === "powerup") { if (rng.nextFloat() > 0.1) def = null; } if (def) { const yOffset = def.yOffset || 0; obstacles.push({ x: spawnX, y: GROUND_Y - def.height - yOffset, def: def, speech: speech }); } } } function checkCollision(p, obs) { const paddingX = 10; const paddingY_Top = (obs.def.type === "teacher") ? 25 : 10; const paddingY_Bottom = 5; // Speed-basierte Hitbox-Erweiterung (CCD) // Wir schätzen den Speed hier, damit er ungefähr dem Server entspricht let currentSpeed = 5 + (currentTick / 3000.0) * 0.5; if (currentSpeed > 12.0) currentSpeed = 12.0; const pLeft = p.x + paddingX; const pRight = p.x + p.w - paddingX; const pTop = p.y + paddingY_Top; const pBottom = p.y + p.h - paddingY_Bottom; const oLeft = obs.x + paddingX; // Wir erweitern die Hitbox nach rechts um die Geschwindigkeit, // um schnelle Durchschüsse zu verhindern. const oRight = obs.x + obs.def.width - paddingX + currentSpeed; const oTop = obs.y + paddingY_Top; const oBottom = obs.y + obs.def.height - paddingY_Bottom; return (pRight > oLeft && pLeft < oRight && pBottom > oTop && pTop < oBottom); }