diff --git a/config.go b/config.go index c309a2f..d672d08 100644 --- a/config.go +++ b/config.go @@ -42,7 +42,7 @@ func initGameConfig() { {ID: "desk", Type: "obstacle", Width: 40, Height: 30, Color: "#8B4513", Image: "desk1.png"}, {ID: "teacher", Type: "teacher", Width: 30, Height: 60, Color: "#000080", Image: "teacher1.png", CanTalk: true, SpeechLines: []string{"Halt!", "Handy weg!"}}, {ID: "trashcan", Type: "obstacle", Width: 25, Height: 35, Color: "#555", Image: "trash1.png"}, - {ID: "eraser", Type: "obstacle", Width: 30, Height: 20, Color: "#fff", Image: "eraser1.png", YOffset: 35.0}, + {ID: "eraser", Type: "obstacle", Width: 30, Height: 20, Color: "#fff", Image: "eraser1.png", YOffset: 30.0}, {ID: "principal", Type: "teacher", Width: 40, Height: 70, Color: "#000", Image: "principal1.png", CanTalk: true, SpeechLines: []string{"EXMATRIKULATION!"}}, diff --git a/main.go b/main.go index 5ecd291..d73215c 100644 --- a/main.go +++ b/main.go @@ -34,16 +34,18 @@ func main() { fs := http.FileServer(http.Dir("./static")) http.Handle("/", fs) - http.HandleFunc("/api/config", handleConfig) - http.HandleFunc("/api/start", handleStart) - http.HandleFunc("/api/validate", handleValidate) - http.HandleFunc("/api/submit-name", handleSubmitName) - http.HandleFunc("/api/leaderboard", handleLeaderboard) - http.HandleFunc("/api/claim/delete", handleClaimDelete) + // API Routes (jetzt mit Logger!) + http.HandleFunc("/api/config", Logger(handleConfig)) + http.HandleFunc("/api/start", Logger(handleStart)) + http.HandleFunc("/api/validate", Logger(handleValidate)) + http.HandleFunc("/api/submit-name", Logger(handleSubmitName)) + http.HandleFunc("/api/leaderboard", Logger(handleLeaderboard)) + http.HandleFunc("/api/claim/delete", Logger(handleClaimDelete)) - http.HandleFunc("/admin", BasicAuth(handleAdminPage)) - http.HandleFunc("/api/admin/list", BasicAuth(handleAdminList)) - http.HandleFunc("/api/admin/action", BasicAuth(handleAdminAction)) + // Admin Routes (Logger + BasicAuth kombinieren) + http.HandleFunc("/admin", Logger(BasicAuth(handleAdminPage))) + http.HandleFunc("/api/admin/list", Logger(BasicAuth(handleAdminList))) + http.HandleFunc("/api/admin/action", Logger(BasicAuth(handleAdminAction))) log.Println("🦖 Server läuft auf :8080") log.Fatal(http.ListenAndServe(":8080", nil)) diff --git a/middleware.go b/middleware.go new file mode 100644 index 0000000..85bfd0b --- /dev/null +++ b/middleware.go @@ -0,0 +1,60 @@ +package main + +import ( + "log" + "net/http" + "time" +) + +// Wir bauen unseren eigenen ResponseWriter, der den Status-Code "mitschreibt" +type StatusRecorder struct { + http.ResponseWriter + Status int +} + +// Überschreiben der WriteHeader Methode, um den Code abzufangen +func (r *StatusRecorder) WriteHeader(status int) { + r.Status = status + r.ResponseWriter.WriteHeader(status) +} + +// Die eigentliche Middleware Funktion +func Logger(next http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + // 1. Startzeit messen + start := time.Now() + + // 2. Recorder initialisieren (Standard ist 200 OK) + recorder := &StatusRecorder{ + ResponseWriter: w, + Status: http.StatusOK, + } + + // 3. Den echten Handler aufrufen (mit unserem Recorder) + next(recorder, r) + + // 4. Dauer berechnen + duration := time.Since(start) + + // 5. Loggen + // Format: [METHODE] PFAD | STATUS | DAUER | IP + // Beispiel: [POST] /api/validate | 200 | 1.2ms | 127.0.0.1 + + icon := "✅" + if recorder.Status >= 400 { + icon = "⚠️" + } + if recorder.Status >= 500 { + icon = "🔥" + } + + log.Printf("%s [%s] %s | %d | %v | %s", + icon, + r.Method, + r.URL.Path, + recorder.Status, + duration, + r.RemoteAddr, + ) + } +} diff --git a/simulation.go b/simulation.go index 7b53589..6531c7b 100644 --- a/simulation.go +++ b/simulation.go @@ -3,13 +3,10 @@ package main import ( "encoding/json" "fmt" - "log" - "math" "strconv" ) func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[string]string) (bool, int, []ActiveObstacle) { - posY := parseOr(vals["pos_y"], PlayerYBase) velY := parseOr(vals["vel_y"], 0.0) score := int(parseOr(vals["score"], 0)) @@ -19,9 +16,6 @@ func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[st hasBat := vals["p_has_bat"] == "1" bootTicks := int(parseOr(vals["p_boot_ticks"], 0)) - lastJumpDist := parseOr(vals["ac_last_dist"], 0.0) - suspicionScore := int(parseOr(vals["ac_suspicion"], 0)) - rng := NewRNG(rngStateVal) var obstacles []ActiveObstacle @@ -31,25 +25,12 @@ func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[st obstacles = []ActiveObstacle{} } - jumpCount := 0 - for _, inp := range inputs { - if inp.Act == "JUMP" { - jumpCount++ - } - } - - if jumpCount > 10 { - log.Printf("[%s] 🤖 [ANTI-CHEAT] SPAM DETECTED: %d mal JUMP!", sessionID, jumpCount) - return true, score, obstacles - } - playerDead := false for i := 0; i < totalTicks; i++ { - - currentSpeed := BaseSpeed + (float64(score)/750.0)*0.5 - if currentSpeed > 14.0 { - currentSpeed = 12.0 + currentSpeed := BaseSpeed + (float64(score) / 1000) + if currentSpeed > 10.0 { + currentSpeed = 10.0 } currentJumpPower := JumpPower @@ -72,7 +53,6 @@ func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[st } isGrounded := posY >= PlayerYBase-1.0 - currentHeight := PlayerHeight if isCrouching { currentHeight = PlayerHeight / 2 @@ -83,32 +63,10 @@ func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[st if didJump && isGrounded && !isCrouching { velY = currentJumpPower - - nextObsDist := -1.0 - for _, o := range obstacles { - if o.X > 50.0 { - nextObsDist = o.X - 50.0 - break - } - } - - if nextObsDist > 0 { - diff := math.Abs(nextObsDist - lastJumpDist) - if diff < 0.5 { - suspicionScore++ - log.Printf("[%s] ⚠️ [ANTI-CHEAT] Verdächtiger Sprung! Diff: %.4f | Suspicion: %d", sessionID, diff, suspicionScore) - } else { - if suspicionScore > 0 { - suspicionScore-- - } - } - lastJumpDist = nextObsDist - } } velY += Gravity posY += velY - if posY > PlayerYBase { posY = PlayerYBase velY = 0 @@ -129,52 +87,55 @@ func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[st continue } - paddingX := 5.0 - paddingY_Top := 5.0 - if obs.Type == "teacher" { - paddingY_Top = 5.0 + paddingX := 10.0 + realRightEdge := obs.X + obs.Width - paddingX + + if realRightEdge < 55.0 { + nextObstacles = append(nextObstacles, obs) + if obs.X+obs.Width > rightmostX { + rightmostX = obs.X + obs.Width + } + continue } - paddingY_Bottom := 5.0 + paddingY_Top := 10.0 + if obs.Type == "teacher" { + paddingY_Top = 25.0 + } - pLeft, pRight := 50.0+paddingX, 50.0+60.0-paddingX - pTop, pBottom := hitboxY+paddingY_Top, hitboxY+currentHeight-paddingY_Bottom + pLeft, pRight := 50.0+paddingX, 50.0+30.0-paddingX + pTop, pBottom := hitboxY+paddingY_Top, hitboxY+currentHeight-5.0 - oLeft, oRight := obs.X+paddingX, obs.X+obs.Width-paddingX - oTop, oBottom := obs.Y+paddingY_Top, obs.Y+obs.Height-paddingY_Bottom + oLeft := obs.X + paddingX + oRight := obs.X + obs.Width - paddingX + currentSpeed + oTop, oBottom := obs.Y+paddingY_Top, obs.Y+obs.Height-5.0 - if pRight > oLeft && pLeft < oRight && pBottom > oTop && pTop < oBottom { + isCollision := pRight > oLeft && pLeft < oRight && pBottom > oTop && pTop < oBottom + if isCollision { if obs.Type == "coin" { score += 2000 continue } else if obs.Type == "powerup" { if obs.ID == "p_god" { godLives = 3 - log.Printf("[%s] 🛡️ POWERUP: Godmode collected", sessionID) } if obs.ID == "p_bat" { hasBat = true - log.Printf("[%s] ⚾ POWERUP: Bat collected", sessionID) } if obs.ID == "p_boot" { bootTicks = 600 - log.Printf("[%s] 👟 POWERUP: Boots collected", sessionID) } continue } else { if hasBat && obs.Type == "teacher" { hasBat = false - log.Printf("[%s] ⚾ BAT SMASH! Lehrer %s zerstört.", sessionID, obs.ID) continue } if godLives > 0 { godLives-- - log.Printf("[%s] 🛡️ GODMODE schützt! (%d lives left)", sessionID, godLives) continue } - - log.Printf("[%s] 💀 [DEATH] PlayerY: %.1f | Obs: %s @ %.1f", sessionID, hitboxY, obs.ID, obs.X) playerDead = true } } @@ -247,11 +208,6 @@ func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[st } } - if suspicionScore > 10 { - log.Printf("[%s] 🤖 [ANTI-CHEAT] BOT BANNED: %d suspicion", sessionID, suspicionScore) - playerDead = true - } - obsJson, _ := json.Marshal(obstacles) batStr := "0" if hasBat { @@ -267,8 +223,6 @@ func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[st "p_god_lives": godLives, "p_has_bat": batStr, "p_boot_ticks": bootTicks, - "ac_last_dist": fmt.Sprintf("%f", lastJumpDist), - "ac_suspicion": suspicionScore, }) return playerDead, score, obstacles diff --git a/static/game.js b/static/game.js deleted file mode 100644 index b8ef63c..0000000 --- a/static/game.js +++ /dev/null @@ -1,328 +0,0 @@ -(function() { - - const canvas = document.getElementById('gameCanvas'); - const ctx = canvas.getContext('2d'); - const container = document.getElementById('game-container'); - - const startScreen = document.getElementById('startScreen'); - const startBtn = document.getElementById('startBtn'); - const loadingText = document.getElementById('loadingText'); - const gameOverScreen = document.getElementById('gameOverScreen'); - - class PseudoRNG { - constructor(seed) { - this.state = BigInt(seed); - } - nextFloat() { - const a = 1664525n; const c = 1013904223n; const m = 4294967296n; - this.state = (this.state * a + c) % m; - return Number(this.state) / Number(m); - } - nextRange(min, max) { - return min + (this.nextFloat() * (max - min)); - } - pick(array) { - if (!array || array.length === 0) return null; - const idx = Math.floor(this.nextRange(0, array.length)); - return array[idx]; - } - } - -// Config - const GAME_WIDTH = 800; const GAME_HEIGHT = 400; - canvas.width = GAME_WIDTH; canvas.height = GAME_HEIGHT; - const GRAVITY = 0.6; const JUMP_POWER = -12; const GROUND_Y = 350; - const GAME_SPEED = 5; const CHUNK_SIZE = 60; - -// State (JETZT PRIVATE VARIABLEN!) - let gameConfig = null; - let isLoaded = false; - let isGameRunning = false; - let isGameOver = false; - let sessionID = null; - let rng = null; - let score = 0; - let currentTick = 0; - let lastSentTick = 0; - let inputLog = []; - let isCrouching = false; - let sprites = {}; - let playerSprite = new Image(); - let bgSprite = new Image(); - let player = { x: 50, y: 300, w: 30, h: 50, color: "red", vy: 0, grounded: false }; - let obstacles = []; - let serverObstacles = []; - -// --- Funktionen --- - - function resize() { - const windowWidth = window.innerWidth; - const windowHeight = window.innerHeight; - const targetRatio = GAME_WIDTH / GAME_HEIGHT; - let finalWidth, finalHeight; - if (windowWidth / windowHeight < targetRatio) { - finalWidth = windowWidth; finalHeight = windowWidth / targetRatio; - } else { - finalHeight = windowHeight; finalWidth = finalHeight * targetRatio; - } - canvas.style.width = `${finalWidth}px`; - canvas.style.height = `${finalHeight}px`; - if(container) { container.style.width = `${finalWidth}px`; container.style.height = `${finalHeight}px`; } - } - window.addEventListener('resize', resize); resize(); - - async function loadAssets() { - playerSprite.src = "assets/player.png"; - if (gameConfig.backgrounds && gameConfig.backgrounds.length > 0) { - const bgName = gameConfig.backgrounds[0]; - if (!bgName.startsWith("#")) bgSprite.src = "assets/" + bgName; - } - const promises = gameConfig.obstacles.map(def => { - return new Promise((resolve) => { - if (!def.image) { resolve(); return; } - const img = new Image(); img.src = "assets/" + def.image; - img.onload = () => { sprites[def.id] = img; resolve(); }; - img.onerror = () => { resolve(); }; - }); - }); - if (bgSprite.src) { - promises.push(new Promise(r => { bgSprite.onload = r; bgSprite.onerror = r; })); - } - await Promise.all(promises); - } - -// Global verfügbar machen für HTML Button - window.startGameClick = async function() { - if (!isLoaded) return; - startScreen.style.display = 'none'; - try { - const sRes = await fetch('/api/start', {method:'POST'}); - const sData = await sRes.json(); - sessionID = sData.sessionId; - rng = new PseudoRNG(sData.seed); - isGameRunning = true; - } catch(e) { location.reload(); } - }; - - function handleInput(action, active) { - if (isGameOver) { if(active) location.reload(); return; } - const relativeTick = currentTick - lastSentTick; - if (action === "JUMP" && active) { - if (player.grounded && !isCrouching) { - player.vy = JUMP_POWER; player.grounded = false; - inputLog.push({ t: relativeTick, act: "JUMP" }); - } - } - if (action === "DUCK") { isCrouching = active; } - } - - window.addEventListener('keydown', (e) => { - if (e.code === 'Space' || e.code === 'ArrowUp') handleInput("JUMP", true); - if (e.code === 'ArrowDown' || e.code === 'KeyS') handleInput("DUCK", true); - }); - window.addEventListener('keyup', (e) => { - if (e.code === 'ArrowDown' || e.code === 'KeyS') handleInput("DUCK", false); - }); - window.addEventListener('mousedown', (e) => { - if (e.target === canvas && e.button === 0) handleInput("JUMP", true); - }); - let touchStartY = 0; - window.addEventListener('touchstart', (e) => { - if(e.target === canvas) { e.preventDefault(); touchStartY = e.touches[0].clientY; } - }, { passive: false }); - window.addEventListener('touchend', (e) => { - if(e.target === canvas) { - e.preventDefault(); - const diff = e.changedTouches[0].clientY - touchStartY; - if (diff < -30) handleInput("JUMP", true); - else if (diff > 30) { handleInput("DUCK", true); setTimeout(() => handleInput("DUCK", false), 800); } - else if (Math.abs(diff) < 10) handleInput("JUMP", true); - } - }); - - async function sendChunk() { - const ticksToSend = currentTick - lastSentTick; - if (ticksToSend <= 0) return; - const payload = { sessionId: sessionID, inputs: [...inputLog], totalTicks: ticksToSend }; - inputLog = []; lastSentTick = currentTick; - try { - const res = await fetch('/api/validate', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(payload) }); - const data = await res.json(); - if (data.serverObs) serverObstacles = data.serverObs; - if (data.status === "dead") gameOver("Vom Server gestoppt"); - else { - const sScore = data.verifiedScore; - if (Math.abs(score - sScore) > 200) score = sScore; - } - } catch (e) {} - } - - function updateGameLogic() { - if (isCrouching) { - const relativeTick = currentTick - lastSentTick; - inputLog.push({ t: relativeTick, act: "DUCK" }); - } - 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; - 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; } - - let nextObstacles = []; let rightmostX = 0; - for (let obs of obstacles) { - obs.x -= GAME_SPEED; - const playerHitbox = { x: player.x, y: drawY, w: player.w, h: player.h }; - if (checkCollision(playerHitbox, obs)) { - player.color = "darkred"; - if (!isGameOver) { sendChunk(); gameOver("Kollision"); } - } - if (obs.x + obs.def.width > -100) { - nextObstacles.push(obs); - if (obs.x + obs.def.width > rightmostX) rightmostX = obs.x + obs.def.width; - } - } - obstacles = nextObstacles; - - if (rightmostX < GAME_WIDTH - 10 && gameConfig) { - const gap = Math.floor(400 + rng.nextRange(0, 500)); - let spawnX = rightmostX + gap; if (spawnX < GAME_WIDTH) spawnX = GAME_WIDTH; - let possibleObs = []; - gameConfig.obstacles.forEach(def => { - if (def.id === "eraser") { if (score >= 500) possibleObs.push(def); } else possibleObs.push(def); - }); - const def = rng.pick(possibleObs); - let speech = null; - if (def && def.canTalk) { if (rng.nextFloat() > 0.7) speech = rng.pick(def.speechLines); } - 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 = 25; const paddingY_Bottom = 5; - return (p.x + p.w - paddingX > obs.x + paddingX && p.x + paddingX < obs.x + obs.def.width - paddingX && - p.y + p.h - paddingY_Bottom > obs.y + paddingY_Top && p.y + paddingY_Top < obs.y + obs.def.height - paddingY_Bottom); - } - - window.submitScore = async function() { - const nameInput = document.getElementById('playerNameInput'); - const name = nameInput.value; - const btn = document.getElementById('submitBtn'); - if (!name) return alert("Namen eingeben!"); - btn.disabled = true; - try { - const res = await fetch('/api/submit-name', { - method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ sessionId: sessionID, name: name }) - }); - const data = await res.json(); - - let myClaims = JSON.parse(localStorage.getItem('escape_claims') || '[]'); - myClaims.push({ - name: name, score: Math.floor(score / 10), code: data.claimCode, - date: new Date().toLocaleString('de-DE'), sessionId: sessionID - }); - localStorage.setItem('escape_claims', JSON.stringify(myClaims)); - - document.getElementById('inputSection').style.display = 'none'; - loadLeaderboard(); - } catch (e) {} - }; - - async function loadLeaderboard() { - const res = await fetch(`/api/leaderboard?sessionId=${sessionID}`); - const entries = await res.json(); - let html = "