diff --git a/config.go b/config.go index 221fee7..c210573 100644 --- a/config.go +++ b/config.go @@ -39,21 +39,21 @@ func initGameConfig() { defaultConfig = GameConfig{ Obstacles: []ObstacleDef{ // --- HINDERNISSE --- - {ID: "desk", Type: "obstacle", Width: 40, Height: 30, Color: "#8B4513", Image: "desk.png"}, - {ID: "teacher", Type: "teacher", Width: 30, Height: 60, Color: "#000080", Image: "teacher.png", CanTalk: true, SpeechLines: []string{"Halt!", "Handy weg!"}}, - {ID: "trashcan", Type: "obstacle", Width: 25, Height: 35, Color: "#555", Image: "trash.png"}, - {ID: "eraser", Type: "obstacle", Width: 30, Height: 20, Color: "#fff", Image: "eraser.png", YOffset: 45.0}, + {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: "principal.png", CanTalk: true, SpeechLines: []string{"EXMATRIKULATION!"}}, + {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: "coin.png", YOffset: 20.0}, + {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_god.png", YOffset: 20.0}, // Godmode - {ID: "p_bat", Type: "powerup", Width: 30, Height: 30, Color: "red", Image: "powerup_bat.png", YOffset: 20.0}, // Schläger - {ID: "p_boot", Type: "powerup", Width: 30, Height: 30, Color: "lime", Image: "powerup_boot.png", YOffset: 20.0}, // Boots + {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 }, // Mehrere Hintergründe für Level-Wechsel Backgrounds: []string{"gym-background.jpg", "school-background.jpg", "school2-background.jpg"}, diff --git a/simulation.go b/simulation.go index ebc0ab1..f170cb2 100644 --- a/simulation.go +++ b/simulation.go @@ -3,10 +3,13 @@ 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)) @@ -16,6 +19,9 @@ 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 @@ -25,9 +31,22 @@ 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)/500.0)*0.5 if currentSpeed > 12.0 { currentSpeed = 12.0 @@ -53,6 +72,7 @@ func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[st } isGrounded := posY >= PlayerYBase-1.0 + currentHeight := PlayerHeight if isCrouching { currentHeight = PlayerHeight / 2 @@ -63,10 +83,32 @@ 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 @@ -87,43 +129,52 @@ func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[st continue } - paddingX := 10.0 - paddingY_Top := 10.0 + paddingX := 5.0 + paddingY_Top := 5.0 if obs.Type == "teacher" { - paddingY_Top = 25.0 + paddingY_Top = 5.0 } - pLeft, pRight := 50.0+paddingX, 50.0+30.0-paddingX - pTop, pBottom := hitboxY+paddingY_Top, hitboxY+currentHeight-5.0 + paddingY_Bottom := 5.0 + + 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-5.0 + oTop, oBottom := obs.Y+paddingY_Top, obs.Y+obs.Height-paddingY_Bottom - isCollision := pRight > oLeft && pLeft < oRight && pBottom > oTop && pTop < oBottom + if 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 } } @@ -136,7 +187,8 @@ func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[st obstacles = nextObstacles if rightmostX < GameWidth-10.0 { - gap := float64(int(400.0 + rng.NextRange(0, 500))) + rawGap := 400.0 + rng.NextRange(0, 500) + gap := float64(int(rawGap)) spawnX := rightmostX + gap if spawnX < GameWidth { spawnX = GameWidth @@ -195,6 +247,11 @@ 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 { @@ -210,6 +267,8 @@ 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/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/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/logic.js b/static/js/logic.js index b8a6c8e..53fe64b 100644 --- a/static/js/logic.js +++ b/static/js/logic.js @@ -25,7 +25,7 @@ function updateGameLogic() { if (player.y + originalHeight >= GROUND_Y) { player.y = GROUND_Y - originalHeight; player.vy = 0; player.grounded = true; } else { player.grounded = false; } - + // 3. Obstacles let nextObstacles = []; diff --git a/static/js/render.js b/static/js/render.js index 860c1aa..1b3421b 100644 --- a/static/js/render.js +++ b/static/js/render.js @@ -109,10 +109,12 @@ function drawGame() { 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); }