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 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) }