package main import ( "encoding/json" "fmt" "log" "math" "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 lastJumpDist := parseOr(vals["ac_last_dist"], 0.0) suspicionScore := int(parseOr(vals["ac_suspicion"], 0)) rng := NewRNG(rngStateVal) var obstacles []ActiveObstacle if val, ok := vals["obstacles"]; ok && val != "" { json.Unmarshal([]byte(val), &obstacles) } else { 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 } playerDead := false // --- SIMULATION LOOP --- for i := 0; i < totalTicks; i++ { // A. INPUT didJump := false isCrouching := false for _, inp := range inputs { if inp.Tick == i { if inp.Act == "JUMP" { didJump = true } if inp.Act == "DUCK" { isCrouching = true } } } // Physik Check isGrounded := posY >= PlayerYBase-1.0 if didJump && isGrounded && !isCrouching { velY = JumpPower // --- 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 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 suspicionScore++ } else { // Menschliche Varianz -> Reset (oder verringern) if suspicionScore > 0 { suspicionScore-- } } lastJumpDist = nextObsDist } } // ... (Restliche Physik wie gehabt) ... currentHeight := PlayerHeight if isCrouching { currentHeight = PlayerHeight / 2 if !isGrounded { velY += 2.0 } } velY += Gravity posY += velY if posY > PlayerYBase { posY = PlayerYBase velY = 0 } hitboxY := posY if isCrouching { hitboxY = posY + (PlayerHeight - currentHeight) } // B. OBSTACLES nextObstacles := []ActiveObstacle{} rightmostX := 0.0 for _, obs := range obstacles { obs.X -= GameSpeed if obs.X+obs.Width < 50.0 { continue } // Hitbox paddingX := 5.0 paddingY_Top := 5.0 paddingY_Bottom := 5.0 pLeft, pRight := 50.0+paddingX, 50.0+30.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.X+obs.Width > -100 { 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)) spawnX := rightmostX + gap if spawnX < GameWidth { spawnX = GameWidth } var possibleDefs []ObstacleDef for _, d := range defaultConfig.Obstacles { if d.ID == "eraser" { if score >= 500 { possibleDefs = append(possibleDefs, d) } } else { possibleDefs = append(possibleDefs, d) } } def := rng.PickDef(possibleDefs) if def != nil && def.CanTalk { if rng.NextFloat() > 0.7 { rng.NextFloat() } } 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 !playerDead { score++ } else { break } } // Ban Hammer für Bots if suspicionScore > 8 { log.Printf("🤖 BOT ALARM (Heuristik): %s springt zu perfekt!", sessionID) playerDead = true } // State speichern obsJson, _ := json.Marshal(obstacles) 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 "ac_last_dist": fmt.Sprintf("%f", lastJumpDist), "ac_suspicion": suspicionScore, }) return playerDead, score, obstacles } func parseOr(s string, def float64) float64 { if s == "" { return def } v, err := strconv.ParseFloat(s, 64) if err != nil { return def } return v }