# 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 ```bash 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. ## 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()` | | `*Enemy*.ts` (Spider, Ghost, Frog, Bat, BeeSwarm, Slime, Brute/Boss, Crab, Turret) | Prozedurale Gegner-Modelle; je `update(dt, game)` | | `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. - **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.