All checks were successful
Dynamic Branch Deploy / build-and-deploy (push) Successful in 7m51s
257 lines
6.9 KiB
Go
257 lines
6.9 KiB
Go
package physics
|
|
|
|
import (
|
|
"git.zb-server.de/ZB-Server/EscapeFromTeacher/pkg/config"
|
|
"git.zb-server.de/ZB-Server/EscapeFromTeacher/pkg/game"
|
|
)
|
|
|
|
// PlayerPhysicsState enthält den kompletten Physik-Zustand eines Spielers
|
|
type PlayerPhysicsState struct {
|
|
X float64
|
|
Y float64
|
|
VX float64
|
|
VY float64
|
|
OnGround bool
|
|
OnWall bool
|
|
}
|
|
|
|
// PhysicsInput enthält alle Inputs für einen Physik-Tick
|
|
type PhysicsInput struct {
|
|
InputX float64 // -1.0 bis 1.0 (Links/Rechts)
|
|
Jump bool
|
|
Down bool
|
|
}
|
|
|
|
// CollisionChecker ist ein Interface für Kollisionserkennung
|
|
// Server und Client können unterschiedliche Implementierungen haben
|
|
type CollisionChecker interface {
|
|
CheckCollision(x, y, w, h float64) (hit bool, collisionType string)
|
|
}
|
|
|
|
// PlayerConstants enthält alle Spieler-spezifischen Konstanten
|
|
type PlayerConstants struct {
|
|
DrawOffX float64
|
|
DrawOffY float64
|
|
HitboxOffX float64
|
|
HitboxOffY float64
|
|
Width float64
|
|
Height float64
|
|
}
|
|
|
|
// DefaultPlayerConstants gibt die Standard-Spieler-Konstanten zurück
|
|
// WICHTIG: Diese Werte müssen EXAKT mit assets.json übereinstimmen!
|
|
func DefaultPlayerConstants() PlayerConstants {
|
|
return PlayerConstants{
|
|
DrawOffX: -56.0, // Aus assets.json "player"
|
|
DrawOffY: -231.0, // Aus assets.json "player"
|
|
HitboxOffX: 68.0, // Aus assets.json "player" Hitbox.OffsetX
|
|
HitboxOffY: 42.0, // Aus assets.json "player" Hitbox.OffsetY
|
|
Width: 73.0,
|
|
Height: 184.0,
|
|
}
|
|
}
|
|
|
|
// ApplyPhysics wendet einen Physik-Tick auf den Spieler an
|
|
// Diese Funktion wird 1:1 von Server und Client verwendet
|
|
func ApplyPhysics(
|
|
state *PlayerPhysicsState,
|
|
input PhysicsInput,
|
|
currentSpeed float64,
|
|
collisionChecker CollisionChecker,
|
|
playerConst PlayerConstants,
|
|
) {
|
|
// --- HORIZONTALE BEWEGUNG MIT KOLLISION ---
|
|
playerMovement := input.InputX * config.PlayerSpeed
|
|
speed := currentSpeed + playerMovement
|
|
nextX := state.X + speed
|
|
|
|
// Horizontale Kollisionsprüfung
|
|
checkX := nextX + playerConst.DrawOffX + playerConst.HitboxOffX
|
|
checkY := state.Y + playerConst.DrawOffY + playerConst.HitboxOffY
|
|
|
|
xHit, xCollisionType := collisionChecker.CheckCollision(
|
|
checkX,
|
|
checkY,
|
|
playerConst.Width,
|
|
playerConst.Height,
|
|
)
|
|
|
|
// Nur X-Bewegung blockieren wenn es eine Wand/Plattform ist (nicht Obstacle)
|
|
if xHit && (xCollisionType == "wall" || xCollisionType == "platform") {
|
|
// Kollision in X-Richtung -> X nicht ändern
|
|
// state.X bleibt wie es ist
|
|
} else {
|
|
// Keine Kollision -> X-Bewegung durchführen
|
|
state.X = nextX
|
|
}
|
|
|
|
// --- VERTIKALE BEWEGUNG ---
|
|
// An der Wand: Reduzierte Gravität + Klettern
|
|
if state.OnWall {
|
|
state.VY += config.Gravity * 0.3 // 30% Gravität an der Wand
|
|
if state.VY > config.WallSlideMax {
|
|
state.VY = config.WallSlideMax
|
|
}
|
|
// Hochklettern wenn Bewegung vorhanden
|
|
if input.InputX != 0 {
|
|
state.VY = -config.WallClimbSpeed
|
|
}
|
|
} else {
|
|
// Normal: Volle Gravität
|
|
state.VY += config.Gravity
|
|
if state.VY > config.MaxFall {
|
|
state.VY = config.MaxFall
|
|
}
|
|
}
|
|
|
|
// Fast Fall
|
|
if input.Down {
|
|
state.VY = config.FastFall
|
|
}
|
|
|
|
// Sprung
|
|
if input.Jump && state.OnGround {
|
|
state.VY = -config.JumpVelocity
|
|
state.OnGround = false
|
|
}
|
|
|
|
// Vertikale Bewegung mit Kollisionserkennung
|
|
nextY := state.Y + state.VY
|
|
|
|
// Kollision für AKTUELLE Position prüfen (um OnGround richtig zu setzen)
|
|
checkX = state.X + playerConst.DrawOffX + playerConst.HitboxOffX
|
|
currentCheckY := state.Y + playerConst.DrawOffY + playerConst.HitboxOffY
|
|
|
|
currentHit, currentType := collisionChecker.CheckCollision(
|
|
checkX,
|
|
currentCheckY,
|
|
playerConst.Width,
|
|
playerConst.Height,
|
|
)
|
|
|
|
// Wenn Spieler aktuell bereits auf dem Boden steht
|
|
if currentHit && currentType == "platform" && state.VY >= 0 {
|
|
state.OnGround = true
|
|
state.VY = 0
|
|
state.OnWall = false
|
|
// Y-Position bleibt wo sie ist
|
|
return
|
|
}
|
|
|
|
// Kollision für NÄCHSTE Position prüfen
|
|
nextCheckY := nextY + playerConst.DrawOffY + playerConst.HitboxOffY
|
|
|
|
hit, collisionType := collisionChecker.CheckCollision(
|
|
checkX,
|
|
nextCheckY,
|
|
playerConst.Width,
|
|
playerConst.Height,
|
|
)
|
|
|
|
if hit {
|
|
if collisionType == "wall" {
|
|
// An der Wand: Position halten (Y nicht ändern)
|
|
state.VY = 0
|
|
state.OnWall = true
|
|
state.OnGround = false
|
|
} else if collisionType == "obstacle" {
|
|
// Obstacle: Spieler bewegt sich in Obstacle (Server killt dann)
|
|
state.Y = nextY
|
|
state.VY = 0
|
|
state.OnGround = false
|
|
state.OnWall = false
|
|
} else if collisionType == "platform" {
|
|
// Platform: Blockiert vertikale Bewegung
|
|
if state.VY > 0 {
|
|
state.OnGround = true
|
|
}
|
|
state.VY = 0
|
|
state.OnWall = false
|
|
// Y-Position bleibt unverändert
|
|
}
|
|
} else {
|
|
// Keine Kollision: Bewegung durchführen
|
|
state.Y = nextY
|
|
state.OnGround = false
|
|
state.OnWall = false
|
|
}
|
|
}
|
|
|
|
// ClientCollisionChecker implementiert CollisionChecker für den Client
|
|
type ClientCollisionChecker struct {
|
|
World *game.World
|
|
ActiveChunks []game.ActiveChunk
|
|
MovingPlatforms []game.MovingPlatformSync
|
|
}
|
|
|
|
func (c *ClientCollisionChecker) CheckCollision(x, y, w, h float64) (bool, string) {
|
|
playerRect := game.Rect{
|
|
OffsetX: x,
|
|
OffsetY: y,
|
|
W: w,
|
|
H: h,
|
|
}
|
|
|
|
// 0. Basis-Boden-Collider (wie Server) - UNTER dem sichtbaren Gras bis tief in die Erde
|
|
floorCollider := game.Rect{
|
|
OffsetX: -10000,
|
|
OffsetY: 540, // Startet bei Y=540 (Gras-Oberfläche)
|
|
W: 100000000,
|
|
H: 5000, // Geht tief nach UNTEN (bis Y=5540) - gesamte Erdschicht
|
|
}
|
|
if game.CheckRectCollision(playerRect, floorCollider) {
|
|
return true, "platform"
|
|
}
|
|
|
|
// 1. Statische Colliders aus Chunks
|
|
for _, activeChunk := range c.ActiveChunks {
|
|
if chunk, ok := c.World.ChunkLibrary[activeChunk.ChunkID]; ok {
|
|
for _, obj := range chunk.Objects {
|
|
if assetDef, ok := c.World.Manifest.Assets[obj.AssetID]; ok {
|
|
if assetDef.Hitbox.W > 0 && assetDef.Hitbox.H > 0 {
|
|
colliderRect := game.Rect{
|
|
OffsetX: activeChunk.X + obj.X + assetDef.DrawOffX + assetDef.Hitbox.OffsetX,
|
|
OffsetY: obj.Y + assetDef.DrawOffY + assetDef.Hitbox.OffsetY,
|
|
W: assetDef.Hitbox.W,
|
|
H: assetDef.Hitbox.H,
|
|
}
|
|
|
|
if game.CheckRectCollision(playerRect, colliderRect) {
|
|
return true, assetDef.Hitbox.Type
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 2. Bewegende Plattformen
|
|
for _, mp := range c.MovingPlatforms {
|
|
if assetDef, ok := c.World.Manifest.Assets[mp.AssetID]; ok {
|
|
if assetDef.Hitbox.W > 0 && assetDef.Hitbox.H > 0 {
|
|
mpRect := game.Rect{
|
|
OffsetX: mp.X + assetDef.DrawOffX + assetDef.Hitbox.OffsetX,
|
|
OffsetY: mp.Y + assetDef.DrawOffY + assetDef.Hitbox.OffsetY,
|
|
W: assetDef.Hitbox.W,
|
|
H: assetDef.Hitbox.H,
|
|
}
|
|
|
|
if game.CheckRectCollision(playerRect, mpRect) {
|
|
return true, "platform"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false, ""
|
|
}
|
|
|
|
// ServerCollisionChecker ist ein Wrapper für die Server CheckCollision-Methode
|
|
type ServerCollisionChecker struct {
|
|
CheckCollisionFunc func(x, y, w, h float64) (bool, string)
|
|
}
|
|
|
|
func (s *ServerCollisionChecker) CheckCollision(x, y, w, h float64) (bool, string) {
|
|
return s.CheckCollisionFunc(x, y, w, h)
|
|
}
|