Merge pull request 'bug fixes' (#9) from add-new-player-skin into main
All checks were successful
Dynamic Branch Deploy / build-and-deploy (push) Successful in 1m16s
All checks were successful
Dynamic Branch Deploy / build-and-deploy (push) Successful in 1m16s
Reviewed-on: #9
This commit is contained in:
114
simulation.go
114
simulation.go
@@ -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{}
|
||||||
}
|
}
|
||||||
|
|
||||||
playerDead := false
|
// DEBUG: Start des Chunks
|
||||||
|
// log.Printf("[%s] Simulating Chunk: %d Ticks, Score: %d", sessionID, totalTicks, score)
|
||||||
|
|
||||||
for i := 0; i < totalTicks; i++ {
|
// --- ANTI-CHEAT 1: SPAM SCHUTZ ---
|
||||||
// WICHTIG: Wir erhöhen die Zeit
|
// Wer mehr als 10x pro Sekunde springt, ist ein Bot oder nutzt ein Makro
|
||||||
ticksAlive++
|
jumpCount := 0
|
||||||
|
for _, inp := range inputs {
|
||||||
// LOGIK FIX: Geschwindigkeit basiert jetzt auf ZEIT (Ticks), nicht Score!
|
if inp.Act == "JUMP" {
|
||||||
// 3000 Ticks = ca. 50 Sekunden. Da wird es schneller.
|
jumpCount++
|
||||||
currentSpeed := BaseSpeed + (float64(ticksAlive)/3000.0)*0.5
|
}
|
||||||
if currentSpeed > 12.0 {
|
}
|
||||||
currentSpeed = 12.0
|
if jumpCount > 10 {
|
||||||
|
log.Printf("🤖BOT-ALARM [%s]: Spammt Sprünge (%d Inputs)", sessionID, jumpCount)
|
||||||
|
return true, score, obstacles // Sofort tot
|
||||||
}
|
}
|
||||||
|
|
||||||
|
playerDead := false
|
||||||
|
|
||||||
|
// --- SIMULATION LOOP ---
|
||||||
|
for i := 0; i < totalTicks; i++ {
|
||||||
|
ticksAlive++
|
||||||
|
|
||||||
|
// Speed Scaling (Zeitbasiert)
|
||||||
|
currentSpeed := BaseSpeed + (float64(ticksAlive)/3000.0)*0.5
|
||||||
|
if currentSpeed > 20.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 {
|
if didJump && isGrounded && !isCrouching {
|
||||||
velY = currentJumpPower
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Physik Anwendung
|
||||||
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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user