From 56dd8db9a387a21539089337987f82ae4f8fe57f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Untersch=C3=BCtz?= Date: Sun, 30 Nov 2025 19:00:59 +0100 Subject: [PATCH] add Sprechblasen --- types.go | 1 + websocket.go | 99 +++++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 83 insertions(+), 17 deletions(-) diff --git a/types.go b/types.go index bfdc1b5..0bedfb6 100644 --- a/types.go +++ b/types.go @@ -57,6 +57,7 @@ type ActiveObstacle struct { Y float64 `json:"y"` Width float64 `json:"w"` Height float64 `json:"h"` + Speech string `json:"speech,omitempty"` } type ActivePlatform struct { diff --git a/websocket.go b/websocket.go index 5265db2..8018243 100644 --- a/websocket.go +++ b/websocket.go @@ -235,46 +235,93 @@ func handleWebSocket(w http.ResponseWriter, r *http.Request) { } } -// ... (generateFutureObjects bleibt gleich wie vorher) ... +// Hilfsfunktion: Generiert Objekte für EINEN Tick in der Zukunft func generateFutureObjects(s *SimState, tick int, speed float64) ([]ActiveObstacle, []ActivePlatform) { var createdObs []ActiveObstacle var createdPlats []ActivePlatform + // Initialisierung beim ersten Lauf if s.NextSpawnTick == 0 { s.NextSpawnTick = tick + 50 } + // Ist es Zeit für etwas Neues? if tick >= s.NextSpawnTick { spawnX := SpawnXStart - chunkCount := len(s.Chunks) + // --- ENTSCHEIDUNG: CHUNK vs RANDOM --- + + // Wir nutzen die globalen Chunks (da Read-Only während des Spiels, ist Zugriff sicher) + chunkCount := len(defaultConfig.Chunks) + if chunkCount > 0 && s.RNG.NextFloat() > 0.8 { + // ================================================= + // OPTION A: CHUNK SPAWNING + // ================================================= idx := int(s.RNG.NextRange(0, float64(chunkCount))) - chunk := s.Chunks[idx] + chunk := defaultConfig.Chunks[idx] + // 1. Plattformen übernehmen for _, p := range chunk.Platforms { - createdPlats = append(createdPlats, ActivePlatform{X: spawnX + p.X, Y: p.Y, Width: p.Width, Height: p.Height}) - } - for _, o := range chunk.Obstacles { - createdObs = append(createdObs, ActiveObstacle{ID: o.ID, Type: o.Type, X: spawnX + o.X, Y: o.Y, Width: o.Width, Height: o.Height}) + createdPlats = append(createdPlats, ActivePlatform{ + X: spawnX + p.X, + Y: p.Y, + Width: p.Width, + Height: p.Height, + }) } - width := chunk.TotalWidth - if width == 0 { - width = 2000 + // 2. Hindernisse übernehmen & Speech berechnen + for _, o := range chunk.Obstacles { + + // Speech-Logik: Wir müssen die Original-Def finden, um zu wissen, ob er sprechen kann + speech := "" + for _, def := range defaultConfig.Obstacles { + if def.ID == o.ID { + // Wenn gefunden, würfeln wir + if def.CanTalk && len(def.SpeechLines) > 0 { + if s.RNG.NextFloat() > 0.7 { // 30% Wahrscheinlichkeit + sIdx := int(s.RNG.NextRange(0, float64(len(def.SpeechLines)))) + speech = def.SpeechLines[sIdx] + } + } + break // Def gefunden, Loop abbrechen + } + } + + createdObs = append(createdObs, ActiveObstacle{ + ID: o.ID, + Type: o.Type, + X: spawnX + o.X, + Y: o.Y, + Width: o.Width, + Height: o.Height, + Speech: speech, // <--- HIER wird der Text gesetzt + }) } - s.NextSpawnTick = tick + int(float64(width)/speed) + + // Timer setzen (Länge des Chunks) + width := float64(chunk.TotalWidth) + if width == 0 { + width = 2000.0 + } + s.NextSpawnTick = tick + int(width/speed) } else { - // Random Logic + // ================================================= + // OPTION B: RANDOM SPAWNING + // ================================================= + + // Lücke berechnen gap := 400 + int(s.RNG.NextRange(0, 500)) s.NextSpawnTick = tick + int(float64(gap)/speed) + // Pool bilden (Boss Phase?) defs := defaultConfig.Obstacles if len(defs) > 0 { - // Boss Check isBoss := (tick % 1500) > 1200 var pool []ObstacleDef + for _, d := range defs { if isBoss { if d.ID == "principal" || d.ID == "trashcan" { @@ -287,18 +334,36 @@ func generateFutureObjects(s *SimState, tick int, speed float64) ([]ActiveObstac } } + // Objekt auswählen def := s.RNG.PickDef(pool) + if def != nil { - // RNG Calls to keep sync (optional now, but good practice) - if def.CanTalk && s.RNG.NextFloat() > 0.7 { - } + // Powerup Rarity (90% Chance, dass es NICHT spawnt) if def.Type == "powerup" && s.RNG.NextFloat() > 0.1 { def = nil } if def != nil { + // Speech Logik + speech := "" + if def.CanTalk && len(def.SpeechLines) > 0 { + if s.RNG.NextFloat() > 0.7 { + sIdx := int(s.RNG.NextRange(0, float64(len(def.SpeechLines)))) + speech = def.SpeechLines[sIdx] + } + } + + // Y-Position berechnen (Boden - Höhe - Offset) + spawnY := GroundY - def.Height - def.YOffset + createdObs = append(createdObs, ActiveObstacle{ - ID: def.ID, Type: def.Type, X: spawnX, Y: GroundY - def.Height - def.YOffset, Width: def.Width, Height: def.Height, + ID: def.ID, + Type: def.Type, + X: spawnX, + Y: spawnY, + Width: def.Width, + Height: def.Height, + Speech: speech, // <--- HIER wird der Text gesetzt }) } }