Files
Wackelpeter/AGENTS.md
T

11 KiB
Raw Blame History

AGENTS.md

Git-Regeln

  • Git-Commits nur auf explizite Anforderung des Users. Niemals eigenständig committen.
  • Vor einem Commit: git status, git diff und git log --oneline -10 prüfen; nur beabsichtigte Dateien stagen, keine Secrets.
  • Commit-Message kurz und im Stil des Repos (bisher englisch, imperative).

Build & Verifikation

cd web
npx tsc --noEmit    # Typcheck (Pflicht nach Änderungen)
npm run build       # tsc + vite build → web/dist
npm run dev         # Dev-Server mit HMR
  • Stack: Three.js 0.170 + TypeScript + Vite. Keine weiteren Laufzeit-Dependencies (kein Postprocessing verfügbar — Screen-Effekte als CSS-Overlays).
  • Makefile: make web/make dev/make build (Container-Build via Podman).
  • UI-Sprache: Deutsch (Skill-Namen, Level-up-Karten); Code-Kommentare bisher gemischt, Git-Messages englisch.

Modell-Viewer (visuelle Modell-Checks)

  • web/viewer.html = eigener Vite-Entry (auch im Build): rendert ein Gegner-/Boss-Modell isoliert auf Grid-Bühne mit hellem Licht, Turntable-Kamera (Auto-Fit per Box3) und Mini-UI zum Umschalten. Modelle laufen gegen ein Stub-Game (Stub-Spieler als grüner Blob 8 m entfernt als Blick-/Angriffsziel).
  • URL-Params: ?m=<id> (ids wie dragon, krake, spider, … siehe Registry in viewer.ts), &still=1 = statische Pose (nur updateAnimation, kein update).
  • Automatisierte Screenshots: scripts/model_shot.sh <id> [shots=4] [intervall=1.5] [still] — startet eigenen Dev-Server auf Port 3100, öffnet Viewer-Fenster (Titel „Wackelpeter Modell-Viewer"), minimiert die Konsole per KWin, macht shots Turntable-Screenshots nach /tmp/modelshot_*_crop.png, blendet die Konsole wieder ein, schließt das Viewer-Fenster und stoppt den 3100er-Server. Pfade kommen auf stdout.
  • Workflow für Modell-Überarbeitung: Code editieren → scripts/model_shot.sh <id> → Crops lesen → iterieren. Keine Cheats, Klicks oder Bosskämpfe nötig.

Architektur (web/src)

Datei Inhalt
Game.ts Zentrale Klasse: State-Machine, Render-Loop (animate()), Input, UI-Verdrahtung, Spawning, Partikel-/Effekt-Arrays (bloodParticles, traps, gasClouds, slashEffects, projectiles), Hotbar
Player.ts Blob-Spieler: Movement, Sprung, damage()/heal() (Schild-Absorption), Stats (applySkillSystem), Hurt-Feedback (Flash + Blut via game.onPlayerHurt)
Enemy.ts Abstrakte Basis: body (THREE.Group), takeDamage (Sound + XP/Lifesteal via game.onDamageDealt), Fade-Death, Felder knockback, rootTime
Skills.ts Alle Skill-Definitionen (SKILLS-Array: id, name, type, maxLevel, describe) + Level-Wertetabellen (z. B. auraRadius(level)). SkillSystem = XP/Level/Angebote. HOTBAR_SKILLS = aktive Skills in Erwerbs-Reihenfolge
SkillIcons.ts SVG-Icons (64×64, Template-Strings) für die Level-up-Karten
Weapon.ts Basis für aktive Skills: cooldown1-3 + reduceCooldowns()
Sword.ts / Staff.ts Linksklick-Schwert (Bogen-Trefferzone via SwordTrail.ts), Rechtsklick-Feuerball + Teleport
ChainLightning.ts Kettenblitz (Q-Slot)
Trapper.ts Falle (skill1) + Gaswolke (skill2)
Aura.ts / Boomerang.ts Passive Skills, lesen Level live aus game.skillSystem
Trap.ts / GasCloud.ts Platzierbare Effekte; update(dt, game): boolean (false = entfernen), dispose()
Golem.ts / Krake.ts / Minotaurus.ts / SpiderQueen.ts / FrostGiant.ts / Necromancer.ts / Dragon.ts Bosse (Welle 10 / 20 / 30 / 40 / 50 / 60 / 70), registriert in Game.bossWaves ({name, spawn}). Krake: stationär (spawnAtCenter), Tinte (InkBlob.ts/InkPuddle.ts: Slow + DoT), Tentakel-Griff (5 rote Boden-Marken um den Oktopus → Pull via player.pullTime), Sweep (roter Tentakel kreist um den Oktopus, überspringbar), Enrage <30% HP. Spinnenkönigin: stationär, Netze (WebPatch.ts: fesselt via player.rootTime), Spinngeschwister (Mini-Spiders), Säuresalven. Frostriese: Eispanzer (70% Schadensreduktion, armorHp, bricht + Stagger, regeneriert nach Timer), Eisberst-Ring (nacheinander explodierende Marken), Eiszapfen-Regen, Stampf (überspringbar). Erz-Nekromant: schwebend, Teleport mit Glow-Ring-Telegraf + Ankunftssalve, Beschwörungen mit wachsendem Boden-Glow. Drache: fliegt und ist nur im Lande-Zustand verletzbar (takeDamage-Override blockt im Flug), Sturzangriff mit wachsendem Schatten-Telegraf, Feuerspeer (Telegraf-Bogen, überspringbar), Feuerbälle im Flug, Enrage mit Doppel-Sturz. Minotaurus: nur von hinten verletzbar (vorn „BLOCK"), Turn-Lag, Dash → Wand-Stun (Sterne) + Wandrisse, Axt-Schwung (überspringbar), Sprung+Stampf (Stun player.stunTime + FallingRock.ts), Enrage <40% (Doppel-Dash, Kamera-Shake)
Maze.ts / MazeGuard.ts / Gnome.ts Labyrinth-Zwischenboss (Welle 5/15/25/…, Game.MAZE_WAVES): randomisierter Prim-Generator (11×11, collidePlayer-Kollision, hasLineOfSight), Wächter-Geister aktivieren sich erst bei Spielernähe (MazeGuard.chase), Gnom als Ziel (Gnome.ts, isBoss, nur mit Sichtlinie verletzbar, ab Level 2 doppeltes Ziel). Spieler-Kollision nur während mazeActive; #maze-arrow-Overlay zeigt den Weg; Boss-Modus-Buttons „Labyrinth (Welle X)"
InkBlob.ts / InkPuddle.ts / FallingRock.ts Boss-Effekte: Tintenprojektil → Pfütze mit Slow (player.slowTimer) + DoT + game.triggerInkVignette(); fallender Fels mit Schatten-Telegraf
*Enemy*.ts (Spider, Ghost, Frog, Bat, BeeSwarm, Slime, Brute, Crab, Turret, Shaman, Archer, Bomber, Wisp, Turtle, SporeMushroom) Prozedurale Gegner-Modelle; je update(dt, game). Schamane heilt Gegner im Radius (Prioritätsziel), Bogenschütze hält Distanz + Pfeil mit Boden-Telegraf, Bomber explodiert bei Nähe (AoE), Irrlicht schießt Homing-Sporen (Slow), Schildkröte blockt vorn komplett + Panzer-Rückzug (unverwundbar), Sporen-Pilz wirft Bogen-Sporen mit DoT-Wolke
SoundManager.ts Vorab geladene .ogg-Buffer (playSound(name, volume)); Sounds: damage, spawn, fireball, explosion, teleport, gameover, sword_hit1, spiderwalking
MeshLoader.ts loadGLB(name)web/public/models/*.glb; Texturen in web/public/textures/

Wichtige Konventionen & Fallstricke

  • Blickrichtung = lokale +Z: Three.js Object3D.lookAt richtet +Z zum Ziel aus. Augen/Gesichter von Gegnern und Spieler müssen auf +Z liegen (sonst laufen sie rückwärts — war schon mal ein Bug bei Frosch/Golem/Brute/Slime/Bat/Crab).
  • UI-Overlays: Alle Panels liegen absolut in #ui-overlay (pointer-events: none). Versteckte Panels brauchen drei Dinge: opacity: 0 + pointerEvents: none + visibility: hidden. Achtung: .menu-button { pointer-events: auto } übersteuert pointer-events: none des Parents — ohne visibility: hidden sind unsichtbare Buttons klickbar (verursachte „stille Neustarts" mitten im Spiel). Neues Panel → immer visibility mitsetzen.
  • State-Machine: menu | playing | levelup | gameover + Flag paused (ESC). animate() rendert bei Pause nur UI. showMenu() hat einen Guard (nur aus menu/gameover/pause).
  • Hotbar: Aktive Skills (Kettenblitz, Teleport, Falle, Gaswolke) werden beim Erwerb auf Q, E, 1, 2, 3, 4 verteilt (HOTBAR_KEY_CODES). Dispatch über triggerHotbarSkill(). Cooldown-Labels zeigen Taste + Name (syncSkillVisibility).
  • Skill-Arten: Aktive = Weapon-Subklassen mit Cooldowns (UI-Balken in index.html, Update in updateUI). Passive = Stats in Player.applySkillSystem ODER Game-seitige Objekte (Aura/Boomerang), die in syncSkillVisibility erzeugt/zersstört und im Loop geupdated werden.
  • Effekt-Lifecycle: Neue Effekte mit eigenem update(dt, game)-Rückgabewert (false = aufräumen) und dispose(); in Game.resetWorld() ALLES aufräumen (sonst Geister-Objekte nach „Nochmal spielen").
  • Gegner-Festhalten: enemy.rootTime > 0 friert enemy.update() ein (siehe Game.updateEnemies) — so funktioniert die Falle, ohne jede Gegner-Klasse anzufassen.
  • Boss-Wellen & Boss-Modus: Game.bossWaves (Map: Welle → {name, spawn}) steuert Boss-Wellen (10, 20 …) — Boss-Welle startet erst, wenn die Arena geräumt ist (enemies.every(e => e.dead)); nach Boss-Tod läuft die nächste normale Welle an. Cheat-Code „idkfa" im Hauptmenü blendet den Boss-Modus ein (#boss-mode, Buttons werden aus bossWaves erzeugt) → startBossFight(wave) mit Zufalls-Skill-Boost (grantRandomSkills). spawnAtCenter-Flag lässt stationäre Bosse in der Arena-Mitte spawnen. Effekte, die ein Boss selbst verwaltet (Tinten-Blobs/-Pfützen, Ringe), müssen in onDeath/dispose aufgeräumt werden (Dead-Enemy-Removal ruft dispose NICHT auf).
  • Balance: XP-Kurve 30 + 15·Level (steiler, Skills maxen langsamer); Level-Ups geben genau 1 Skill-Punkt (Multi-Level nur bei XP-Überschuss, pendingPicks); Gegner-HP skaliert erst NACH den Bossen (jede 10. Welle = 1 Boss-Gate → +20% HP, nur Nicht-Bosse, in spawnEnemyInstance); neue Gegner erscheinen in enemyWeights nur nach Boss-Wellen (vor Golem nur Spinnen/Frösche, dann nach Golem/Krake/Minotaurus/Spinnenkönigin/Frostriese/Nekromant/Drache); Boss-Modus-Grants wave·0.4 Skills. Arena-Theme: Arena.applyTheme(stage) (8 Stufen, Boden/Wände/Nebel/Licht) wird in Game nach jedem Boss-Sieg (arenaStage++) getriggert, weiches Lerpen in arena.update(dt).
  • Gegner-Spawn im Sichtbereich: spawnEnemy/spawnBossForWave spawnen im Ring 1024 m um den Spieler; stationäre Gegner (immovable), die >8 s außerhalb des Bildschirms sind (NDC-Projektion isOnScreen), werden wieder in Sicht teleportiert (updateOffscreenReposition).
  • Labyrinth-Zwischenboss: Game.MAZE_WAVES (5, 15, 25, …) ersetzt die Welle komplett (startMazeWave/updateMaze/endMazeWave); Zeitlimit (75 s 5 s/Level, Min 50 s) → bei Ablauf wird das Labyrinth neu generiert und der Spieler zur Startecke gesetzt; Gnome nur mit Sichtlinie verletzbar (hasLineOfSight); mazeActive pausiert Spawn-Logik und Off-Screen-Reposition; Aufräumen über disposeMaze() in resetWorld; #maze-arrow-Overlay zeigt zum Ziel.
  • Spieler-Status-Effekte (in Player.ts + Game.updatePlayerMovement): slowTimer/slowFactor (Tinten-Slow, Lila-Tönung), pullTime/pullDir (Tentakel-Griff: Eingabe wird ignoriert, Geschwindigkeit = Pull-Richtung), stunTime (Stampf-Schockwelle: keine Eingaben, gelbe Sterne kreisen um den Kopf) und rootTime (Netz-Fessel: keine Bewegung, Angriffe bleiben möglich). Alles wird in Player.reset() zurückgesetzt.
  • Kamera-Shake: game.triggerCameraShake(duration, amp) → in animate() wird die Kameraposition kurz zufällig versetzt (kein Postprocessing nötig).
  • Kontaktschaden (Gegner berührt Spieler) läuft in Game.ts (Close-Encounter-Loop) mit contactDps * dt; Dornen-Skill ebendort als Gegenschlag.
  • playSound erzeugt pro Aufruf einen AudioBufferSourceNode — bei hochfrequenten Effekten (Aura-Ticks, Bumerang-Hits) ggf. throtteln.