Private
Public Access
1
0

bug fixes
All checks were successful
Dynamic Branch Deploy / build-and-deploy (push) Successful in 1m48s

This commit is contained in:
Sebastian Unterschütz
2025-11-26 12:22:31 +01:00
parent 95119cdf98
commit 6fdad68a9b
2 changed files with 87 additions and 25 deletions

View File

@@ -3,23 +3,29 @@ package main
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"log"
"math"
"strconv" "strconv"
) )
func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[string]string) (bool, int, []ActiveObstacle) { func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[string]string) (bool, int, []ActiveObstacle) {
// State laden // --- 1. STATE LADEN ---
posY := parseOr(vals["pos_y"], PlayerYBase) posY := parseOr(vals["pos_y"], PlayerYBase)
velY := parseOr(vals["vel_y"], 0.0) velY := parseOr(vals["vel_y"], 0.0)
score := int(parseOr(vals["score"], 0)) score := int(parseOr(vals["score"], 0))
ticksAlive := int(parseOr(vals["total_ticks"], 0)) // Zeit-Basis
// NEU: Wir laden die bisher vergangene Zeit (Ticks)
ticksAlive := int(parseOr(vals["total_ticks"], 0))
rngStateVal, _ := strconv.ParseInt(vals["rng_state"], 10, 64) rngStateVal, _ := strconv.ParseInt(vals["rng_state"], 10, 64)
// Powerups
godLives := int(parseOr(vals["p_god_lives"], 0)) godLives := int(parseOr(vals["p_god_lives"], 0))
hasBat := vals["p_has_bat"] == "1" hasBat := vals["p_has_bat"] == "1"
bootTicks := int(parseOr(vals["p_boot_ticks"], 0)) bootTicks := int(parseOr(vals["p_boot_ticks"], 0))
// Anti-Cheat State laden (Wichtig für Heuristik über Chunks hinweg)
lastJumpDist := parseOr(vals["ac_last_dist"], 0.0)
suspicionScore := int(parseOr(vals["ac_suspicion"], 0))
rng := NewRNG(rngStateVal) rng := NewRNG(rngStateVal)
var obstacles []ActiveObstacle var obstacles []ActiveObstacle
@@ -29,26 +35,42 @@ func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[st
obstacles = []ActiveObstacle{} obstacles = []ActiveObstacle{}
} }
// DEBUG: Start des Chunks
// log.Printf("[%s] Simulating Chunk: %d Ticks, Score: %d", sessionID, totalTicks, score)
// --- ANTI-CHEAT 1: SPAM SCHUTZ ---
// Wer mehr als 10x pro Sekunde springt, ist ein Bot oder nutzt ein Makro
jumpCount := 0
for _, inp := range inputs {
if inp.Act == "JUMP" {
jumpCount++
}
}
if jumpCount > 10 {
log.Printf("🤖BOT-ALARM [%s]: Spammt Sprünge (%d Inputs)", sessionID, jumpCount)
return true, score, obstacles // Sofort tot
}
playerDead := false playerDead := false
// --- SIMULATION LOOP ---
for i := 0; i < totalTicks; i++ { for i := 0; i < totalTicks; i++ {
// WICHTIG: Wir erhöhen die Zeit
ticksAlive++ ticksAlive++
// LOGIK FIX: Geschwindigkeit basiert jetzt auf ZEIT (Ticks), nicht Score! // Speed Scaling (Zeitbasiert)
// 3000 Ticks = ca. 50 Sekunden. Da wird es schneller.
currentSpeed := BaseSpeed + (float64(ticksAlive)/3000.0)*0.5 currentSpeed := BaseSpeed + (float64(ticksAlive)/3000.0)*0.5
if currentSpeed > 12.0 { if currentSpeed > 20.0 {
currentSpeed = 12.0 currentSpeed = 20.0
} }
// Jump Power (Boots Powerup)
currentJumpPower := JumpPower currentJumpPower := JumpPower
if bootTicks > 0 { if bootTicks > 0 {
currentJumpPower = HighJumpPower currentJumpPower = HighJumpPower
bootTicks-- bootTicks--
} }
// Input Verarbeitung // Input
didJump := false didJump := false
isCrouching := false isCrouching := false
for _, inp := range inputs { for _, inp := range inputs {
@@ -62,20 +84,47 @@ func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[st
} }
} }
// Physik // Physik Status
isGrounded := posY >= PlayerYBase-1.0 isGrounded := posY >= PlayerYBase-1.0
currentHeight := PlayerHeight currentHeight := PlayerHeight
if isCrouching { if isCrouching {
currentHeight = PlayerHeight / 2 currentHeight = PlayerHeight / 2
if !isGrounded { if !isGrounded {
velY += 2.0 velY += 2.0
} // Fast fall
}
// Springen & ANTI-CHEAT 2 (Heuristik)
if didJump && isGrounded && !isCrouching {
velY = currentJumpPower
// Wir messen den Abstand zum nächsten Hindernis beim Absprung
var distToObs float64 = -1.0
for _, o := range obstacles {
if o.X > 50.0 { // Das nächste Hindernis vor uns
distToObs = o.X - 50.0
break
}
}
// Bot Check: Wenn der Abstand IMMER gleich ist (z.B. exakt 75.5px)
if distToObs > 0 {
diff := math.Abs(distToObs - lastJumpDist)
if diff < 1.0 {
// Verdächtig perfekt wiederholt
suspicionScore++
} else {
// Menschliche Varianz -> Verdacht senken
if suspicionScore > 0 {
suspicionScore--
}
}
lastJumpDist = distToObs
} }
} }
if didJump && isGrounded && !isCrouching { // Physik Anwendung
velY = currentJumpPower
}
velY += Gravity velY += Gravity
posY += velY posY += velY
if posY > PlayerYBase { if posY > PlayerYBase {
@@ -88,7 +137,7 @@ func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[st
hitboxY = posY + (PlayerHeight - currentHeight) hitboxY = posY + (PlayerHeight - currentHeight)
} }
// Obstacles // Hindernisse bewegen & Kollision
nextObstacles := []ActiveObstacle{} nextObstacles := []ActiveObstacle{}
rightmostX := 0.0 rightmostX := 0.0
@@ -99,10 +148,9 @@ func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[st
continue continue
} }
// Passed Check (Verhindert Hängenbleiben an "toten" Objekten) // Passed Check (Verhindert "Geister-Kollision" von hinten)
paddingX := 10.0 paddingX := 10.0
if obs.X+obs.Width-paddingX < 55.0 { if obs.X+obs.Width-paddingX < 55.0 {
// Schon vorbei -> Keine Kollision mehr prüfen
nextObstacles = append(nextObstacles, obs) nextObstacles = append(nextObstacles, obs)
if obs.X+obs.Width > rightmostX { if obs.X+obs.Width > rightmostX {
rightmostX = obs.X + obs.Width rightmostX = obs.X + obs.Width
@@ -124,7 +172,7 @@ func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[st
if isCollision { if isCollision {
if obs.Type == "coin" { if obs.Type == "coin" {
score += 2000 // Score Bonus macht das Spiel nicht mehr kaputt! score += 2000
continue continue
} else if obs.Type == "powerup" { } else if obs.Type == "powerup" {
if obs.ID == "p_god" { if obs.ID == "p_god" {
@@ -138,14 +186,19 @@ func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[st
} }
continue continue
} else { } else {
// Kollision mit Gegner
if hasBat && obs.Type == "teacher" { if hasBat && obs.Type == "teacher" {
hasBat = false hasBat = false
log.Printf("[%s] ⚾ Bat used on %s", sessionID, obs.ID)
continue continue
} }
if godLives > 0 { if godLives > 0 {
godLives-- godLives--
log.Printf("[%s] 🛡️ Godmode saved life (%d left)", sessionID, godLives)
continue continue
} }
log.Printf("💀 DEATH [%s]: Hit %s at Tick %d", sessionID, obs.ID, ticksAlive)
playerDead = true playerDead = true
} }
} }
@@ -165,10 +218,9 @@ func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[st
spawnX = GameWidth spawnX = GameWidth
} }
// LOGIK FIX: Boss Phase basiert auf ZEIT (Ticks)
isBossPhase := (ticksAlive % 1500) > 1200 isBossPhase := (ticksAlive % 1500) > 1200
var possibleDefs []ObstacleDef var possibleDefs []ObstacleDef
for _, d := range defaultConfig.Obstacles { for _, d := range defaultConfig.Obstacles {
if isBossPhase { if isBossPhase {
if d.ID == "principal" || d.ID == "trashcan" { if d.ID == "principal" || d.ID == "trashcan" {
@@ -178,7 +230,6 @@ func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[st
if d.ID == "principal" { if d.ID == "principal" {
continue continue
} }
// Eraser kommt ab Tick 3000 (ca. 50 sekunden)
if d.ID == "eraser" && ticksAlive < 3000 { if d.ID == "eraser" && ticksAlive < 3000 {
continue continue
} }
@@ -197,6 +248,7 @@ func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[st
if def.Type == "powerup" && rng.NextFloat() > 0.1 { if def.Type == "powerup" && rng.NextFloat() > 0.1 {
def = nil def = nil
} }
if def != nil { if def != nil {
spawnY := GroundY - def.Height - def.YOffset spawnY := GroundY - def.Height - def.YOffset
obstacles = append(obstacles, ActiveObstacle{ obstacles = append(obstacles, ActiveObstacle{
@@ -207,12 +259,19 @@ func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[st
} }
if !playerDead { if !playerDead {
score++ // Basis-Score läuft normal weiter score++
} else { } else {
break break
} }
} }
// --- ANTI-CHEAT CHECK (Ergebnis) ---
if suspicionScore > 10 {
log.Printf("🤖BOT-ALARM [%s]: Zu perfekte Sprünge (Heuristik Fail)", sessionID)
playerDead = true
}
// --- SPEICHERN ---
obsJson, _ := json.Marshal(obstacles) obsJson, _ := json.Marshal(obstacles)
batStr := "0" batStr := "0"
if hasBat { if hasBat {
@@ -221,7 +280,7 @@ func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[st
rdb.HSet(ctx, "session:"+sessionID, map[string]interface{}{ rdb.HSet(ctx, "session:"+sessionID, map[string]interface{}{
"score": score, "score": score,
"total_ticks": ticksAlive, // NEU: Speichern "total_ticks": ticksAlive,
"pos_y": fmt.Sprintf("%f", posY), "pos_y": fmt.Sprintf("%f", posY),
"vel_y": fmt.Sprintf("%f", velY), "vel_y": fmt.Sprintf("%f", velY),
"rng_state": rng.State, "rng_state": rng.State,
@@ -229,6 +288,9 @@ func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[st
"p_god_lives": godLives, "p_god_lives": godLives,
"p_has_bat": batStr, "p_has_bat": batStr,
"p_boot_ticks": bootTicks, "p_boot_ticks": bootTicks,
// AC Daten speichern für nächsten Chunk
"ac_last_dist": fmt.Sprintf("%f", lastJumpDist),
"ac_suspicion": suspicionScore,
}) })
return playerDead, score, obstacles return playerDead, score, obstacles

View File

@@ -7,7 +7,7 @@ function updateGameLogic() {
// 2. Geschwindigkeit (Basiert auf ZEIT/Ticks, nicht Score!) // 2. Geschwindigkeit (Basiert auf ZEIT/Ticks, nicht Score!)
// Formel: Start bei 5, erhöht sich alle 3000 Ticks (ca. 50 Sek) um 0.5 // Formel: Start bei 5, erhöht sich alle 3000 Ticks (ca. 50 Sek) um 0.5
let currentSpeed = 5 + (currentTick / 3000.0) * 0.5; let currentSpeed = 5 + (currentTick / 3000.0) * 0.5;
if (currentSpeed > 12.0) currentSpeed = 12.0; // Max Speed Cap if (currentSpeed > 20.0) currentSpeed = 20.0; // Max Speed Cap
// 3. Spieler Physik & Größe // 3. Spieler Physik & Größe
const originalHeight = 50; const originalHeight = 50;