package main import ( "log" "math/rand" "time" "git.zb-server.de/ZB-Server/EscapeFromTeacher/pkg/config" "git.zb-server.de/ZB-Server/EscapeFromTeacher/pkg/game" ) // startOfflineGame initialisiert eine lokale Spielrunde ohne Server func (g *Game) startOfflineGame() { g.resetForNewGame() g.isOffline = true g.connected = false // Explizit offline g.appState = StateGame // Initialen GameState lokal erstellen g.stateMutex.Lock() g.gameState = game.GameState{ Status: "RUNNING", RoomID: "offline_solo", Players: make(map[string]game.PlayerState), WorldChunks: []game.ActiveChunk{{ChunkID: "start", X: 0}}, CurrentSpeed: config.RunSpeed, DifficultyFactor: 0, } // Lokalen Spieler hinzufügen g.gameState.Players[g.playerName] = game.PlayerState{ ID: g.playerName, Name: g.playerName, X: 100, Y: 200, IsAlive: true, } g.stateMutex.Unlock() // Initialer Chunk-Library Check if len(g.world.ChunkLibrary) == 0 { log.Println("⚠️ Warnung: Keine Chunks in Library geladen!") } g.roundStartTime = time.Now() g.predictedX = 100 g.predictedY = 200 g.currentSpeed = config.RunSpeed g.audio.PlayMusic() g.notifyGameStarted() log.Println("🕹️ Offline-Modus gestartet") } // updateOfflineLoop simuliert die Server-Logik lokal func (g *Game) updateOfflineLoop() { if !g.isOffline || g.gameState.Status != "RUNNING" { return } g.stateMutex.Lock() defer g.stateMutex.Unlock() elapsed := time.Since(g.roundStartTime).Seconds() // 1. Schwierigkeit & Speed (analog zu pkg/server/room.go) g.gameState.DifficultyFactor = elapsed / config.MaxDifficultySeconds if g.gameState.DifficultyFactor > 1.0 { g.gameState.DifficultyFactor = 1.0 } speedIncrease := g.gameState.DifficultyFactor * g.gameState.DifficultyFactor * 18.0 g.gameState.CurrentSpeed = config.RunSpeed + speedIncrease g.currentSpeed = g.gameState.CurrentSpeed // 2. Scrolling g.gameState.ScrollX += g.currentSpeed // 3. Chunks nachladen // Wenn das Ende der Map nah am rechten Rand ist, neuen Chunk spawnen mapEnd := 0.0 for _, c := range g.gameState.WorldChunks { chunkDef := g.world.ChunkLibrary[c.ChunkID] end := c.X + float64(chunkDef.Width*config.TileSize) if end > mapEnd { mapEnd = end } } if mapEnd < g.gameState.ScrollX+2500 { g.spawnOfflineChunk(mapEnd) } // 4. Entferne alte Chunks (links aus dem Bild) if len(g.gameState.WorldChunks) > 5 { // Behalte immer mindestens die letzten paar Chunks if g.gameState.WorldChunks[0].X < g.gameState.ScrollX-2000 { g.gameState.WorldChunks = g.gameState.WorldChunks[1:] } } // 5. Score Update (Distanz) p, ok := g.gameState.Players[g.playerName] if ok && p.IsAlive { // Grobe Score-Simulation p.Score = int(g.gameState.ScrollX / 10) g.gameState.Players[g.playerName] = p } } func (g *Game) spawnOfflineChunk(atX float64) { // Zufälligen Chunk wählen var pool []string for id := range g.world.ChunkLibrary { if id != "start" { pool = append(pool, id) } } if len(pool) > 0 { randomID := pool[rand.Intn(len(pool))] g.gameState.WorldChunks = append(g.gameState.WorldChunks, game.ActiveChunk{ ChunkID: randomID, X: atX, }) } }