diff --git a/cmd/client/game_render.go b/cmd/client/game_render.go index e3a1089..4d731da 100644 --- a/cmd/client/game_render.go +++ b/cmd/client/game_render.go @@ -362,11 +362,12 @@ func (g *Game) DrawGame(screen *ebiten.Image) { sprite := "player" // Default: am Boden // Nur Jump-Animation wenn wirklich in der Luft - // (nicht auf Boden, nicht auf Platform mit VY ~= 0) - isInAir := !onGround && (vy < -1.0 || vy > 1.0) + // Bei 20 TPS größerer Threshold (3.0 statt 1.0) + // OnGround oder sehr kleine VY = am Boden/Plattform + isInAir := !onGround && (vy < -3.0 || vy > 3.0) if isInAir { - if vy < -2.0 { + if vy < -5.0 { // Springt nach oben sprite = "jump0" } else { diff --git a/cmd/client/prediction.go b/cmd/client/prediction.go index 7d15681..376b66b 100644 --- a/cmd/client/prediction.go +++ b/cmd/client/prediction.go @@ -21,8 +21,8 @@ func (g *Game) ApplyInput(input InputState) { } // Geschwindigkeit skaliert mit Joystick-Intensität - // war 4.0 bei 60 TPS (4.0 * 3 = 12.0) - speed := config.RunSpeed + (moveX * 12.0) + // Bewegung relativ zum Scroll (symmetrisch) + speed := config.RunSpeed + (moveX * config.PlayerSpeed) g.predictedX += speed // Gravitation @@ -33,12 +33,12 @@ func (g *Game) ApplyInput(input InputState) { // Fast Fall if input.Down { - g.predictedVY = 45.0 // war 15.0 bei 60 TPS (15.0 * 3) + g.predictedVY = config.FastFall } // Sprung if input.Jump && g.predictedGround { - g.predictedVY = -30.0 // Reduziert für besseres Spielgefühl bei 20 TPS + g.predictedVY = -config.JumpVelocity g.predictedGround = false } diff --git a/cmd/client/web/game.js b/cmd/client/web/game.js index 038d271..95abacc 100644 --- a/cmd/client/web/game.js +++ b/cmd/client/web/game.js @@ -1,6 +1,7 @@ // Game State let wasmReady = false; let gameStarted = false; +let gameStarting = false; // Verhindert doppeltes Starten let audioMuted = false; let currentLeaderboard = []; // Store full leaderboard data with proof codes @@ -234,7 +235,7 @@ window.onWasmReady = function() { }; // Cache Management - Version wird bei jedem Build aktualisiert -const CACHE_VERSION = 1767639190355; // Wird durch Build-Prozess ersetzt +const CACHE_VERSION = 1767643088942; // Wird durch Build-Prozess ersetzt // Fetch mit Cache-Busting async function fetchWithCache(url) { @@ -315,7 +316,22 @@ function startSoloGame() { return; } + // Verhindere doppeltes Starten + if (gameStarting) { + console.log('⚠️ Game is already starting...'); + return; + } + gameStarting = true; + const playerName = document.getElementById('playerName').value || 'ANON'; + const startBtn = document.getElementById('startBtn'); + + // Button deaktivieren und Loading anzeigen + if (startBtn) { + startBtn.disabled = true; + startBtn.innerHTML = ' Starte...'; + startBtn.style.opacity = '0.6'; + } // Store in localStorage for WASM to read localStorage.setItem('escape_player_name', playerName); @@ -341,6 +357,21 @@ function createRoom() { return; } + // Verhindere doppeltes Starten + if (gameStarting) { + console.log('⚠️ Game is already starting...'); + return; + } + gameStarting = true; + + const createBtn = document.getElementById('createRoomBtn'); + // Button deaktivieren und Loading anzeigen + if (createBtn) { + createBtn.disabled = true; + createBtn.innerHTML = ' Erstelle Raum...'; + createBtn.style.opacity = '0.6'; + } + const playerName = document.getElementById('playerName').value || 'ANON'; const roomID = 'R' + Math.random().toString(36).substr(2, 5).toUpperCase(); const teamName = 'TEAM'; @@ -352,11 +383,16 @@ function createRoom() { localStorage.setItem('escape_team_name', teamName); localStorage.setItem('escape_is_host', 'true'); - // Show Lobby - setUIState(UIState.LOBBY); - document.getElementById('lobbyRoomCode').textContent = roomID; - document.getElementById('lobbyHostControls').classList.remove('hidden'); - document.getElementById('lobbyStatus').textContent = 'Du bist Host - starte wenn bereit!'; + // Show Lobby nach kurzer Verzögerung (damit User Feedback sieht) + setTimeout(() => { + setUIState(UIState.LOBBY); + document.getElementById('lobbyRoomCode').textContent = roomID; + document.getElementById('lobbyHostControls').classList.remove('hidden'); + document.getElementById('lobbyStatus').textContent = 'Du bist Host - starte wenn bereit!'; + + // Reset gameStarting für Lobby + gameStarting = false; + }, 300); // Trigger WASM game start (im Hintergrund) if (window.startGame) { @@ -375,12 +411,34 @@ function joinRoom() { return; } + // Verhindere doppeltes Starten + if (gameStarting) { + console.log('⚠️ Game is already starting...'); + return; + } + gameStarting = true; + + const joinBtn = document.getElementById('joinRoomBtn'); + // Button deaktivieren und Loading anzeigen + if (joinBtn) { + joinBtn.disabled = true; + joinBtn.innerHTML = ' Trete bei...'; + joinBtn.style.opacity = '0.6'; + } + const playerName = document.getElementById('playerName').value || 'ANON'; const roomID = document.getElementById('joinRoomCode').value.toUpperCase(); const teamName = document.getElementById('teamNameJoin').value || 'TEAM'; if (!roomID || roomID.length < 4) { alert('Bitte gib einen gültigen Raum-Code ein!'); + // Reset bei Fehler + gameStarting = false; + if (joinBtn) { + joinBtn.disabled = false; + joinBtn.innerHTML = 'RAUM BEITRETEN'; + joinBtn.style.opacity = '1'; + } return; } @@ -391,11 +449,20 @@ function joinRoom() { localStorage.setItem('escape_team_name', teamName); localStorage.setItem('escape_is_host', 'false'); - // Show Lobby - setUIState(UIState.LOBBY); - document.getElementById('lobbyRoomCode').textContent = roomID; - document.getElementById('lobbyHostControls').classList.add('hidden'); - document.getElementById('lobbyStatus').textContent = 'Warte auf Host...'; + // Show Lobby nach kurzer Verzögerung + setTimeout(() => { + setUIState(UIState.LOBBY); + document.getElementById('lobbyRoomCode').textContent = roomID; + document.getElementById('lobbyHostControls').classList.add('hidden'); + document.getElementById('lobbyStatus').textContent = 'Warte auf Host...'; + + // Reset gameStarting für Lobby + gameStarting = false; + }, 300); + + // Reset gameStarting für Lobby + gameStarting = false; + }, 300); // Trigger WASM game start (im Hintergrund) if (window.startGame) { diff --git a/cmd/client/web/index.html b/cmd/client/web/index.html index 9b73def..65ab4e7 100644 --- a/cmd/client/web/index.html +++ b/cmd/client/web/index.html @@ -291,7 +291,7 @@ diff --git a/cmd/client/web/style.css b/cmd/client/web/style.css index 1fd58dc..fcda342 100644 --- a/cmd/client/web/style.css +++ b/cmd/client/web/style.css @@ -39,6 +39,7 @@ input[type=range]{width:100%;max-width:300px} .setting-item span{color:#fff;font-size:14px} .loading-screen{position:absolute;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,.95);display:flex;flex-direction:column;justify-content:center;align-items:center;z-index:9999} .spinner{border:8px solid #333;border-top:8px solid #fc0;border-radius:50%;width:60px;height:60px;animation:spin 1s linear infinite} +.spinner-small{display:inline-block;border:2px solid #666;border-top:2px solid #000;border-radius:50%;width:12px;height:12px;animation:spin 1s linear infinite;vertical-align:middle;margin-right:8px} @keyframes spin{0%{transform:rotate(0)}100%{transform:rotate(360deg)}} #mute-btn{position:fixed;top:10px;left:10px;z-index:10000;background:rgba(0,0,0,.5);border:2px solid #555;color:#fff;font-size:20px;width:40px;height:40px;border-radius:50%;cursor:pointer;padding:0;margin:0;display:flex;align-items:center;justify-content:center;box-shadow:0 0 10px rgba(0,0,0,.5)} #mute-btn:hover{background:rgba(255,255,255,.2);border-color:#fff} diff --git a/pkg/config/config.go b/pkg/config/config.go index 631b70c..0124058 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -10,13 +10,19 @@ const ( // Physics (angepasst für 20 TPS statt 60 TPS) // Bei 20 TPS ist jeder Tick 3x länger (50ms statt 16.67ms) - // Geschwindigkeiten müssen mit 3 multipliziert werden Gravity = 1.5 // war 0.5 bei 60 TPS MaxFall = 45.0 // war 15.0 bei 60 TPS TileSize = 64 + // Player Movement (bei 20 TPS) + RunSpeed = 21.0 // Basis-Scroll-Geschwindigkeit + PlayerSpeed = 33.0 // Links/Rechts Bewegung relativ zu Scroll (war 11.0 * 3) + JumpVelocity = 24.0 // Sprunghöhe (reduziert für besseres Gefühl) + FastFall = 45.0 // Schnell-Fall nach unten + WallSlideMax = 9.0 // Maximale Rutsch-Geschwindigkeit an Wand + WallClimbSpeed = 15.0 // Kletter-Geschwindigkeit + // Gameplay - RunSpeed = 21.0 // war 7.0 bei 60 TPS (7.0 * 3) StartTime = 5 // Sekunden Countdown TickRate = time.Millisecond * 50 // 20 TPS (war 16ms für 60 TPS) diff --git a/pkg/server/room.go b/pkg/server/room.go index d585bb2..aee2d81 100644 --- a/pkg/server/room.go +++ b/pkg/server/room.go @@ -261,17 +261,17 @@ func (r *Room) HandleInput(input game.ClientInput) { switch input.Type { case "JUMP": if p.OnGround { - p.VY = -30.0 // Reduziert für besseres Spielgefühl bei 20 TPS + p.VY = -config.JumpVelocity p.OnGround = false p.DoubleJumpUsed = false // Reset double jump on ground jump } else if p.HasDoubleJump && !p.DoubleJumpUsed { // Double Jump in der Luft - p.VY = -30.0 // Reduziert für besseres Spielgefühl bei 20 TPS + p.VY = -config.JumpVelocity p.DoubleJumpUsed = true log.Printf("⚡ %s verwendet Double Jump!", p.Name) } case "DOWN": - p.VY = 45.0 // war 15.0 bei 60 TPS (15.0 * 3) + p.VY = config.FastFall case "LEFT_DOWN": p.InputX = -1 case "LEFT_UP": @@ -355,11 +355,10 @@ func (r *Room) Update() { } // X Bewegung - // Symmetrische Geschwindigkeit: Links = Rechts - // Nach rechts: CurrentSpeed + 33, Nach links: CurrentSpeed - 33 - // Verwendet r.CurrentSpeed statt config.RunSpeed für dynamische Geschwindigkeit - // war 11.0 bei 60 TPS (11.0 * 3 = 33.0) - currentSpeed := r.CurrentSpeed + (p.InputX * 33.0) + // Spieler bewegt sich relativ zum Scroll + // Scroll-Geschwindigkeit + Links/Rechts Bewegung + playerMovement := p.InputX * config.PlayerSpeed + currentSpeed := r.CurrentSpeed + playerMovement nextX := p.X + currentSpeed hitX, typeX := r.CheckCollision(nextX+r.pDrawOffX+r.pHitboxOffX, p.Y+r.pDrawOffY+r.pHitboxOffY, r.pW, r.pH) @@ -408,13 +407,13 @@ func (r *Room) Update() { if p.OnWall { // Wandrutschen (langsame Fallgeschwindigkeit) p.VY += config.Gravity * 0.3 // 30% Gravität an der Wand - if p.VY > 9.0 { // war 3.0 bei 60 TPS (3.0 * 3) - p.VY = 9.0 // Maximal 9.0 beim Rutschen + if p.VY > config.WallSlideMax { + p.VY = config.WallSlideMax } // Hochklettern wenn nach oben gedrückt (InputX in Wandrichtung) if p.InputX != 0 { - p.VY = -15.0 // war -5.0 bei 60 TPS (-5.0 * 3) + p.VY = -config.WallClimbSpeed } } else { // Normal: Volle Gravität