diff --git a/handlers.go b/handlers.go index f5b3a8c..068ee0a 100644 --- a/handlers.go +++ b/handlers.go @@ -3,9 +3,11 @@ package main import ( "encoding/json" "html" + "log" "math/rand" "net/http" "strconv" + "strings" "time" "github.com/google/uuid" @@ -63,19 +65,21 @@ func handleValidate(w http.ResponseWriter, r *http.Request) { } // ---> HIER RUFEN WIR JETZT DIE SIMULATION AUF <--- - isDead, score, obstacles := simulateChunk(req.SessionID, req.Inputs, req.TotalTicks, vals) + isDead, score, obstacles, powerUpState, serverTick := simulateChunk(req.SessionID, req.Inputs, req.TotalTicks, vals) status := "alive" if isDead { status = "dead" rdb.HSet(ctx, key, "is_dead", 1) } - rdb.Expire(ctx, key, 4000*time.Hour) + rdb.Expire(ctx, key, 1*time.Hour) json.NewEncoder(w).Encode(ValidateResponse{ Status: status, VerifiedScore: score, ServerObs: obstacles, + PowerUps: powerUpState, + ServerTick: serverTick, }) } @@ -86,8 +90,19 @@ func handleSubmitName(w http.ResponseWriter, r *http.Request) { return } + // Validierung + if len(req.Name) > 4 { + http.Error(w, "Zu lang", 400) + return + } + if containsBadWord(req.Name) { + http.Error(w, "Name verboten", 400) + return + } + safeName := html.EscapeString(req.Name) sessionKey := "session:" + req.SessionID + scoreVal, err := rdb.HGet(ctx, sessionKey, "score").Result() if err != nil { http.Error(w, "Session expired", 404) @@ -104,10 +119,13 @@ func handleSubmitName(w http.ResponseWriter, r *http.Request) { "created_at": timestamp, }) - rdb.ZAdd(ctx, "leaderboard:unverified", redis.Z{ - Score: float64(scoreInt), - Member: req.SessionID, - }) + // Leaderboard Eintrag + rdb.ZAdd(ctx, "leaderboard:unverified", redis.Z{Score: float64(scoreInt), Member: req.SessionID}) + rdb.ZAdd(ctx, "leaderboard:public", redis.Z{Score: float64(scoreInt), Member: req.SessionID}) + + rdb.Persist(ctx, sessionKey) + + rdb.HDel(ctx, sessionKey, "obstacles", "rng_state", "pos_y", "vel_y", "p_god_lives", "p_has_bat", "p_boot_ticks") w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(SubmitResponse{ClaimCode: claimCode}) @@ -191,6 +209,9 @@ func handleAdminAction(w http.ResponseWriter, r *http.Request) { http.Error(w, "Bad Request", 400) return } + + log.Printf("👮 ADMIN ACTION: %s on %s", req.Action, req.SessionID) + if req.Action == "approve" { score, err := rdb.ZScore(ctx, "leaderboard:unverified", req.SessionID).Result() if err == nil { @@ -200,7 +221,10 @@ func handleAdminAction(w http.ResponseWriter, r *http.Request) { } else if req.Action == "delete" { rdb.ZRem(ctx, "leaderboard:unverified", req.SessionID) rdb.ZRem(ctx, "leaderboard:public", req.SessionID) + + rdb.Del(ctx, "session:"+req.SessionID) } + w.WriteHeader(http.StatusOK) } @@ -210,16 +234,28 @@ func handleClaimDelete(w http.ResponseWriter, r *http.Request) { http.Error(w, "Bad Request", 400) return } + sessionKey := "session:" + req.SessionID realCode, err := rdb.HGet(ctx, sessionKey, "claim_code").Result() - if err != nil || realCode != req.ClaimCode { - http.Error(w, "Error", 403) + if err != nil || realCode == "" { + http.Error(w, "Not found", 404) return } + + if realCode != req.ClaimCode { + http.Error(w, "Wrong Code", 403) + return + } + + log.Printf("🗑️ USER DELETE: Session %s deleted via code", req.SessionID) + + // Aus Listen entfernen rdb.ZRem(ctx, "leaderboard:unverified", req.SessionID) rdb.ZRem(ctx, "leaderboard:public", req.SessionID) - rdb.HDel(ctx, sessionKey, "name") + + rdb.Del(ctx, sessionKey) + w.WriteHeader(http.StatusOK) } @@ -231,3 +267,37 @@ func generateClaimCode() string { } return string(b) } + +func handleAdminBadwords(w http.ResponseWriter, r *http.Request) { + key := "config:badwords" + + // GET: Liste abrufen + if r.Method == http.MethodGet { + words, _ := rdb.SMembers(ctx, key).Result() + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(words) + return + } + + // POST: Hinzufügen oder Löschen + if r.Method == http.MethodPost { + // Wir nutzen ein einfaches Struct für den Request + type WordReq struct { + Word string `json:"word"` + Action string `json:"action"` // "add" oder "remove" + } + var req WordReq + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + http.Error(w, "Bad Request", 400) + return + } + + if req.Action == "add" && req.Word != "" { + rdb.SAdd(ctx, key, strings.ToLower(req.Word)) + } else if req.Action == "remove" && req.Word != "" { + rdb.SRem(ctx, key, strings.ToLower(req.Word)) + } + + w.WriteHeader(http.StatusOK) + } +} diff --git a/main.go b/main.go index d73215c..c2421a6 100644 --- a/main.go +++ b/main.go @@ -30,6 +30,7 @@ func main() { } initGameConfig() + initBadWords() fs := http.FileServer(http.Dir("./static")) http.Handle("/", fs) @@ -44,6 +45,7 @@ func main() { // Admin Routes (Logger + BasicAuth kombinieren) http.HandleFunc("/admin", Logger(BasicAuth(handleAdminPage))) + http.HandleFunc("/api/admin/badwords", Logger(BasicAuth(handleAdminBadwords))) http.HandleFunc("/api/admin/list", Logger(BasicAuth(handleAdminList))) http.HandleFunc("/api/admin/action", Logger(BasicAuth(handleAdminAction))) diff --git a/secure/admin.html b/secure/admin.html index 1e4a820..95549f2 100644 --- a/secure/admin.html +++ b/secure/admin.html @@ -12,7 +12,7 @@ input[type="text"] { padding: 10px; font-size: 16px; width: 250px; - background: #333; border: 1px solid #666; color: white; + background: #333; border: 1px solid #666; color: white; outline: none; } /* Tabs */ @@ -22,14 +22,15 @@ } .tab-btn.active { background: #4caf50; color: white; } .tab-btn#tab-public.active { background: #2196F3; } /* Blau für Public */ + .tab-btn#tab-badwords.active { background: #f44336; } /* Rot für Badwords */ - /* Liste */ + /* Normale Liste */ .entry { background: #333; padding: 15px; margin-bottom: 8px; display: flex; justify-content: space-between; align-items: center; border-left: 5px solid #555; } - .entry.highlight { border-left-color: #ffeb3b; background: #444; } /* Suchtreffer */ + .entry.highlight { border-left-color: #ffeb3b; background: #444; } .info { font-size: 1.1em; } .meta { font-size: 0.85em; color: #aaa; margin-top: 4px; font-family: monospace; } @@ -37,10 +38,42 @@ /* Buttons */ button { cursor: pointer; padding: 8px 15px; border: none; font-weight: bold; color: white; border-radius: 4px; } - .btn-approve { background: #4caf50; margin-right: 5px; } .btn-delete { background: #f44336; } .btn-delete:hover { background: #d32f2f; } + /* BADWORD STYLES (Tags) */ + #badword-container { + display: flex; + flex-wrap: wrap; + gap: 10px; + padding: 20px; + background: #333; + border: 1px solid #555; + } + + .badword-tag { + background: #222; + padding: 8px 15px; + border-radius: 20px; + display: flex; + align-items: center; + gap: 10px; + border: 1px solid #f44336; + color: #ff8a80; + font-weight: bold; + } + + .badword-remove { + cursor: pointer; + color: #f44336; + font-weight: bold; + background: rgba(255,255,255,0.1); + width: 20px; height: 20px; + display: flex; align-items: center; justify-content: center; + border-radius: 50%; + } + .badword-remove:hover { background: #f44336; color: white; } + .empty { text-align: center; padding: 40px; color: #666; font-style: italic; } @@ -52,28 +85,43 @@
- - + +
-
Lade...
+
+
Lade...
+
+ + \ No newline at end of file diff --git a/simulation.go b/simulation.go index 91e0eb7..c0ee4a8 100644 --- a/simulation.go +++ b/simulation.go @@ -8,21 +8,20 @@ import ( "strconv" ) -func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[string]string) (bool, int, []ActiveObstacle) { - // --- 1. STATE LADEN --- +func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[string]string) (bool, int, []ActiveObstacle, PowerUpState, int) { + // 1. State laden posY := parseOr(vals["pos_y"], PlayerYBase) velY := parseOr(vals["vel_y"], 0.0) score := int(parseOr(vals["score"], 0)) - ticksAlive := int(parseOr(vals["total_ticks"], 0)) // Zeit-Basis - + ticksAlive := int(parseOr(vals["total_ticks"], 0)) rngStateVal, _ := strconv.ParseInt(vals["rng_state"], 10, 64) - // Powerups + // Powerups laden godLives := int(parseOr(vals["p_god_lives"], 0)) hasBat := vals["p_has_bat"] == "1" bootTicks := int(parseOr(vals["p_boot_ticks"], 0)) - // Anti-Cheat State laden (Wichtig für Heuristik über Chunks hinweg) + // Anti-Cheat State lastJumpDist := parseOr(vals["ac_last_dist"], 0.0) suspicionScore := int(parseOr(vals["ac_suspicion"], 0)) @@ -35,11 +34,12 @@ func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[st obstacles = []ActiveObstacle{} } - // DEBUG: Start des Chunks - // log.Printf("[%s] Simulating Chunk: %d Ticks, Score: %d", sessionID, totalTicks, score) + // --- DEBUG: Chunk Info --- + if len(inputs) > 0 { + log.Printf("📦 [%s] Processing Chunk: %d Ticks, %d Inputs", sessionID, totalTicks, len(inputs)) + } - // --- ANTI-CHEAT 1: SPAM SCHUTZ --- - // Wer mehr als 10x pro Sekunde springt, ist ein Bot oder nutzt ein Makro + // --- ANTI-CHEAT: Spam Check --- jumpCount := 0 for _, inp := range inputs { if inp.Act == "JUMP" { @@ -47,8 +47,8 @@ func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[st } } if jumpCount > 10 { - log.Printf("🤖BOT-ALARM [%s]: Spammt Sprünge (%d Inputs)", sessionID, jumpCount) - return true, score, obstacles // Sofort tot + log.Printf("🤖 BOT-ALARM [%s]: Spammt Sprünge (%d)", sessionID, jumpCount) + return true, score, obstacles, PowerUpState{}, ticksAlive } playerDead := false @@ -57,24 +57,26 @@ func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[st for i := 0; i < totalTicks; i++ { ticksAlive++ - // Speed Scaling (Zeitbasiert) + // 1. Speed (Zeitbasiert) currentSpeed := BaseSpeed + (float64(ticksAlive)/3000.0)*0.5 - if currentSpeed > 20.0 { - currentSpeed = 20.0 + if currentSpeed > 12.0 { + currentSpeed = 12.0 } - // Jump Power (Boots Powerup) + // 2. Powerups Timer currentJumpPower := JumpPower if bootTicks > 0 { currentJumpPower = HighJumpPower bootTicks-- } - // Input + // 3. Input Verarbeitung (MIT DEBUG LOG) didJump := false isCrouching := false for _, inp := range inputs { if inp.Tick == i { + log.Printf("🕹️ [%s] ACTION at Tick %d: %s", sessionID, ticksAlive, inp.Act) + if inp.Act == "JUMP" { didJump = true } @@ -84,47 +86,38 @@ func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[st } } - // Physik Status + // 4. Physik isGrounded := posY >= PlayerYBase-1.0 - currentHeight := PlayerHeight if isCrouching { currentHeight = PlayerHeight / 2 if !isGrounded { velY += 2.0 - } // Fast fall + } } - // Springen & ANTI-CHEAT 2 (Heuristik) if didJump && isGrounded && !isCrouching { velY = currentJumpPower - // Wir messen den Abstand zum nächsten Hindernis beim Absprung + // Heuristik Anti-Cheat (Abstand messen) var distToObs float64 = -1.0 for _, o := range obstacles { - if o.X > 50.0 { // Das nächste Hindernis vor uns + if o.X > 50.0 { distToObs = o.X - 50.0 break } } - - // Bot Check: Wenn der Abstand IMMER gleich ist (z.B. exakt 75.5px) if distToObs > 0 { diff := math.Abs(distToObs - lastJumpDist) if diff < 1.0 { - // Verdächtig perfekt wiederholt suspicionScore++ - } else { - // Menschliche Varianz -> Verdacht senken - if suspicionScore > 0 { - suspicionScore-- - } + } else if suspicionScore > 0 { + suspicionScore-- } lastJumpDist = distToObs } } - // Physik Anwendung velY += Gravity posY += velY if posY > PlayerYBase { @@ -137,7 +130,7 @@ func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[st hitboxY = posY + (PlayerHeight - currentHeight) } - // Hindernisse bewegen & Kollision + // 5. Hindernisse & Kollision nextObstacles := []ActiveObstacle{} rightmostX := 0.0 @@ -148,7 +141,7 @@ func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[st continue } - // Passed Check (Verhindert "Geister-Kollision" von hinten) + // Passed Check paddingX := 10.0 if obs.X+obs.Width-paddingX < 55.0 { nextObstacles = append(nextObstacles, obs) @@ -186,7 +179,6 @@ func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[st } continue } else { - // Kollision mit Gegner if hasBat && obs.Type == "teacher" { hasBat = false log.Printf("[%s] ⚾ Bat used on %s", sessionID, obs.ID) @@ -194,10 +186,9 @@ func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[st } if godLives > 0 { godLives-- - log.Printf("[%s] 🛡️ Godmode saved life (%d left)", sessionID, godLives) + log.Printf("[%s] 🛡️ Godmode saved life", sessionID) continue } - log.Printf("💀 DEATH [%s]: Hit %s at Tick %d", sessionID, obs.ID, ticksAlive) playerDead = true } @@ -210,7 +201,7 @@ func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[st } obstacles = nextObstacles - // Spawning + // 6. Spawning if rightmostX < GameWidth-10.0 { gap := float64(int(400.0 + rng.NextRange(0, 500))) spawnX := rightmostX + gap @@ -252,7 +243,12 @@ func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[st if def != nil { spawnY := GroundY - def.Height - def.YOffset obstacles = append(obstacles, ActiveObstacle{ - ID: def.ID, Type: def.Type, X: spawnX, Y: spawnY, Width: def.Width, Height: def.Height, + ID: def.ID, + Type: def.Type, + X: spawnX, + Y: spawnY, + Width: def.Width, + Height: def.Height, }) } } @@ -265,13 +261,11 @@ func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[st } } - // --- ANTI-CHEAT CHECK (Ergebnis) --- if suspicionScore > 10 { - log.Printf("🤖BOT-ALARM [%s]: Zu perfekte Sprünge (Heuristik Fail)", sessionID) + log.Printf("🤖 BOT-ALARM [%s]: Zu perfekte Sprünge (Heuristik)", sessionID) playerDead = true } - // --- SPEICHERN --- obsJson, _ := json.Marshal(obstacles) batStr := "0" if hasBat { @@ -288,12 +282,18 @@ func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[st "p_god_lives": godLives, "p_has_bat": batStr, "p_boot_ticks": bootTicks, - // AC Daten speichern für nächsten Chunk "ac_last_dist": fmt.Sprintf("%f", lastJumpDist), "ac_suspicion": suspicionScore, }) - return playerDead, score, obstacles + // Return PowerUp State + pState := PowerUpState{ + GodLives: godLives, + HasBat: hasBat, + BootTicks: bootTicks, + } + + return playerDead, score, obstacles, pState, ticksAlive } func parseOr(s string, def float64) float64 { diff --git a/static/index.html b/static/index.html index 081b6dd..7377c20 100644 --- a/static/index.html +++ b/static/index.html @@ -69,7 +69,7 @@

Dein Score: 0

- +
diff --git a/static/js/config.js b/static/js/config.js index 5eefcc0..7107670 100644 --- a/static/js/config.js +++ b/static/js/config.js @@ -10,7 +10,7 @@ const CHUNK_SIZE = 60; const TARGET_FPS = 60; const MS_PER_TICK = 1000 / TARGET_FPS; -const DEBUG_SYNC = true; +const DEBUG_SYNC = false; const SYNC_TOLERANCE = 5.0; // RNG Klasse diff --git a/static/js/logic.js b/static/js/logic.js index 8dcef70..6a350c2 100644 --- a/static/js/logic.js +++ b/static/js/logic.js @@ -53,7 +53,9 @@ function updateGameLogic() { else if (obs.def.type === "powerup") { if (obs.def.id === "p_god") godModeLives = 3; if (obs.def.id === "p_bat") hasBat = true; - if (obs.def.id === "p_boot") bootTicks = 600; // ca. 10 Sekunden + if (obs.def.id === "p_boot") bootTicks = 600; + + lastPowerupTick = currentTick; continue; // Entfernen (Eingesammelt) } // C. GEGNER / HINDERNIS diff --git a/static/js/main.js b/static/js/main.js index b71fe76..9e99bd2 100644 --- a/static/js/main.js +++ b/static/js/main.js @@ -50,7 +50,7 @@ window.startGameClick = async function() { sessionID = sData.sessionId; rng = new PseudoRNG(sData.seed); isGameRunning = true; - // Wir resetten die Zeit, damit es keinen Sprung gibt + maxRawBgIndex = 0; lastTime = performance.now(); resize(); } catch(e) { @@ -100,32 +100,48 @@ window.submitScore = async function() { // ========================================== window.showMyCodes = function() { if(window.openModal) window.openModal('codes'); + const listEl = document.getElementById('codesList'); if(!listEl) return; - const claims = JSON.parse(localStorage.getItem('escape_claims') || '[]'); - if (claims.length === 0) { + + const rawClaims = JSON.parse(localStorage.getItem('escape_claims') || '[]'); + + if (rawClaims.length === 0) { listEl.innerHTML = "
Keine Codes gespeichert.
"; return; } + const sortedClaims = rawClaims + .map((item, index) => ({ ...item, originalIndex: index })) + .sort((a, b) => b.score - a.score); + let html = ""; - for (let i = claims.length - 1; i >= 0; i--) { - const c = claims[i]; + + + sortedClaims.forEach(c => { const canDelete = c.sessionId ? true : false; const btnStyle = canDelete ? "cursor:pointer; color:#ff4444; border-color:#ff4444;" : "cursor:not-allowed; color:gray; border-color:gray;"; - const btnAttr = canDelete ? `onclick="deleteClaim(${i}, '${c.sessionId}', '${c.code}')"` : "disabled"; + const btnAttr = canDelete ? `onclick="deleteClaim(${c.originalIndex}, '${c.sessionId}', '${c.code}')"` : "disabled"; + + let rankIcon = "📄"; + if (c.score >= 10000) rankIcon = "🔥"; + if (c.score >= 5000) rankIcon = "⭐"; html += `
- ${c.code} - (${c.score} Pkt)
+ ${rankIcon} ${c.code} + (${c.score} Pkt)
${c.name} • ${c.date}
- +
`; - } + }); + listEl.innerHTML = html; }; @@ -150,13 +166,38 @@ async function loadLeaderboard() { try { const res = await fetch(`/api/leaderboard?sessionId=${sessionID}`); const entries = await res.json(); - let html = "

BESTENLISTE

"; + + let html = "

BESTENLISTE

"; + entries.forEach(e => { const color = e.isMe ? "yellow" : "white"; - html += `
#${e.rank} ${e.name}${Math.floor(e.score/10)}
`; + const bgStyle = e.isMe ? "background:rgba(255,255,0,0.1);" : ""; + + const betterThanMe = e.rank - 1; + let infoText = ""; + + if (e.isMe && betterThanMe > 0) { + infoText = `
(${betterThanMe} waren besser)
`; + } else if (e.isMe && betterThanMe === 0) { + infoText = `
👑 NIEMAND ist besser!
`; + } + + html += ` +
+
+ #${e.rank} ${e.name.toUpperCase()} + ${Math.floor(e.score/10)} +
+ ${infoText} +
`; + + if(e.rank === 3 && entries.length > 3 && !entries[3].isMe) { + html += "
...
"; + } }); + document.getElementById('leaderboard').innerHTML = html; - } catch(e) {} + } catch(e) { console.error(e); } } async function loadStartScreenLeaderboard() { diff --git a/static/js/network.js b/static/js/network.js index 627787d..975c748 100644 --- a/static/js/network.js +++ b/static/js/network.js @@ -27,11 +27,21 @@ async function sendChunk() { if (data.serverObs) { serverObstacles = data.serverObs; - // --- NEU: DEBUG MODUS VERGLEICH --- if (typeof DEBUG_SYNC !== 'undefined' && DEBUG_SYNC) { compareState(snapshotobstacles, data.serverObs); } - // ---------------------------------- + + if (data.powerups) { + const sTick = data.serverTick; + + if (lastPowerupTick > sTick) { + } else { + godModeLives = data.powerups.godLives; + hasBat = data.powerups.hasBat; + bootTicks = data.powerups.bootTicks; + } + } + } if (data.status === "dead") { diff --git a/static/js/render.js b/static/js/render.js index b947f9e..96bfcc2 100644 --- a/static/js/render.js +++ b/static/js/render.js @@ -44,29 +44,29 @@ resize(); // --- DRAWING --- function drawGame() { - // 1. Alles löschen ctx.clearRect(0, 0, GAME_WIDTH, GAME_HEIGHT); - // --- HINTERGRUND (Level-Wechsel) --- let currentBg = null; - // Haben wir Hintergründe geladen? if (bgSprites.length > 0) { - // Wechsel alle 2000 Punkte (Server-Score) = 200 Punkte (Anzeige) const changeInterval = 10000; - // Berechne Index: 0-1999 -> 0, 2000-3999 -> 1, etc. - // Das % (Modulo) sorgt dafür, dass es wieder von vorne anfängt, wenn die Bilder ausgehen - const bgIndex = Math.floor(score / changeInterval) % bgSprites.length; + const currentRawIndex = Math.floor(score / changeInterval); + + if (currentRawIndex > maxRawBgIndex) { + maxRawBgIndex = currentRawIndex; + } + const bgIndex = maxRawBgIndex % bgSprites.length; currentBg = bgSprites[bgIndex]; } + if (currentBg && currentBg.complete && currentBg.naturalHeight !== 0) { ctx.drawImage(currentBg, 0, 0, GAME_WIDTH, GAME_HEIGHT); } else { - // Fallback: Hellgrau, falls Bild fehlt + // Fallback ctx.fillStyle = "#f0f0f0"; ctx.fillRect(0, 0, GAME_WIDTH, GAME_HEIGHT); } @@ -127,7 +127,7 @@ function drawGame() { if(bootTicks > 0) statusText += `👟 ${(bootTicks/60).toFixed(1)}s`; // Drift Info (nur wenn Objekte da sind) - if (obstacles.length > 0 && serverObstacles.length > 0) { + if (DEBUG_SYNC == true && 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 } diff --git a/static/js/state.js b/static/js/state.js index 799b659..7ca67f0 100644 --- a/static/js/state.js +++ b/static/js/state.js @@ -19,10 +19,12 @@ let bootTicks = 0; // Hintergrund let currentBgIndex = 0; +let maxRawBgIndex = 0; // Tick Time let lastTime = 0; let accumulator = 0; +let lastPowerupTick = -9999; // Grafiken let sprites = {}; diff --git a/types.go b/types.go index 64ee69c..83f5462 100644 --- a/types.go +++ b/types.go @@ -39,11 +39,18 @@ type ValidateRequest struct { TotalTicks int `json:"totalTicks"` } +type PowerUpState struct { + GodLives int `json:"godLives"` + HasBat bool `json:"hasBat"` + BootTicks int `json:"bootTicks"` +} + type ValidateResponse struct { Status string `json:"status"` VerifiedScore int `json:"verifiedScore"` ServerObs []ActiveObstacle `json:"serverObs"` - ActivePowerup string `json:"activePowerup"` + PowerUps PowerUpState `json:"powerups"` + ServerTick int `json:"serverTick"` } type StartResponse struct { diff --git a/utils.go b/utils.go new file mode 100644 index 0000000..d44cf91 --- /dev/null +++ b/utils.go @@ -0,0 +1,35 @@ +package main + +import ( + "log" + "strings" +) + +func initBadWords() { + key := "config:badwords" + count, _ := rdb.SCard(ctx, key).Result() + + if count == 0 { + log.Println("Lade Default Badwords...") + defaults := []string{"admin", "root", "hitler", "nazi", "sex", "fuck", "shit", "ass"} + for _, w := range defaults { + rdb.SAdd(ctx, key, w) + } + } +} + +func containsBadWord(name string) bool { + badwords, _ := rdb.SMembers(ctx, "config:badwords").Result() + + lowerName := strings.ToLower(name) + + for _, word := range badwords { + if word == "" { + continue + } + if strings.Contains(lowerName, strings.ToLower(word)) { + return true + } + } + return false +}