diff --git a/config.go b/config.go index 8a87528..c210573 100644 --- a/config.go +++ b/config.go @@ -9,13 +9,14 @@ import ( ) const ( - Gravity = 0.6 - JumpPower = -12.0 - GroundY = 350.0 - PlayerHeight = 50.0 - PlayerYBase = GroundY - PlayerHeight - GameSpeed = 5.0 - GameWidth = 800.0 + Gravity = 0.6 + JumpPower = -12.0 + HighJumpPower = -16.0 + GroundY = 350.0 + PlayerHeight = 50.0 + PlayerYBase = GroundY - PlayerHeight + BaseSpeed = 5.0 + GameWidth = 800.0 ) // Globale Variablen @@ -37,12 +38,25 @@ func getEnv(key, fallback string) string { func initGameConfig() { defaultConfig = GameConfig{ Obstacles: []ObstacleDef{ - {ID: "desk", Width: 40, Height: 30, Color: "#8B4513", Image: "desk.png"}, - {ID: "teacher", Width: 30, Height: 60, Color: "#000080", Image: "teacher.png", CanTalk: true, SpeechLines: []string{"Halt!", "Handy weg!", "Nachsitzen!"}}, - {ID: "trashcan", Width: 25, Height: 35, Color: "#555", Image: "trash.png"}, - {ID: "eraser", Width: 30, Height: 20, Color: "#fff", Image: "eraser.png", YOffset: 45.0}, + // --- HINDERNISSE --- + {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: 45.0}, + + // --- BOSS OBJEKTE (Kommen häufiger im Bosskampf) --- + {ID: "principal", Type: "teacher", Width: 40, Height: 70, Color: "#000", Image: "principal1.png", CanTalk: true, SpeechLines: []string{"EXMATRIKULATION!"}}, + + // --- COINS --- + {ID: "coin", Type: "coin", Width: 20, Height: 20, Color: "gold", Image: "coin1.png", YOffset: 20.0}, + + // --- POWERUPS --- + {ID: "p_god", Type: "powerup", Width: 30, Height: 30, Color: "cyan", Image: "powerup_god1.png", YOffset: 20.0}, // Godmode + {ID: "p_bat", Type: "powerup", Width: 30, Height: 30, Color: "red", Image: "powerup_bat1.png", YOffset: 20.0}, // Schläger + {ID: "p_boot", Type: "powerup", Width: 30, Height: 30, Color: "lime", Image: "powerup_boot1.png", YOffset: 20.0}, // Boots }, - Backgrounds: []string{"background.jpg"}, + // Mehrere Hintergründe für Level-Wechsel + Backgrounds: []string{"gym-background.jpg", "school-background.jpg", "school2-background.jpg"}, } - log.Println("✅ Config geladen") + log.Println("✅ Config mit Powerups geladen") } diff --git a/simulation.go b/simulation.go index 99fa9dc..f170cb2 100644 --- a/simulation.go +++ b/simulation.go @@ -8,16 +8,17 @@ import ( "strconv" ) -// Führt die Physik-Simulation durch und prüft auf Cheats func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[string]string) (bool, int, []ActiveObstacle) { - // State parsen posY := parseOr(vals["pos_y"], PlayerYBase) velY := parseOr(vals["vel_y"], 0.0) score := int(parseOr(vals["score"], 0)) rngStateVal, _ := strconv.ParseInt(vals["rng_state"], 10, 64) - // Anti-Cheat State laden + godLives := int(parseOr(vals["p_god_lives"], 0)) + 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)) @@ -30,24 +31,33 @@ func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[st obstacles = []ActiveObstacle{} } - // --- ANTI-CHEAT STUFE 2: SPAM SCHUTZ --- jumpCount := 0 for _, inp := range inputs { if inp.Act == "JUMP" { jumpCount++ } } - if jumpCount > 8 { // Wer mehr als 8x pro Sekunde springt, ist ein Bot - log.Printf("🤖 BOT ALARM (Spam): %s sprang %d mal!", sessionID, jumpCount) - return true, score, obstacles // Player Dead + + if jumpCount > 10 { + log.Printf("[%s] 🤖 [ANTI-CHEAT] SPAM DETECTED: %d mal JUMP!", sessionID, jumpCount) + return true, score, obstacles } playerDead := false - // --- SIMULATION LOOP --- for i := 0; i < totalTicks; i++ { - // A. INPUT + currentSpeed := BaseSpeed + (float64(score)/500.0)*0.5 + if currentSpeed > 12.0 { + currentSpeed = 12.0 + } + + currentJumpPower := JumpPower + if bootTicks > 0 { + currentJumpPower = HighJumpPower + bootTicks-- + } + didJump := false isCrouching := false for _, inp := range inputs { @@ -61,30 +71,33 @@ func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[st } } - // Physik Check isGrounded := posY >= PlayerYBase-1.0 - if didJump && isGrounded && !isCrouching { - velY = JumpPower + currentHeight := PlayerHeight + if isCrouching { + currentHeight = PlayerHeight / 2 + if !isGrounded { + velY += 2.0 + } + } + + if didJump && isGrounded && !isCrouching { + velY = currentJumpPower - // --- ANTI-CHEAT STUFE 3: HEURISTIK (Perfektes Springen) --- - // Wir messen den Abstand zum nächsten Hindernis beim Absprung nextObsDist := -1.0 for _, o := range obstacles { - if o.X > 50.0 { // Erstes Hindernis vor uns + if o.X > 50.0 { nextObsDist = o.X - 50.0 break } } if nextObsDist > 0 { - // Bot-Check: Springt er immer exakt bei "75.5" Pixel Abstand? diff := math.Abs(nextObsDist - lastJumpDist) - if diff < 1.0 { - // Abstand ist fast identisch zum letzten Sprung -> Verdächtig + if diff < 0.5 { suspicionScore++ + log.Printf("[%s] ⚠️ [ANTI-CHEAT] Verdächtiger Sprung! Diff: %.4f | Suspicion: %d", sessionID, diff, suspicionScore) } else { - // Menschliche Varianz -> Reset (oder verringern) if suspicionScore > 0 { suspicionScore-- } @@ -93,15 +106,6 @@ func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[st } } - // ... (Restliche Physik wie gehabt) ... - currentHeight := PlayerHeight - if isCrouching { - currentHeight = PlayerHeight / 2 - if !isGrounded { - velY += 2.0 - } - } - velY += Gravity posY += velY @@ -115,42 +119,73 @@ func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[st hitboxY = posY + (PlayerHeight - currentHeight) } - // B. OBSTACLES nextObstacles := []ActiveObstacle{} rightmostX := 0.0 for _, obs := range obstacles { - obs.X -= GameSpeed + obs.X -= currentSpeed - if obs.X+obs.Width < 50.0 { + if obs.X+obs.Width < -50.0 { continue } - // Hitbox - paddingX := 10.0 - paddingY_Top := 25.0 + paddingX := 5.0 + paddingY_Top := 5.0 + if obs.Type == "teacher" { + paddingY_Top = 5.0 + } + paddingY_Bottom := 5.0 - pLeft, pRight := 50.0+paddingX, 50.0+30.0-paddingX + pLeft, pRight := 50.0+paddingX, 50.0+60.0-paddingX pTop, pBottom := hitboxY+paddingY_Top, hitboxY+currentHeight-paddingY_Bottom oLeft, oRight := obs.X+paddingX, obs.X+obs.Width-paddingX oTop, oBottom := obs.Y+paddingY_Top, obs.Y+obs.Height-paddingY_Bottom if pRight > oLeft && pLeft < oRight && pBottom > oTop && pTop < oBottom { - playerDead = true + + 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 + } } - if obs.X+obs.Width > -100 { - nextObstacles = append(nextObstacles, obs) - if obs.X+obs.Width > rightmostX { - rightmostX = obs.X + obs.Width - } + nextObstacles = append(nextObstacles, obs) + if obs.X+obs.Width > rightmostX { + rightmostX = obs.X + obs.Width } } obstacles = nextObstacles - // C. SPAWNING if rightmostX < GameWidth-10.0 { rawGap := 400.0 + rng.NextRange(0, 500) gap := float64(int(rawGap)) @@ -159,18 +194,27 @@ func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[st spawnX = GameWidth } + isBossPhase := (score % 1500) > 1200 var possibleDefs []ObstacleDef + for _, d := range defaultConfig.Obstacles { - if d.ID == "eraser" { - if score >= 500 { + if isBossPhase { + if d.ID == "principal" || d.ID == "trashcan" { possibleDefs = append(possibleDefs, d) } } else { + if d.ID == "principal" { + continue + } + if d.ID == "eraser" && score < 500 { + continue + } possibleDefs = append(possibleDefs, d) } } def := rng.PickDef(possibleDefs) + if def != nil && def.CanTalk { if rng.NextFloat() > 0.7 { rng.NextFloat() @@ -178,14 +222,21 @@ func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[st } if def != nil { - spawnY := GroundY - def.Height - def.YOffset - obstacles = append(obstacles, ActiveObstacle{ - ID: def.ID, - X: spawnX, - Y: spawnY, - Width: def.Width, - Height: def.Height, - }) + if def.Type == "powerup" && rng.NextFloat() > 0.1 { + def = nil + } + + if def != nil { + spawnY := GroundY - def.Height - def.YOffset + obstacles = append(obstacles, ActiveObstacle{ + ID: def.ID, + Type: def.Type, + X: spawnX, + Y: spawnY, + Width: def.Width, + Height: def.Height, + }) + } } } @@ -196,21 +247,26 @@ func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[st } } - // Ban Hammer für Bots - if suspicionScore > 8 { - log.Printf("🤖 BOT ALARM (Heuristik): %s springt zu perfekt!", sessionID) + if suspicionScore > 10 { + log.Printf("[%s] 🤖 [ANTI-CHEAT] BOT BANNED: %d suspicion", sessionID, suspicionScore) playerDead = true } - // State speichern obsJson, _ := json.Marshal(obstacles) + batStr := "0" + if hasBat { + batStr = "1" + } + rdb.HSet(ctx, "session:"+sessionID, map[string]interface{}{ - "score": score, - "pos_y": fmt.Sprintf("%f", posY), - "vel_y": fmt.Sprintf("%f", velY), - "rng_state": rng.State, - "obstacles": string(obsJson), - // Anti-Cheat Daten mitspeichern + "score": score, + "pos_y": fmt.Sprintf("%f", posY), + "vel_y": fmt.Sprintf("%f", velY), + "rng_state": rng.State, + "obstacles": string(obsJson), + "p_god_lives": godLives, + "p_has_bat": batStr, + "p_boot_ticks": bootTicks, "ac_last_dist": fmt.Sprintf("%f", lastJumpDist), "ac_suspicion": suspicionScore, }) diff --git a/static/assets/coin.png b/static/assets/coin.png new file mode 100644 index 0000000..e8edeb8 Binary files /dev/null and b/static/assets/coin.png differ diff --git a/static/assets/desk.png b/static/assets/desk.png new file mode 100644 index 0000000..d4fdedf Binary files /dev/null and b/static/assets/desk.png differ diff --git a/static/assets/eraser.png b/static/assets/eraser.png new file mode 100644 index 0000000..3fcc99f Binary files /dev/null and b/static/assets/eraser.png differ diff --git a/static/assets/gym-background.jpg b/static/assets/gym-background.jpg new file mode 100644 index 0000000..e4dd3ab Binary files /dev/null and b/static/assets/gym-background.jpg differ diff --git a/static/assets/background.jpg b/static/assets/school-background.jpg similarity index 100% rename from static/assets/background.jpg rename to static/assets/school-background.jpg diff --git a/static/assets/school2-background.jpg b/static/assets/school2-background.jpg new file mode 100644 index 0000000..dc6a800 Binary files /dev/null and b/static/assets/school2-background.jpg differ diff --git a/static/coin.png b/static/coin.png new file mode 100644 index 0000000..e8edeb8 Binary files /dev/null and b/static/coin.png differ diff --git a/static/js/config.js b/static/js/config.js index ffd7073..89597f9 100644 --- a/static/js/config.js +++ b/static/js/config.js @@ -3,9 +3,13 @@ const GAME_WIDTH = 800; const GAME_HEIGHT = 400; const GRAVITY = 0.6; const JUMP_POWER = -12; +const HIGH_JUMP_POWER = -16; const GROUND_Y = 350; -const GAME_SPEED = 5; +const BASE_SPEED = 5; const CHUNK_SIZE = 60; +const TARGET_FPS = 60; +const MS_PER_TICK = 1000 / TARGET_FPS; + // RNG Klasse class PseudoRNG { diff --git a/static/js/logic.js b/static/js/logic.js index e7d6ddc..53fe64b 100644 --- a/static/js/logic.js +++ b/static/js/logic.js @@ -1,12 +1,23 @@ function updateGameLogic() { - if (isCrouching) { - inputLog.push({ t: currentTick - lastSentTick, act: "DUCK" }); - } + // 1. Speed Berechnung (Sync mit Server!) + let currentSpeed = BASE_SPEED + (score / 500.0) * 0.5; + if (currentSpeed > 12.0) currentSpeed = 12.0; + + // 2. Input & Sprung + if (isCrouching) inputLog.push({ t: currentTick - lastSentTick, act: "DUCK" }); const originalHeight = 50; const crouchHeight = 25; player.h = isCrouching ? crouchHeight : originalHeight; let drawY = isCrouching ? player.y + (originalHeight - crouchHeight) : player.y; + // Jump Power Check + let jumpP = JUMP_POWER; + if (bootTicks > 0) { + jumpP = HIGH_JUMP_POWER; + bootTicks--; + } + + // Physik player.vy += GRAVITY; if (isCrouching && !player.grounded) player.vy += 2.0; player.y += player.vy; @@ -15,15 +26,44 @@ function updateGameLogic() { player.y = GROUND_Y - originalHeight; player.vy = 0; player.grounded = true; } else { player.grounded = false; } - let nextObstacles = []; let rightmostX = 0; + + // 3. Obstacles + let nextObstacles = []; + let rightmostX = 0; + for (let obs of obstacles) { - obs.x -= GAME_SPEED; + obs.x -= currentSpeed; + 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"); } + // TYPE CHECK + if (obs.def.type === "coin") { + score += 2000; + continue; + } + 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; + continue; + } + else { + // HINDERNIS + if (hasBat && obs.def.type === "teacher") { + hasBat = false; + continue; // Zerstört! + } + if (godModeLives > 0) { + godModeLives--; + continue; // Überlebt! + } + + 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; @@ -31,29 +71,49 @@ function updateGameLogic() { } obstacles = nextObstacles; - // Spawning + // 4. Spawning (Sync mit Go!) 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; + const isBossPhase = (score % 1500) > 1200; + let possibleObs = []; gameConfig.obstacles.forEach(def => { - if (def.id === "eraser") { if (score >= 500) possibleObs.push(def); } else possibleObs.push(def); + if (isBossPhase) { + if (def.id === "principal" || def.id === "trashcan") possibleObs.push(def); + } else { + if (def.id === "principal") return; + if (def.id === "eraser" && score < 500) return; + possibleObs.push(def); + } }); - const def = rng.pick(possibleObs); + let def = rng.pick(possibleObs); + + // Speech Sync let speech = null; - if (def && def.canTalk) { if (rng.nextFloat() > 0.7) speech = rng.pick(def.speechLines); } + if (def && def.canTalk) { + if (rng.nextFloat() > 0.7) speech = rng.pick(def.speechLines); + } + + // Powerup Rarity Sync (Muss exakt wie Go sein: 10% Chance) + 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 }); + 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; + const paddingX = 5; const paddingY_Top = 5; 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); } \ No newline at end of file diff --git a/static/js/main.js b/static/js/main.js index f6f2f53..cbb2408 100644 --- a/static/js/main.js +++ b/static/js/main.js @@ -44,14 +44,39 @@ function gameOver(reason) { drawGame(); } -function gameLoop() { - if (!isLoaded) return; - if (isGameRunning && !isGameOver) { - updateGameLogic(); currentTick++; score++; - const scoreEl = document.getElementById('score'); if (scoreEl) scoreEl.innerText = Math.floor(score / 10); - if (currentTick - lastSentTick >= CHUNK_SIZE) sendChunk(); +function gameLoop(timestamp) { + requestAnimationFrame(gameLoop); + + if (!isLoaded || !isGameRunning || isGameOver) { + lastTime = timestamp; + return; } - drawGame(); requestAnimationFrame(gameLoop); + + if (!lastTime) lastTime = timestamp; + const deltaTime = timestamp - lastTime; + lastTime = timestamp; + + if (deltaTime > 1000) { + accumulator = 0; + return; + } + + accumulator += deltaTime; + + while (accumulator >= MS_PER_TICK) { + updateGameLogic(); + currentTick++; + score++; + + if (currentTick - lastSentTick >= CHUNK_SIZE) sendChunk(); + + accumulator -= MS_PER_TICK; + } + + const scoreEl = document.getElementById('score'); + if (scoreEl) scoreEl.innerText = Math.floor(score / 10); + + drawGame(); } async function initGame() { diff --git a/static/js/render.js b/static/js/render.js index d6985a6..1b3421b 100644 --- a/static/js/render.js +++ b/static/js/render.js @@ -44,46 +44,53 @@ resize(); // --- DRAWING --- function drawGame() { - // Alles löschen ctx.clearRect(0, 0, GAME_WIDTH, GAME_HEIGHT); - // Background + // --- BACKGROUND --- + // Hier war der Check schon drin, das ist gut if (bgSprite.complete && bgSprite.naturalHeight !== 0) { - // Hintergrundbild exakt auf 800x400 skalieren ctx.drawImage(bgSprite, 0, 0, GAME_WIDTH, GAME_HEIGHT); } else { - // Fallback Farbe ctx.fillStyle = "#f0f0f0"; ctx.fillRect(0, 0, GAME_WIDTH, GAME_HEIGHT); } - // Boden ctx.fillStyle = "rgba(60, 60, 60, 0.8)"; ctx.fillRect(0, GROUND_Y, GAME_WIDTH, 50); - // Hindernisse + // --- HINDERNISSE (HIER WAR DER FEHLER) --- obstacles.forEach(obs => { const img = sprites[obs.def.id]; - if (img) { + + // FIX: Wir prüfen jetzt strikt, ob das Bild wirklich bereit ist + if (img && img.complete && img.naturalHeight !== 0) { ctx.drawImage(img, obs.x, obs.y, obs.def.width, obs.def.height); } else { - ctx.fillStyle = obs.def.color; + // Fallback: Wenn Bild fehlt/kaputt -> Farbiges Rechteck + // Wir prüfen auf Typ Coin, damit Coins gold sind, auch wenn Bild fehlt + if (obs.def.type === "coin") ctx.fillStyle = "gold"; + else ctx.fillStyle = obs.def.color; + ctx.fillRect(obs.x, obs.y, obs.def.width, obs.def.height); } + if(obs.speech) drawSpeechBubble(obs.x, obs.y, obs.speech); }); - - // Debug Rahmen (Server Hitboxen) + + /* + // --- DEBUG --- ctx.strokeStyle = isGameOver ? "red" : "lime"; ctx.lineWidth = 2; serverObstacles.forEach(srvObs => { ctx.strokeRect(srvObs.x, srvObs.y, srvObs.w, srvObs.h); }); + */ - // Spieler + // --- PLAYER --- const drawY = isCrouching ? player.y + 25 : player.y; const drawH = isCrouching ? 25 : 50; + // Hier war der Check auch schon korrekt if (playerSprite.complete && playerSprite.naturalHeight !== 0) { ctx.drawImage(playerSprite, player.x, drawY, player.w, drawH); } else { @@ -91,13 +98,33 @@ function drawGame() { ctx.fillRect(player.x, drawY, player.w, drawH); } - // Game Over Overlay (Dunkelheit) + // --- POWERUP UI (Oben Links) --- + if (isGameRunning && !isGameOver) { + ctx.fillStyle = "black"; + ctx.font = "bold 10px monospace"; + ctx.textAlign = "left"; + let statusText = ""; + if(godModeLives > 0) statusText += `🛡️ x${godModeLives} `; + if(hasBat) statusText += `⚾ BAT `; + if(bootTicks > 0) statusText += `👟 ${(bootTicks/60).toFixed(1)}s`; + + // Drift Anzeige + /* + if (obstacles.length > 0 && serverObstacles.length > 0) { + const drift = Math.abs(obstacles[0].x - serverObstacles[0].x).toFixed(1); + statusText += ` | Drift: ${drift}px`; + } + */ + ctx.fillText(statusText, 10, 40); + } + if (isGameOver) { ctx.fillStyle = "rgba(0,0,0,0.7)"; ctx.fillRect(0,0,GAME_WIDTH, GAME_HEIGHT); } } +// Sprechblasen Helper function drawSpeechBubble(x, y, text) { const bX = x-20; const bY = y-40; const bW = 120; const bH = 30; ctx.fillStyle="white"; ctx.fillRect(bX,bY,bW,bH); diff --git a/static/js/state.js b/static/js/state.js index d382ec6..4326cbd 100644 --- a/static/js/state.js +++ b/static/js/state.js @@ -12,6 +12,18 @@ let lastSentTick = 0; let inputLog = []; let isCrouching = false; +// Powerups Client State +let godModeLives = 0; +let hasBat = false; +let bootTicks = 0; + +// Hintergrund +let currentBgIndex = 0; + +// Tick Time +let lastTime = 0; +let accumulator = 0; + // Grafiken let sprites = {}; let playerSprite = new Image(); diff --git a/types.go b/types.go index 1c22b24..64ee69c 100644 --- a/types.go +++ b/types.go @@ -2,6 +2,7 @@ package main type ObstacleDef struct { ID string `json:"id"` + Type string `json:"type"` Width float64 `json:"width"` Height float64 `json:"height"` Color string `json:"color"` @@ -19,6 +20,7 @@ type GameConfig struct { // Dynamischer State type ActiveObstacle struct { ID string `json:"id"` + Type string `json:"type"` X float64 `json:"x"` Y float64 `json:"y"` Width float64 `json:"w"` @@ -41,6 +43,7 @@ type ValidateResponse struct { Status string `json:"status"` VerifiedScore int `json:"verifiedScore"` ServerObs []ActiveObstacle `json:"serverObs"` + ActivePowerup string `json:"activePowerup"` } type StartResponse struct {