Private
Public Access
1
0
Files
EscapeFromTeacher/cmd/client/prediction.go
Sebastian Unterschütz f48ade50bb
All checks were successful
Dynamic Branch Deploy / build-and-deploy (push) Successful in 8m20s
fix game
2026-03-21 13:31:34 +01:00

178 lines
5.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package main
import (
"git.zb-server.de/ZB-Server/EscapeFromTeacher/pkg/game"
"git.zb-server.de/ZB-Server/EscapeFromTeacher/pkg/physics"
)
// ApplyInput wendet einen Input auf den vorhergesagten Zustand an
// Nutzt die gemeinsame Physik-Engine aus pkg/physics
func (g *Game) ApplyInput(input InputState) {
// Horizontale Bewegung mit analogem Joystick
moveX := 0.0
if input.Left {
moveX = -1.0
} else if input.Right {
moveX = 1.0
}
// Wenn Joystick benutzt wird, überschreibe moveX mit analogem Wert
if input.JoyX != 0 {
moveX = input.JoyX
}
// Physik-State vorbereiten
state := physics.PlayerPhysicsState{
X: g.predictedX,
Y: g.predictedY,
VX: g.predictedVX,
VY: g.predictedVY,
OnGround: g.predictedGround,
OnWall: g.predictedOnWall,
}
// Physik-Input vorbereiten
physicsInput := physics.PhysicsInput{
InputX: moveX,
Jump: input.Jump,
Down: input.Down,
}
// Kollisions-Checker vorbereiten
g.stateMutex.Lock()
collisionChecker := &physics.ClientCollisionChecker{
World: g.world,
ActiveChunks: g.gameState.WorldChunks,
MovingPlatforms: g.gameState.MovingPlatforms,
}
g.stateMutex.Unlock()
// Gemeinsame Physik anwenden (1:1 wie Server)
physics.ApplyPhysics(&state, physicsInput, g.currentSpeed, collisionChecker, physics.DefaultPlayerConstants())
// Ergebnis zurückschreiben
g.predictedX = state.X
g.predictedY = state.Y
g.predictedVX = state.VX
g.predictedVY = state.VY
g.predictedGround = state.OnGround
g.predictedOnWall = state.OnWall
}
// ReconcileWithServer gleicht lokale Prediction mit Server-State ab
func (g *Game) ReconcileWithServer(serverState game.PlayerState) {
g.predictionMutex.Lock()
defer g.predictionMutex.Unlock()
// Server-bestätigte Sequenz
g.lastServerSeq = serverState.LastInputSeq
// Entferne alle bestätigten Inputs
for seq := range g.pendingInputs {
if seq <= g.lastServerSeq {
delete(g.pendingInputs, seq)
}
}
// Temporäre Position für Replay (jetzt MIT Y-Achse)
replayX := serverState.X
replayY := serverState.Y
replayVX := serverState.VX
replayVY := serverState.VY
replayGround := serverState.OnGround
replayOnWall := serverState.OnWall
// Replay alle noch nicht bestätigten Inputs mit VOLLER Physik
if len(g.pendingInputs) > 0 {
for seq := g.lastServerSeq + 1; seq <= g.inputSequence; seq++ {
if input, ok := g.pendingInputs[seq]; ok {
// Temporär auf Replay-Position setzen
oldX, oldY := g.predictedX, g.predictedY
oldVX, oldVY := g.predictedVX, g.predictedVY
oldGround := g.predictedGround
oldOnWall := g.predictedOnWall
g.predictedX = replayX
g.predictedY = replayY
g.predictedVX = replayVX
g.predictedVY = replayVY
g.predictedGround = replayGround
g.predictedOnWall = replayOnWall
g.ApplyInput(input)
replayX = g.predictedX
replayY = g.predictedY
replayVX = g.predictedVX
replayVY = g.predictedVY
replayGround = g.predictedGround
replayOnWall = g.predictedOnWall
// Zurücksetzen
g.predictedX = oldX
g.predictedY = oldY
g.predictedVX = oldVX
g.predictedVY = oldVY
g.predictedGround = oldGround
g.predictedOnWall = oldOnWall
}
}
}
// Berechne Differenz zwischen Client-Prediction und Server-Replay (X und Y)
diffX := replayX - g.predictedX
diffY := replayY - g.predictedY
dist := diffX*diffX + diffY*diffY
// Speichere Korrektur-Magnitude für Debug
g.correctionX = diffX
g.correctionY = diffY
// === SMOOTH CORRECTION MIT VISUELLEM OFFSET ===
//
// Strategie: Physics springt sofort zur richtigen Position (korrekte Physik),
// aber der angezeigte Wert (predictedX/Y + correctionOffsetX/Y) springt NICHT
// er driftet sanft über ~80ms zur Physics-Position hin.
//
// Dadurch sehen Spieler keinen "Pushback/Teleport" mehr, selbst wenn der Server
// eine größere Korrektur schickt.
const (
acceptTolerance = 400.0 // ~20px: keine Korrektur
criticalTolerance = 2500.0 // ~50px: sanfte Korrektur
)
if dist < acceptTolerance {
// Kleine Abweichung (<20px): Client-Position beibehalten, kein Eingriff nötig
} else if dist < criticalTolerance {
// Mittlere Abweichung (20-50px): sanfte Physics-Korrektur (25%)
// Visueller Offset kompensiert die Bewegung → kein sichtbarer Sprung
factor := 0.25
corrX := diffX * factor
corrY := diffY * factor
g.correctionOffsetX -= corrX // Offset gegenläufig zur Physics-Korrektur
g.correctionOffsetY -= corrY
g.predictedX += corrX
g.predictedY += corrY
g.correctionCount++
} else {
// Große Abweichung (>50px): Physics springt komplett zur Replay-Position.
// Visueller Offset hält das Bild an der alten Stelle Spieler sieht keinen Teleport.
g.correctionOffsetX += g.predictedX - replayX // = -(replayX - predictedX) = -diffX
g.correctionOffsetY += g.predictedY - replayY
g.predictedX = replayX
g.predictedY = replayY
g.correctionCount++
}
// Velocity und Ground-State immer vom Replay übernehmen.
// Replay-Werte kommen aus korrekter Server-State + Pending-Input-Replay
// und sind physikalisch genauer als reine Client-Prediction über Zeit.
g.predictedVX = replayVX
g.predictedVY = replayVY
g.predictedGround = replayGround
g.predictedOnWall = replayOnWall
}