update workflows and game logic: add CERT_ISSUER support, enhance offline mode with countdown, and adjust default audio settings
All checks were successful
Dynamic Branch Deploy / build-and-deploy (push) Successful in 1m50s
All checks were successful
Dynamic Branch Deploy / build-and-deploy (push) Successful in 1m50s
This commit is contained in:
7
.github/workflows/deploy.yaml
vendored
7
.github/workflows/deploy.yaml
vendored
@@ -35,11 +35,13 @@ jobs:
|
||||
APP_URL="${{ env.BASE_DOMAIN }}"
|
||||
TARGET_NS="${REPO_LOWER}"
|
||||
BUILD_MODE="main"
|
||||
CERT_ISSUER="letsencrypt-prod"
|
||||
echo "Mode: PRODUCTION (Root Domain)"
|
||||
else
|
||||
APP_URL="${REPO_LOWER}-${BRANCH_LOWER}.${{ env.BASE_DOMAIN }}"
|
||||
TARGET_NS="${REPO_LOWER}-${BRANCH_LOWER}"
|
||||
BUILD_MODE="dev"
|
||||
CERT_ISSUER="letsencrypt-prod"
|
||||
echo "Mode: DEVELOPMENT (Subdomain)"
|
||||
fi
|
||||
|
||||
@@ -53,6 +55,7 @@ jobs:
|
||||
echo "DEBUG: URL: $APP_URL"
|
||||
echo "DEBUG: Branch-Tag: $BRANCH_TAG"
|
||||
echo "DEBUG: Build-Mode: $BUILD_MODE"
|
||||
echo "DEBUG: Cert-Issuer: $CERT_ISSUER"
|
||||
|
||||
# In Gitea Actions Environment schreiben
|
||||
echo "FULL_IMAGE_PATH=$FULL_IMAGE_PATH" >> $GITHUB_ENV
|
||||
@@ -62,6 +65,7 @@ jobs:
|
||||
echo "IMAGE_TAG=$IMAGE_TAG" >> $GITHUB_ENV
|
||||
echo "BRANCH_TAG=$BRANCH_TAG" >> $GITHUB_ENV
|
||||
echo "BUILD_MODE=$BUILD_MODE" >> $GITHUB_ENV
|
||||
echo "CERT_ISSUER=$CERT_ISSUER" >> $GITHUB_ENV
|
||||
|
||||
# 3. Prüfen ob ein Image-Rebuild nötig ist
|
||||
- name: Detect Source Changes
|
||||
@@ -171,6 +175,9 @@ jobs:
|
||||
# TARGET_NS überall ersetzen (z.B. für Middlewares oder explizite Namespaces)
|
||||
find k8s/ -name "*.yaml" -exec sed -i "s|\${TARGET_NS}|${{ env.TARGET_NS }}|g" {} +
|
||||
|
||||
# CERT_ISSUER in allen K8s-Manifesten ersetzen
|
||||
find k8s/ -name "*.yaml" -exec sed -i "s|\${CERT_ISSUER}|${{ env.CERT_ISSUER }}|g" {} +
|
||||
|
||||
# Admin-Credentials Secret anlegen/aktualisieren (aus Gitea Secret)
|
||||
kubectl create secret generic admin-credentials \
|
||||
--from-literal=username="${{ secrets.ADMIN_USER }}" \
|
||||
|
||||
@@ -51,8 +51,8 @@ func NewAudioSystem() *AudioSystem {
|
||||
|
||||
as := &AudioSystem{
|
||||
audioContext: ctx,
|
||||
musicVolume: 0.3, // 30% Standard-Lautstärke
|
||||
sfxVolume: 0.5, // 50% Standard-Lautstärke
|
||||
musicVolume: 0.7, // 70% Standard-Lautstärke
|
||||
sfxVolume: 0.3, // 30% Standard-Lautstärke
|
||||
muted: false,
|
||||
}
|
||||
|
||||
|
||||
@@ -18,10 +18,11 @@ func (g *Game) startOfflineGame() {
|
||||
g.connected = false // Explizit offline
|
||||
g.appState = StateGame
|
||||
|
||||
// Initialen GameState lokal erstellen
|
||||
// Initialen GameState lokal erstellen (mit Countdown)
|
||||
g.stateMutex.Lock()
|
||||
g.gameState = game.GameState{
|
||||
Status: "RUNNING",
|
||||
Status: "COUNTDOWN",
|
||||
TimeLeft: 3,
|
||||
RoomID: "offline_solo",
|
||||
Players: make(map[string]game.PlayerState),
|
||||
WorldChunks: []game.ActiveChunk{{ChunkID: "start", X: 0}},
|
||||
@@ -48,28 +49,48 @@ func (g *Game) startOfflineGame() {
|
||||
log.Println("⚠️ Warnung: Keine Chunks in Library geladen!")
|
||||
}
|
||||
|
||||
g.roundStartTime = time.Now()
|
||||
// Startzeit für Countdown
|
||||
g.roundStartTime = time.Now().Add(3 * time.Second)
|
||||
g.predictedX = 100
|
||||
g.predictedY = 200
|
||||
g.currentSpeed = config.RunSpeed
|
||||
g.currentSpeed = 0 // Stillstand während Countdown
|
||||
|
||||
g.audio.PlayMusic()
|
||||
g.notifyGameStarted()
|
||||
log.Println("🕹️ Offline-Modus gestartet")
|
||||
log.Println("🕹️ Offline-Modus mit Countdown gestartet")
|
||||
}
|
||||
|
||||
// updateOfflineLoop simuliert die Server-Logik lokal
|
||||
func (g *Game) updateOfflineLoop() {
|
||||
if !g.isOffline || g.gameState.Status != "RUNNING" {
|
||||
if !g.isOffline || g.gameState.Status == "GAMEOVER" {
|
||||
return
|
||||
}
|
||||
|
||||
g.stateMutex.Lock()
|
||||
defer g.stateMutex.Unlock()
|
||||
|
||||
// 1. Status Logic (Countdown -> Running)
|
||||
if g.gameState.Status == "COUNTDOWN" {
|
||||
rem := time.Until(g.roundStartTime)
|
||||
g.gameState.TimeLeft = int(rem.Seconds()) + 1
|
||||
|
||||
if rem <= 0 {
|
||||
log.Println("🚀 Offline: GO!")
|
||||
g.gameState.Status = "RUNNING"
|
||||
g.gameState.TimeLeft = 0
|
||||
g.audio.PlayMusic()
|
||||
// Reset roundStartTime auf den tatsächlichen Spielstart für Schwierigkeits-Skalierung
|
||||
g.roundStartTime = time.Now()
|
||||
}
|
||||
return // Während Countdown keine weitere Logik (kein Scrolling, etc.)
|
||||
}
|
||||
|
||||
if g.gameState.Status != "RUNNING" {
|
||||
return
|
||||
}
|
||||
|
||||
elapsed := time.Since(g.roundStartTime).Seconds()
|
||||
|
||||
// 1. Schwierigkeit & Speed
|
||||
// 2. Schwierigkeit & Speed
|
||||
g.gameState.DifficultyFactor = elapsed / config.MaxDifficultySeconds
|
||||
if g.gameState.DifficultyFactor > 1.0 {
|
||||
g.gameState.DifficultyFactor = 1.0
|
||||
@@ -79,10 +100,10 @@ func (g *Game) updateOfflineLoop() {
|
||||
g.gameState.CurrentSpeed = config.RunSpeed + speedIncrease
|
||||
g.currentSpeed = g.gameState.CurrentSpeed
|
||||
|
||||
// 2. Scrolling
|
||||
// 3. Scrolling
|
||||
g.gameState.ScrollX += g.currentSpeed
|
||||
|
||||
// 3. Chunks nachladen
|
||||
// 4. Chunks nachladen
|
||||
mapEnd := 0.0
|
||||
for _, c := range g.gameState.WorldChunks {
|
||||
chunkDef := g.world.ChunkLibrary[c.ChunkID]
|
||||
@@ -96,7 +117,7 @@ func (g *Game) updateOfflineLoop() {
|
||||
g.spawnOfflineChunk(mapEnd)
|
||||
}
|
||||
|
||||
// 4. Entferne alte Chunks
|
||||
// 5. Entferne alte Chunks
|
||||
if len(g.gameState.WorldChunks) > 5 {
|
||||
if g.gameState.WorldChunks[0].X < g.gameState.ScrollX-2000 {
|
||||
// Bereinige auch Moving Platforms des alten Chunks
|
||||
@@ -112,10 +133,10 @@ func (g *Game) updateOfflineLoop() {
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Update Moving Platforms
|
||||
// 6. Update Moving Platforms
|
||||
g.updateOfflineMovingPlatforms()
|
||||
|
||||
// 6. Player State Update (Score, Powerups, Collisions)
|
||||
// 7. Player State Update (Score, Powerups, Collisions)
|
||||
p, ok := g.gameState.Players[g.playerName]
|
||||
if ok && p.IsAlive {
|
||||
// Basis-Score aus Distanz
|
||||
@@ -177,25 +198,25 @@ func (g *Game) spawnOfflineChunk(atX float64) {
|
||||
X: atX,
|
||||
})
|
||||
|
||||
// Extrahiere Moving Platforms aus dem neuen Chunk
|
||||
// Extrahiere Plattformen aus dem neuen Chunk
|
||||
chunkDef := g.world.ChunkLibrary[randomID]
|
||||
for i, obj := range chunkDef.Objects {
|
||||
asset, ok := g.world.Manifest.Assets[obj.AssetID]
|
||||
if ok && asset.Type == "moving_platform" && obj.MovingPlatform != nil {
|
||||
mp := obj.MovingPlatform
|
||||
// In Solo gibt es keine MovingPlatformData, Plattformen sind statisch
|
||||
if ok && asset.Type == "moving_platform" {
|
||||
p := &MovingPlatform{
|
||||
ChunkID: randomID,
|
||||
ObjectIdx: i,
|
||||
AssetID: obj.AssetID,
|
||||
StartX: atX + mp.StartX,
|
||||
StartY: mp.StartY,
|
||||
EndX: atX + mp.EndX,
|
||||
EndY: mp.EndY,
|
||||
Speed: mp.Speed,
|
||||
StartX: atX + obj.X,
|
||||
StartY: obj.Y,
|
||||
EndX: atX + obj.X,
|
||||
EndY: obj.Y,
|
||||
Speed: 0,
|
||||
Direction: 1.0,
|
||||
IsActive: true,
|
||||
CurrentX: atX + mp.StartX,
|
||||
CurrentY: mp.StartY,
|
||||
IsActive: false,
|
||||
CurrentX: atX + obj.X,
|
||||
CurrentY: obj.Y,
|
||||
HitboxW: asset.Hitbox.W,
|
||||
HitboxH: asset.Hitbox.H,
|
||||
DrawOffX: asset.DrawOffX,
|
||||
@@ -268,13 +289,17 @@ func (g *Game) checkOfflineCollisions(p *game.PlayerState) {
|
||||
chunkDef := g.world.ChunkLibrary[ac.ChunkID]
|
||||
for i, obj := range chunkDef.Objects {
|
||||
asset, ok := g.world.Manifest.Assets[obj.AssetID]
|
||||
if !ok { continue }
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
objID := fmt.Sprintf("%s_%d", ac.ChunkID, i)
|
||||
|
||||
// 1. COINS
|
||||
if asset.Type == "coin" {
|
||||
if g.gameState.CollectedCoins[objID] { continue }
|
||||
if g.gameState.CollectedCoins[objID] {
|
||||
continue
|
||||
}
|
||||
|
||||
coinX := ac.X + obj.X + asset.DrawOffX + asset.Hitbox.OffsetX
|
||||
coinY := obj.Y + asset.DrawOffY + asset.Hitbox.OffsetY
|
||||
@@ -306,7 +331,9 @@ func (g *Game) checkOfflineCollisions(p *game.PlayerState) {
|
||||
|
||||
// 2. POWERUPS
|
||||
if asset.Type == "powerup" {
|
||||
if g.gameState.CollectedPowerups[objID] { continue }
|
||||
if g.gameState.CollectedPowerups[objID] {
|
||||
continue
|
||||
}
|
||||
|
||||
puRect := game.Rect{
|
||||
OffsetX: ac.X + obj.X + asset.DrawOffX + asset.Hitbox.OffsetX,
|
||||
|
||||
@@ -12,6 +12,14 @@ import (
|
||||
// ApplyInput wendet einen Input auf den vorhergesagten Zustand an
|
||||
// Nutzt die gemeinsame Physik-Engine aus pkg/physics
|
||||
func (g *Game) ApplyInput(input InputState) {
|
||||
g.stateMutex.Lock()
|
||||
status := g.gameState.Status
|
||||
g.stateMutex.Unlock()
|
||||
|
||||
if status == "COUNTDOWN" {
|
||||
return
|
||||
}
|
||||
|
||||
// Horizontale Bewegung mit analogem Joystick
|
||||
moveX := 0.0
|
||||
if input.Left {
|
||||
@@ -203,40 +211,14 @@ func (g *Game) checkSoloRound() {
|
||||
return
|
||||
}
|
||||
|
||||
// 1. Lokale Todes-Erkennung (Obstacles & Grenzen)
|
||||
// Wir nutzen die vorhergesagte Position
|
||||
pConst := physics.DefaultPlayerConstants()
|
||||
checkX := g.predictedX + pConst.DrawOffX + pConst.HitboxOffX
|
||||
checkY := g.predictedY + pConst.DrawOffY + pConst.HitboxOffY
|
||||
|
||||
// 1. Lokale Todes-Erkennung (Nur noch Grenzen im Solo-Modus)
|
||||
g.stateMutex.Lock()
|
||||
collisionChecker := &physics.ClientCollisionChecker{
|
||||
World: g.world,
|
||||
ActiveChunks: g.gameState.WorldChunks,
|
||||
MovingPlatforms: g.gameState.MovingPlatforms,
|
||||
}
|
||||
scrollX := g.gameState.ScrollX
|
||||
|
||||
hasGodMode := false
|
||||
for _, p := range g.gameState.Players {
|
||||
if p.Name == g.playerName {
|
||||
hasGodMode = p.HasGodMode
|
||||
break
|
||||
}
|
||||
}
|
||||
g.stateMutex.Unlock()
|
||||
|
||||
// Kollision mit Hindernis?
|
||||
hit, colType := collisionChecker.CheckCollision(checkX, checkY, pConst.Width, pConst.Height)
|
||||
|
||||
isDead := false
|
||||
deathReason := ""
|
||||
|
||||
if hit && colType == "obstacle" && !hasGodMode {
|
||||
isDead = true
|
||||
deathReason = "Hindernis berührt"
|
||||
}
|
||||
|
||||
// Aus dem linken Bildschirmrand gefallen?
|
||||
if g.predictedX < scrollX-50 {
|
||||
isDead = true
|
||||
|
||||
@@ -630,8 +630,8 @@ function toggleAudio() {
|
||||
if (window.setSFXVolume) window.setSFXVolume(0);
|
||||
} else {
|
||||
btn.textContent = '🔊';
|
||||
const musicVol = parseInt(localStorage.getItem('escape_music_volume') || 70) / 100;
|
||||
const sfxVol = parseInt(localStorage.getItem('escape_sfx_volume') || 70) / 100;
|
||||
const musicVol = parseInt(localStorage.getItem('escape_music_volume') || 80) / 100;
|
||||
const sfxVol = parseInt(localStorage.getItem('escape_sfx_volume') || 40) / 100;
|
||||
if (window.setMusicVolume) window.setMusicVolume(musicVol);
|
||||
if (window.setSFXVolume) window.setSFXVolume(sfxVol);
|
||||
}
|
||||
@@ -656,7 +656,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
});
|
||||
|
||||
// Load saved value
|
||||
const savedMusic = localStorage.getItem('escape_music_volume') || 70;
|
||||
const savedMusic = localStorage.getItem('escape_music_volume') || 80;
|
||||
musicSlider.value = savedMusic;
|
||||
musicValue.textContent = savedMusic + '%';
|
||||
}
|
||||
@@ -673,7 +673,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
});
|
||||
|
||||
// Load saved value
|
||||
const savedSFX = localStorage.getItem('escape_sfx_volume') || 70;
|
||||
const savedSFX = localStorage.getItem('escape_sfx_volume') || 40;
|
||||
sfxSlider.value = savedSFX;
|
||||
sfxValue.textContent = savedSFX + '%';
|
||||
}
|
||||
|
||||
@@ -95,14 +95,14 @@
|
||||
<div class="settings-group">
|
||||
<div class="setting-item">
|
||||
<label>MUSIK LAUTSTÄRKE:</label>
|
||||
<input type="range" id="musicVolume" min="0" max="100" value="70">
|
||||
<span id="musicValue">70%</span>
|
||||
<input type="range" id="musicVolume" min="0" max="100" value="80">
|
||||
<span id="musicValue">80%</span>
|
||||
</div>
|
||||
|
||||
<div class="setting-item">
|
||||
<label>SFX LAUTSTÄRKE:</label>
|
||||
<input type="range" id="sfxVolume" min="0" max="100" value="70">
|
||||
<span id="sfxValue">70%</span>
|
||||
<input type="range" id="sfxVolume" min="0" max="100" value="40">
|
||||
<span id="sfxValue">40%</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ kind: Ingress
|
||||
metadata:
|
||||
name: game-ingress
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
cert-manager.io/cluster-issuer: ${CERT_ISSUER}
|
||||
traefik.ingress.kubernetes.io/router.entrypoints: web, websecure
|
||||
traefik.ingress.kubernetes.io/router.middlewares: gitea-redirect-https@kubernetescrd,${TARGET_NS}-compress@kubernetescrd
|
||||
spec:
|
||||
|
||||
Reference in New Issue
Block a user