Private
Public Access
1
0
Files
it232Abschied/simulation.go
Sebastian Unterschütz 5175f52652
All checks were successful
Dynamic Branch Deploy / build-and-deploy (push) Successful in 2m24s
padding to 5
2025-11-25 18:43:24 +01:00

231 lines
5.0 KiB
Go

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
}