bug fixes
All checks were successful
Dynamic Branch Deploy / build-and-deploy (push) Successful in 1m37s
All checks were successful
Dynamic Branch Deploy / build-and-deploy (push) Successful in 1m37s
This commit is contained in:
@@ -42,7 +42,7 @@ func initGameConfig() {
|
||||
{ID: "desk", Type: "obstacle", Width: 40, Height: 30, Color: "#8B4513", Image: "desk1.png"},
|
||||
{ID: "teacher", Type: "teacher", Width: 30, Height: 60, Color: "#000080", Image: "teacher1.png", CanTalk: true, SpeechLines: []string{"Halt!", "Handy weg!"}},
|
||||
{ID: "trashcan", Type: "obstacle", Width: 25, Height: 35, Color: "#555", Image: "trash1.png"},
|
||||
{ID: "eraser", Type: "obstacle", Width: 30, Height: 20, Color: "#fff", Image: "eraser1.png", YOffset: 35.0},
|
||||
{ID: "eraser", Type: "obstacle", Width: 30, Height: 20, Color: "#fff", Image: "eraser1.png", YOffset: 30.0},
|
||||
|
||||
{ID: "principal", Type: "teacher", Width: 40, Height: 70, Color: "#000", Image: "principal1.png", CanTalk: true, SpeechLines: []string{"EXMATRIKULATION!"}},
|
||||
|
||||
|
||||
20
main.go
20
main.go
@@ -34,16 +34,18 @@ func main() {
|
||||
fs := http.FileServer(http.Dir("./static"))
|
||||
http.Handle("/", fs)
|
||||
|
||||
http.HandleFunc("/api/config", handleConfig)
|
||||
http.HandleFunc("/api/start", handleStart)
|
||||
http.HandleFunc("/api/validate", handleValidate)
|
||||
http.HandleFunc("/api/submit-name", handleSubmitName)
|
||||
http.HandleFunc("/api/leaderboard", handleLeaderboard)
|
||||
http.HandleFunc("/api/claim/delete", handleClaimDelete)
|
||||
// API Routes (jetzt mit Logger!)
|
||||
http.HandleFunc("/api/config", Logger(handleConfig))
|
||||
http.HandleFunc("/api/start", Logger(handleStart))
|
||||
http.HandleFunc("/api/validate", Logger(handleValidate))
|
||||
http.HandleFunc("/api/submit-name", Logger(handleSubmitName))
|
||||
http.HandleFunc("/api/leaderboard", Logger(handleLeaderboard))
|
||||
http.HandleFunc("/api/claim/delete", Logger(handleClaimDelete))
|
||||
|
||||
http.HandleFunc("/admin", BasicAuth(handleAdminPage))
|
||||
http.HandleFunc("/api/admin/list", BasicAuth(handleAdminList))
|
||||
http.HandleFunc("/api/admin/action", BasicAuth(handleAdminAction))
|
||||
// Admin Routes (Logger + BasicAuth kombinieren)
|
||||
http.HandleFunc("/admin", Logger(BasicAuth(handleAdminPage)))
|
||||
http.HandleFunc("/api/admin/list", Logger(BasicAuth(handleAdminList)))
|
||||
http.HandleFunc("/api/admin/action", Logger(BasicAuth(handleAdminAction)))
|
||||
|
||||
log.Println("🦖 Server läuft auf :8080")
|
||||
log.Fatal(http.ListenAndServe(":8080", nil))
|
||||
|
||||
60
middleware.go
Normal file
60
middleware.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Wir bauen unseren eigenen ResponseWriter, der den Status-Code "mitschreibt"
|
||||
type StatusRecorder struct {
|
||||
http.ResponseWriter
|
||||
Status int
|
||||
}
|
||||
|
||||
// Überschreiben der WriteHeader Methode, um den Code abzufangen
|
||||
func (r *StatusRecorder) WriteHeader(status int) {
|
||||
r.Status = status
|
||||
r.ResponseWriter.WriteHeader(status)
|
||||
}
|
||||
|
||||
// Die eigentliche Middleware Funktion
|
||||
func Logger(next http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
// 1. Startzeit messen
|
||||
start := time.Now()
|
||||
|
||||
// 2. Recorder initialisieren (Standard ist 200 OK)
|
||||
recorder := &StatusRecorder{
|
||||
ResponseWriter: w,
|
||||
Status: http.StatusOK,
|
||||
}
|
||||
|
||||
// 3. Den echten Handler aufrufen (mit unserem Recorder)
|
||||
next(recorder, r)
|
||||
|
||||
// 4. Dauer berechnen
|
||||
duration := time.Since(start)
|
||||
|
||||
// 5. Loggen
|
||||
// Format: [METHODE] PFAD | STATUS | DAUER | IP
|
||||
// Beispiel: [POST] /api/validate | 200 | 1.2ms | 127.0.0.1
|
||||
|
||||
icon := "✅"
|
||||
if recorder.Status >= 400 {
|
||||
icon = "⚠️"
|
||||
}
|
||||
if recorder.Status >= 500 {
|
||||
icon = "🔥"
|
||||
}
|
||||
|
||||
log.Printf("%s [%s] %s | %d | %v | %s",
|
||||
icon,
|
||||
r.Method,
|
||||
r.URL.Path,
|
||||
recorder.Status,
|
||||
duration,
|
||||
r.RemoteAddr,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -3,13 +3,10 @@ package main
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"math"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[string]string) (bool, int, []ActiveObstacle) {
|
||||
|
||||
posY := parseOr(vals["pos_y"], PlayerYBase)
|
||||
velY := parseOr(vals["vel_y"], 0.0)
|
||||
score := int(parseOr(vals["score"], 0))
|
||||
@@ -19,9 +16,6 @@ func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[st
|
||||
hasBat := vals["p_has_bat"] == "1"
|
||||
bootTicks := int(parseOr(vals["p_boot_ticks"], 0))
|
||||
|
||||
lastJumpDist := parseOr(vals["ac_last_dist"], 0.0)
|
||||
suspicionScore := int(parseOr(vals["ac_suspicion"], 0))
|
||||
|
||||
rng := NewRNG(rngStateVal)
|
||||
|
||||
var obstacles []ActiveObstacle
|
||||
@@ -31,25 +25,12 @@ func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[st
|
||||
obstacles = []ActiveObstacle{}
|
||||
}
|
||||
|
||||
jumpCount := 0
|
||||
for _, inp := range inputs {
|
||||
if inp.Act == "JUMP" {
|
||||
jumpCount++
|
||||
}
|
||||
}
|
||||
|
||||
if jumpCount > 10 {
|
||||
log.Printf("[%s] 🤖 [ANTI-CHEAT] SPAM DETECTED: %d mal JUMP!", sessionID, jumpCount)
|
||||
return true, score, obstacles
|
||||
}
|
||||
|
||||
playerDead := false
|
||||
|
||||
for i := 0; i < totalTicks; i++ {
|
||||
|
||||
currentSpeed := BaseSpeed + (float64(score)/750.0)*0.5
|
||||
if currentSpeed > 14.0 {
|
||||
currentSpeed = 12.0
|
||||
currentSpeed := BaseSpeed + (float64(score) / 1000)
|
||||
if currentSpeed > 10.0 {
|
||||
currentSpeed = 10.0
|
||||
}
|
||||
|
||||
currentJumpPower := JumpPower
|
||||
@@ -72,7 +53,6 @@ func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[st
|
||||
}
|
||||
|
||||
isGrounded := posY >= PlayerYBase-1.0
|
||||
|
||||
currentHeight := PlayerHeight
|
||||
if isCrouching {
|
||||
currentHeight = PlayerHeight / 2
|
||||
@@ -83,32 +63,10 @@ func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[st
|
||||
|
||||
if didJump && isGrounded && !isCrouching {
|
||||
velY = currentJumpPower
|
||||
|
||||
nextObsDist := -1.0
|
||||
for _, o := range obstacles {
|
||||
if o.X > 50.0 {
|
||||
nextObsDist = o.X - 50.0
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if nextObsDist > 0 {
|
||||
diff := math.Abs(nextObsDist - lastJumpDist)
|
||||
if diff < 0.5 {
|
||||
suspicionScore++
|
||||
log.Printf("[%s] ⚠️ [ANTI-CHEAT] Verdächtiger Sprung! Diff: %.4f | Suspicion: %d", sessionID, diff, suspicionScore)
|
||||
} else {
|
||||
if suspicionScore > 0 {
|
||||
suspicionScore--
|
||||
}
|
||||
}
|
||||
lastJumpDist = nextObsDist
|
||||
}
|
||||
}
|
||||
|
||||
velY += Gravity
|
||||
posY += velY
|
||||
|
||||
if posY > PlayerYBase {
|
||||
posY = PlayerYBase
|
||||
velY = 0
|
||||
@@ -129,52 +87,55 @@ func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[st
|
||||
continue
|
||||
}
|
||||
|
||||
paddingX := 5.0
|
||||
paddingY_Top := 5.0
|
||||
if obs.Type == "teacher" {
|
||||
paddingY_Top = 5.0
|
||||
paddingX := 10.0
|
||||
realRightEdge := obs.X + obs.Width - paddingX
|
||||
|
||||
if realRightEdge < 55.0 {
|
||||
nextObstacles = append(nextObstacles, obs)
|
||||
if obs.X+obs.Width > rightmostX {
|
||||
rightmostX = obs.X + obs.Width
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
paddingY_Bottom := 5.0
|
||||
paddingY_Top := 10.0
|
||||
if obs.Type == "teacher" {
|
||||
paddingY_Top = 25.0
|
||||
}
|
||||
|
||||
pLeft, pRight := 50.0+paddingX, 50.0+60.0-paddingX
|
||||
pTop, pBottom := hitboxY+paddingY_Top, hitboxY+currentHeight-paddingY_Bottom
|
||||
pLeft, pRight := 50.0+paddingX, 50.0+30.0-paddingX
|
||||
pTop, pBottom := hitboxY+paddingY_Top, hitboxY+currentHeight-5.0
|
||||
|
||||
oLeft, oRight := obs.X+paddingX, obs.X+obs.Width-paddingX
|
||||
oTop, oBottom := obs.Y+paddingY_Top, obs.Y+obs.Height-paddingY_Bottom
|
||||
oLeft := obs.X + paddingX
|
||||
oRight := obs.X + obs.Width - paddingX + currentSpeed
|
||||
oTop, oBottom := obs.Y+paddingY_Top, obs.Y+obs.Height-5.0
|
||||
|
||||
if pRight > oLeft && pLeft < oRight && pBottom > oTop && pTop < oBottom {
|
||||
isCollision := pRight > oLeft && pLeft < oRight && pBottom > oTop && pTop < oBottom
|
||||
|
||||
if isCollision {
|
||||
if obs.Type == "coin" {
|
||||
score += 2000
|
||||
continue
|
||||
} else if obs.Type == "powerup" {
|
||||
if obs.ID == "p_god" {
|
||||
godLives = 3
|
||||
log.Printf("[%s] 🛡️ POWERUP: Godmode collected", sessionID)
|
||||
}
|
||||
if obs.ID == "p_bat" {
|
||||
hasBat = true
|
||||
log.Printf("[%s] ⚾ POWERUP: Bat collected", sessionID)
|
||||
}
|
||||
if obs.ID == "p_boot" {
|
||||
bootTicks = 600
|
||||
log.Printf("[%s] 👟 POWERUP: Boots collected", sessionID)
|
||||
}
|
||||
continue
|
||||
} else {
|
||||
if hasBat && obs.Type == "teacher" {
|
||||
hasBat = false
|
||||
log.Printf("[%s] ⚾ BAT SMASH! Lehrer %s zerstört.", sessionID, obs.ID)
|
||||
continue
|
||||
}
|
||||
if godLives > 0 {
|
||||
godLives--
|
||||
log.Printf("[%s] 🛡️ GODMODE schützt! (%d lives left)", sessionID, godLives)
|
||||
continue
|
||||
}
|
||||
|
||||
log.Printf("[%s] 💀 [DEATH] PlayerY: %.1f | Obs: %s @ %.1f", sessionID, hitboxY, obs.ID, obs.X)
|
||||
playerDead = true
|
||||
}
|
||||
}
|
||||
@@ -247,11 +208,6 @@ func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[st
|
||||
}
|
||||
}
|
||||
|
||||
if suspicionScore > 10 {
|
||||
log.Printf("[%s] 🤖 [ANTI-CHEAT] BOT BANNED: %d suspicion", sessionID, suspicionScore)
|
||||
playerDead = true
|
||||
}
|
||||
|
||||
obsJson, _ := json.Marshal(obstacles)
|
||||
batStr := "0"
|
||||
if hasBat {
|
||||
@@ -267,8 +223,6 @@ func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[st
|
||||
"p_god_lives": godLives,
|
||||
"p_has_bat": batStr,
|
||||
"p_boot_ticks": bootTicks,
|
||||
"ac_last_dist": fmt.Sprintf("%f", lastJumpDist),
|
||||
"ac_suspicion": suspicionScore,
|
||||
})
|
||||
|
||||
return playerDead, score, obstacles
|
||||
|
||||
328
static/game.js
328
static/game.js
@@ -1,328 +0,0 @@
|
||||
(function() {
|
||||
|
||||
const canvas = document.getElementById('gameCanvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
const container = document.getElementById('game-container');
|
||||
|
||||
const startScreen = document.getElementById('startScreen');
|
||||
const startBtn = document.getElementById('startBtn');
|
||||
const loadingText = document.getElementById('loadingText');
|
||||
const gameOverScreen = document.getElementById('gameOverScreen');
|
||||
|
||||
class PseudoRNG {
|
||||
constructor(seed) {
|
||||
this.state = BigInt(seed);
|
||||
}
|
||||
nextFloat() {
|
||||
const a = 1664525n; const c = 1013904223n; const m = 4294967296n;
|
||||
this.state = (this.state * a + c) % m;
|
||||
return Number(this.state) / Number(m);
|
||||
}
|
||||
nextRange(min, max) {
|
||||
return min + (this.nextFloat() * (max - min));
|
||||
}
|
||||
pick(array) {
|
||||
if (!array || array.length === 0) return null;
|
||||
const idx = Math.floor(this.nextRange(0, array.length));
|
||||
return array[idx];
|
||||
}
|
||||
}
|
||||
|
||||
// Config
|
||||
const GAME_WIDTH = 800; const GAME_HEIGHT = 400;
|
||||
canvas.width = GAME_WIDTH; canvas.height = GAME_HEIGHT;
|
||||
const GRAVITY = 0.6; const JUMP_POWER = -12; const GROUND_Y = 350;
|
||||
const GAME_SPEED = 5; const CHUNK_SIZE = 60;
|
||||
|
||||
// State (JETZT PRIVATE VARIABLEN!)
|
||||
let gameConfig = null;
|
||||
let isLoaded = false;
|
||||
let isGameRunning = false;
|
||||
let isGameOver = false;
|
||||
let sessionID = null;
|
||||
let rng = null;
|
||||
let score = 0;
|
||||
let currentTick = 0;
|
||||
let lastSentTick = 0;
|
||||
let inputLog = [];
|
||||
let isCrouching = false;
|
||||
let sprites = {};
|
||||
let playerSprite = new Image();
|
||||
let bgSprite = new Image();
|
||||
let player = { x: 50, y: 300, w: 30, h: 50, color: "red", vy: 0, grounded: false };
|
||||
let obstacles = [];
|
||||
let serverObstacles = [];
|
||||
|
||||
// --- Funktionen ---
|
||||
|
||||
function resize() {
|
||||
const windowWidth = window.innerWidth;
|
||||
const windowHeight = window.innerHeight;
|
||||
const targetRatio = GAME_WIDTH / GAME_HEIGHT;
|
||||
let finalWidth, finalHeight;
|
||||
if (windowWidth / windowHeight < targetRatio) {
|
||||
finalWidth = windowWidth; finalHeight = windowWidth / targetRatio;
|
||||
} else {
|
||||
finalHeight = windowHeight; finalWidth = finalHeight * targetRatio;
|
||||
}
|
||||
canvas.style.width = `${finalWidth}px`;
|
||||
canvas.style.height = `${finalHeight}px`;
|
||||
if(container) { container.style.width = `${finalWidth}px`; container.style.height = `${finalHeight}px`; }
|
||||
}
|
||||
window.addEventListener('resize', resize); resize();
|
||||
|
||||
async function loadAssets() {
|
||||
playerSprite.src = "assets/player.png";
|
||||
if (gameConfig.backgrounds && gameConfig.backgrounds.length > 0) {
|
||||
const bgName = gameConfig.backgrounds[0];
|
||||
if (!bgName.startsWith("#")) bgSprite.src = "assets/" + bgName;
|
||||
}
|
||||
const promises = gameConfig.obstacles.map(def => {
|
||||
return new Promise((resolve) => {
|
||||
if (!def.image) { resolve(); return; }
|
||||
const img = new Image(); img.src = "assets/" + def.image;
|
||||
img.onload = () => { sprites[def.id] = img; resolve(); };
|
||||
img.onerror = () => { resolve(); };
|
||||
});
|
||||
});
|
||||
if (bgSprite.src) {
|
||||
promises.push(new Promise(r => { bgSprite.onload = r; bgSprite.onerror = r; }));
|
||||
}
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
// Global verfügbar machen für HTML Button
|
||||
window.startGameClick = async function() {
|
||||
if (!isLoaded) return;
|
||||
startScreen.style.display = 'none';
|
||||
try {
|
||||
const sRes = await fetch('/api/start', {method:'POST'});
|
||||
const sData = await sRes.json();
|
||||
sessionID = sData.sessionId;
|
||||
rng = new PseudoRNG(sData.seed);
|
||||
isGameRunning = true;
|
||||
} catch(e) { location.reload(); }
|
||||
};
|
||||
|
||||
function handleInput(action, active) {
|
||||
if (isGameOver) { if(active) location.reload(); return; }
|
||||
const relativeTick = currentTick - lastSentTick;
|
||||
if (action === "JUMP" && active) {
|
||||
if (player.grounded && !isCrouching) {
|
||||
player.vy = JUMP_POWER; player.grounded = false;
|
||||
inputLog.push({ t: relativeTick, act: "JUMP" });
|
||||
}
|
||||
}
|
||||
if (action === "DUCK") { isCrouching = active; }
|
||||
}
|
||||
|
||||
window.addEventListener('keydown', (e) => {
|
||||
if (e.code === 'Space' || e.code === 'ArrowUp') handleInput("JUMP", true);
|
||||
if (e.code === 'ArrowDown' || e.code === 'KeyS') handleInput("DUCK", true);
|
||||
});
|
||||
window.addEventListener('keyup', (e) => {
|
||||
if (e.code === 'ArrowDown' || e.code === 'KeyS') handleInput("DUCK", false);
|
||||
});
|
||||
window.addEventListener('mousedown', (e) => {
|
||||
if (e.target === canvas && e.button === 0) handleInput("JUMP", true);
|
||||
});
|
||||
let touchStartY = 0;
|
||||
window.addEventListener('touchstart', (e) => {
|
||||
if(e.target === canvas) { e.preventDefault(); touchStartY = e.touches[0].clientY; }
|
||||
}, { passive: false });
|
||||
window.addEventListener('touchend', (e) => {
|
||||
if(e.target === canvas) {
|
||||
e.preventDefault();
|
||||
const diff = e.changedTouches[0].clientY - touchStartY;
|
||||
if (diff < -30) handleInput("JUMP", true);
|
||||
else if (diff > 30) { handleInput("DUCK", true); setTimeout(() => handleInput("DUCK", false), 800); }
|
||||
else if (Math.abs(diff) < 10) handleInput("JUMP", true);
|
||||
}
|
||||
});
|
||||
|
||||
async function sendChunk() {
|
||||
const ticksToSend = currentTick - lastSentTick;
|
||||
if (ticksToSend <= 0) return;
|
||||
const payload = { sessionId: sessionID, inputs: [...inputLog], totalTicks: ticksToSend };
|
||||
inputLog = []; lastSentTick = currentTick;
|
||||
try {
|
||||
const res = await fetch('/api/validate', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(payload) });
|
||||
const data = await res.json();
|
||||
if (data.serverObs) serverObstacles = data.serverObs;
|
||||
if (data.status === "dead") gameOver("Vom Server gestoppt");
|
||||
else {
|
||||
const sScore = data.verifiedScore;
|
||||
if (Math.abs(score - sScore) > 200) score = sScore;
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
function updateGameLogic() {
|
||||
if (isCrouching) {
|
||||
const relativeTick = currentTick - lastSentTick;
|
||||
inputLog.push({ t: relativeTick, act: "DUCK" });
|
||||
}
|
||||
const originalHeight = 50; const crouchHeight = 25;
|
||||
player.h = isCrouching ? crouchHeight : originalHeight;
|
||||
let drawY = isCrouching ? player.y + (originalHeight - crouchHeight) : player.y;
|
||||
player.vy += GRAVITY;
|
||||
if (isCrouching && !player.grounded) player.vy += 2.0;
|
||||
player.y += player.vy;
|
||||
if (player.y + originalHeight >= GROUND_Y) {
|
||||
player.y = GROUND_Y - originalHeight; player.vy = 0; player.grounded = true;
|
||||
} else { player.grounded = false; }
|
||||
|
||||
let nextObstacles = []; let rightmostX = 0;
|
||||
for (let obs of obstacles) {
|
||||
obs.x -= GAME_SPEED;
|
||||
const playerHitbox = { x: player.x, y: drawY, w: player.w, h: player.h };
|
||||
if (checkCollision(playerHitbox, obs)) {
|
||||
player.color = "darkred";
|
||||
if (!isGameOver) { sendChunk(); gameOver("Kollision"); }
|
||||
}
|
||||
if (obs.x + obs.def.width > -100) {
|
||||
nextObstacles.push(obs);
|
||||
if (obs.x + obs.def.width > rightmostX) rightmostX = obs.x + obs.def.width;
|
||||
}
|
||||
}
|
||||
obstacles = nextObstacles;
|
||||
|
||||
if (rightmostX < GAME_WIDTH - 10 && gameConfig) {
|
||||
const gap = Math.floor(400 + rng.nextRange(0, 500));
|
||||
let spawnX = rightmostX + gap; if (spawnX < GAME_WIDTH) spawnX = GAME_WIDTH;
|
||||
let possibleObs = [];
|
||||
gameConfig.obstacles.forEach(def => {
|
||||
if (def.id === "eraser") { if (score >= 500) possibleObs.push(def); } else possibleObs.push(def);
|
||||
});
|
||||
const def = rng.pick(possibleObs);
|
||||
let speech = null;
|
||||
if (def && def.canTalk) { if (rng.nextFloat() > 0.7) speech = rng.pick(def.speechLines); }
|
||||
if (def) {
|
||||
const yOffset = def.yOffset || 0;
|
||||
obstacles.push({ x: spawnX, y: GROUND_Y - def.height - yOffset, def: def, speech: speech });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function checkCollision(p, obs) {
|
||||
const paddingX = 10; const paddingY_Top = 25; const paddingY_Bottom = 5;
|
||||
return (p.x + p.w - paddingX > obs.x + paddingX && p.x + paddingX < obs.x + obs.def.width - paddingX &&
|
||||
p.y + p.h - paddingY_Bottom > obs.y + paddingY_Top && p.y + paddingY_Top < obs.y + obs.def.height - paddingY_Bottom);
|
||||
}
|
||||
|
||||
window.submitScore = async function() {
|
||||
const nameInput = document.getElementById('playerNameInput');
|
||||
const name = nameInput.value;
|
||||
const btn = document.getElementById('submitBtn');
|
||||
if (!name) return alert("Namen eingeben!");
|
||||
btn.disabled = true;
|
||||
try {
|
||||
const res = await fetch('/api/submit-name', {
|
||||
method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ sessionId: sessionID, name: name })
|
||||
});
|
||||
const data = await res.json();
|
||||
|
||||
let myClaims = JSON.parse(localStorage.getItem('escape_claims') || '[]');
|
||||
myClaims.push({
|
||||
name: name, score: Math.floor(score / 10), code: data.claimCode,
|
||||
date: new Date().toLocaleString('de-DE'), sessionId: sessionID
|
||||
});
|
||||
localStorage.setItem('escape_claims', JSON.stringify(myClaims));
|
||||
|
||||
document.getElementById('inputSection').style.display = 'none';
|
||||
loadLeaderboard();
|
||||
} catch (e) {}
|
||||
};
|
||||
|
||||
async function loadLeaderboard() {
|
||||
const res = await fetch(`/api/leaderboard?sessionId=${sessionID}`);
|
||||
const entries = await res.json();
|
||||
let html = "<h3>BESTENLISTE</h3>";
|
||||
entries.forEach(e => {
|
||||
const color = e.isMe ? "yellow" : "white";
|
||||
html += `<div style="display:flex; justify-content:space-between; color:${color}; margin-bottom:5px;">
|
||||
<span>#${e.rank} ${e.name}</span><span>${Math.floor(e.score/10)}</span></div>`;
|
||||
});
|
||||
document.getElementById('leaderboard').innerHTML = html;
|
||||
}
|
||||
|
||||
async function loadStartScreenLeaderboard() {
|
||||
try {
|
||||
const listEl = document.getElementById('startLeaderboardList');
|
||||
if (!listEl) return;
|
||||
const res = await fetch('/api/leaderboard');
|
||||
const entries = await res.json();
|
||||
if (entries.length === 0) { listEl.innerHTML = "<div style='padding:20px'>Noch keine Scores.</div>"; return; }
|
||||
let html = "";
|
||||
entries.forEach(e => {
|
||||
let icon = "#" + e.rank;
|
||||
if (e.rank === 1) icon = "🥇"; if (e.rank === 2) icon = "🥈"; if (e.rank === 3) icon = "🥉";
|
||||
html += `<div class="hof-entry"><span><span class="hof-rank">${icon}</span> ${e.name}</span><span class="hof-score">${Math.floor(e.score / 10)}</span></div>`;
|
||||
});
|
||||
listEl.innerHTML = html;
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
function gameOver(reason) {
|
||||
if (isGameOver) return;
|
||||
isGameOver = true;
|
||||
const finalScoreVal = Math.floor(score / 10);
|
||||
const currentHighscore = localStorage.getItem('escape_highscore') || 0;
|
||||
if (finalScoreVal > currentHighscore) localStorage.setItem('escape_highscore', finalScoreVal);
|
||||
gameOverScreen.style.display = 'flex';
|
||||
document.getElementById('finalScore').innerText = finalScoreVal;
|
||||
loadLeaderboard();
|
||||
drawGame();
|
||||
}
|
||||
|
||||
function drawGame() {
|
||||
ctx.clearRect(0, 0, GAME_WIDTH, GAME_HEIGHT);
|
||||
if (bgSprite.complete && bgSprite.naturalHeight !== 0) ctx.drawImage(bgSprite, 0, 0, GAME_WIDTH, GAME_HEIGHT);
|
||||
else { ctx.fillStyle = "#f0f0f0"; ctx.fillRect(0, 0, GAME_WIDTH, GAME_HEIGHT); }
|
||||
ctx.fillStyle = "rgba(60, 60, 60, 0.8)"; ctx.fillRect(0, GROUND_Y, GAME_WIDTH, 50);
|
||||
obstacles.forEach(obs => {
|
||||
const img = sprites[obs.def.id];
|
||||
if (img) ctx.drawImage(img, obs.x, obs.y, obs.def.width, obs.def.height);
|
||||
else { ctx.fillStyle = obs.def.color; ctx.fillRect(obs.x, obs.y, obs.def.width, obs.def.height); }
|
||||
if(obs.speech) drawSpeechBubble(obs.x, obs.y, obs.speech);
|
||||
});
|
||||
ctx.strokeStyle = isGameOver ? "red" : "lime"; ctx.lineWidth = 2;
|
||||
serverObstacles.forEach(srvObs => ctx.strokeRect(srvObs.x, srvObs.y, srvObs.w, srvObs.h));
|
||||
const drawY = isCrouching ? player.y + 25 : player.y; const drawH = isCrouching ? 25 : 50;
|
||||
if (playerSprite.complete && playerSprite.naturalHeight !== 0) ctx.drawImage(playerSprite, player.x, drawY, player.w, drawH);
|
||||
else { ctx.fillStyle = player.color; ctx.fillRect(player.x, drawY, player.w, drawH); }
|
||||
if (isGameOver) { ctx.fillStyle = "rgba(0,0,0,0.7)"; ctx.fillRect(0,0,GAME_WIDTH, GAME_HEIGHT); }
|
||||
}
|
||||
|
||||
function drawSpeechBubble(x, y, text) {
|
||||
const bX = x-20; const bY = y-40; const bW = 120; const bH = 30;
|
||||
ctx.fillStyle="white"; ctx.fillRect(bX,bY,bW,bH); ctx.strokeRect(bX,bY,bW,bH);
|
||||
ctx.fillStyle="black"; ctx.font="10px Arial"; ctx.textAlign="center"; ctx.fillText(text, bX+bW/2, bY+20);
|
||||
}
|
||||
|
||||
function gameLoop() {
|
||||
if (!isLoaded) return;
|
||||
if (isGameRunning && !isGameOver) {
|
||||
updateGameLogic(); currentTick++; score++;
|
||||
const scoreEl = document.getElementById('score'); if (scoreEl) scoreEl.innerText = Math.floor(score / 10);
|
||||
if (currentTick - lastSentTick >= CHUNK_SIZE) sendChunk();
|
||||
}
|
||||
drawGame(); requestAnimationFrame(gameLoop);
|
||||
}
|
||||
|
||||
async function initGame() {
|
||||
try {
|
||||
const cRes = await fetch('/api/config'); gameConfig = await cRes.json();
|
||||
await loadAssets();
|
||||
await loadStartScreenLeaderboard();
|
||||
isLoaded = true;
|
||||
if(loadingText) loadingText.style.display = 'none'; if(startBtn) startBtn.style.display = 'inline-block';
|
||||
const savedHighscore = localStorage.getItem('escape_highscore') || 0;
|
||||
const hsEl = document.getElementById('localHighscore'); if(hsEl) hsEl.innerText = savedHighscore;
|
||||
requestAnimationFrame(gameLoop);
|
||||
} catch(e) { if(loadingText) loadingText.innerText = "Fehler!"; }
|
||||
}
|
||||
|
||||
initGame();
|
||||
|
||||
})();
|
||||
@@ -5,12 +5,11 @@ const GRAVITY = 0.6;
|
||||
const JUMP_POWER = -12;
|
||||
const HIGH_JUMP_POWER = -16;
|
||||
const GROUND_Y = 350;
|
||||
const BASE_SPEED = 5;
|
||||
const BASE_SPEED = 5.0;
|
||||
const CHUNK_SIZE = 60;
|
||||
const TARGET_FPS = 60;
|
||||
const MS_PER_TICK = 1000 / TARGET_FPS;
|
||||
|
||||
|
||||
// RNG Klasse
|
||||
class PseudoRNG {
|
||||
constructor(seed) {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
function updateGameLogic() {
|
||||
// 1. Speed Berechnung (Sync mit Server!)
|
||||
let currentSpeed = BASE_SPEED + (score / 750.0) * 0.5;
|
||||
if (currentSpeed > 14.0) currentSpeed = 14.0;
|
||||
let currentSpeed = BASE_SPEED + (score / 1000);
|
||||
if (currentSpeed > 10.0) currentSpeed = 10.0;
|
||||
|
||||
|
||||
// 2. Input & Sprung
|
||||
if (isCrouching) inputLog.push({ t: currentTick - lastSentTick, act: "DUCK" });
|
||||
@@ -113,7 +114,36 @@ function updateGameLogic() {
|
||||
}
|
||||
|
||||
function checkCollision(p, obs) {
|
||||
const paddingX = 5; const paddingY_Top = 5; const paddingY_Bottom = 5;
|
||||
return (p.x + p.w - paddingX > obs.x + paddingX && p.x + paddingX < obs.x + obs.def.width - paddingX &&
|
||||
p.y + p.h - paddingY_Bottom > obs.y + paddingY_Top && p.y + paddingY_Top < obs.y + obs.def.height - paddingY_Bottom);
|
||||
const paddingX = 10;
|
||||
|
||||
|
||||
const realRightEdge = obs.x + obs.def.width - paddingX;
|
||||
|
||||
if (realRightEdge < p.x + 5) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
const paddingY_Top = (obs.def.type === "teacher") ? 25 : 10;
|
||||
const paddingY_Bottom = 5;
|
||||
|
||||
// Geschwindigkeit schätzen (oder global holen) für CCD
|
||||
let currentSpeed = 5 + (score / 5000.0) * 0.5; // /5000 weil score hier /10 ist?
|
||||
// Moment, in main.js ist 'score' der rohe Wert. Also /500.
|
||||
// Da wir score global haben:
|
||||
currentSpeed = 5 + (score / 500.0) * 0.5;
|
||||
if (currentSpeed > 12.0) currentSpeed = 12.0;
|
||||
|
||||
const pLeft = p.x + paddingX;
|
||||
const pRight = p.x + p.w - paddingX;
|
||||
const pTop = p.y + paddingY_Top;
|
||||
const pBottom = p.y + p.h - paddingY_Bottom;
|
||||
|
||||
const oLeft = obs.x + paddingX;
|
||||
// CCD Erweiterung
|
||||
const oRight = obs.x + obs.def.width - paddingX + currentSpeed;
|
||||
const oTop = obs.y + paddingY_Top;
|
||||
const oBottom = obs.y + obs.def.height - paddingY_Bottom;
|
||||
|
||||
return (pRight > oLeft && pLeft < oRight && pBottom > oTop && pTop < oBottom);
|
||||
}
|
||||
@@ -93,7 +93,7 @@ function drawGame() {
|
||||
|
||||
if(obs.speech) drawSpeechBubble(obs.x, obs.y, obs.speech);
|
||||
});
|
||||
/*
|
||||
|
||||
// --- DEBUG RAHMEN (Server Hitboxen) ---
|
||||
// Grün im Spiel, Rot bei Tod
|
||||
ctx.strokeStyle = isGameOver ? "red" : "lime";
|
||||
@@ -101,7 +101,6 @@ function drawGame() {
|
||||
serverObstacles.forEach(srvObs => {
|
||||
ctx.strokeRect(srvObs.x, srvObs.y, srvObs.w, srvObs.h);
|
||||
});
|
||||
*/
|
||||
|
||||
|
||||
// --- SPIELER ---
|
||||
@@ -130,7 +129,7 @@ function drawGame() {
|
||||
// Drift Info (nur wenn Objekte da sind)
|
||||
if (obstacles.length > 0 && serverObstacles.length > 0) {
|
||||
const drift = Math.abs(obstacles[0].x - serverObstacles[0].x).toFixed(1);
|
||||
// statusText += ` | Drift: ${drift}px`; // Einkommentieren für Debugging
|
||||
statusText += ` | Drift: ${drift}px`; // Einkommentieren für Debugging
|
||||
}
|
||||
|
||||
if(statusText !== "") {
|
||||
|
||||
Reference in New Issue
Block a user