11 KiB
11 KiB
AGENTS.md
Git-Regeln
- Git-Commits nur auf explizite Anforderung des Users. Niemals eigenständig committen.
- Vor einem Commit:
git status,git diffundgit log --oneline -10prü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 wiedragon,krake,spider, … siehe Registry inviewer.ts),&still=1= statische Pose (nurupdateAnimation, keinupdate). - 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, machtshotsTurntable-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.lookAtrichtet +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 }übersteuertpointer-events: nonedes Parents — ohnevisibility: hiddensind unsichtbare Buttons klickbar (verursachte „stille Neustarts" mitten im Spiel). Neues Panel → immervisibilitymitsetzen. - State-Machine:
menu | playing | levelup | gameover+ Flagpaused(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 übertriggerHotbarSkill(). Cooldown-Labels zeigen Taste + Name (syncSkillVisibility). - Skill-Arten: Aktive =
Weapon-Subklassen mit Cooldowns (UI-Balken inindex.html, Update inupdateUI). Passive = Stats inPlayer.applySkillSystemODER Game-seitige Objekte (Aura/Boomerang), die insyncSkillVisibilityerzeugt/zersstört und im Loop geupdated werden. - Effekt-Lifecycle: Neue Effekte mit eigenem
update(dt, game)-Rückgabewert (false = aufräumen) unddispose(); inGame.resetWorld()ALLES aufräumen (sonst Geister-Objekte nach „Nochmal spielen"). - Gegner-Festhalten:
enemy.rootTime > 0friertenemy.update()ein (sieheGame.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 ausbossWaveserzeugt) →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 inonDeath/disposeaufgeräumt werden (Dead-Enemy-Removal ruftdisposeNICHT 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, inspawnEnemyInstance); neue Gegner erscheinen inenemyWeightsnur nach Boss-Wellen (vor Golem nur Spinnen/Frösche, dann nach Golem/Krake/Minotaurus/Spinnenkönigin/Frostriese/Nekromant/Drache); Boss-Modus-Grantswave·0.4Skills. Arena-Theme:Arena.applyTheme(stage)(8 Stufen, Boden/Wände/Nebel/Licht) wird inGamenach jedem Boss-Sieg (arenaStage++) getriggert, weiches Lerpen inarena.update(dt). - Gegner-Spawn im Sichtbereich:
spawnEnemy/spawnBossForWavespawnen im Ring 10–24 m um den Spieler; stationäre Gegner (immovable), die >8 s außerhalb des Bildschirms sind (NDC-ProjektionisOnScreen), 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);mazeActivepausiert Spawn-Logik und Off-Screen-Reposition; Aufräumen überdisposeMaze()inresetWorld;#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) undrootTime(Netz-Fessel: keine Bewegung, Angriffe bleiben möglich). Alles wird inPlayer.reset()zurückgesetzt. - Kamera-Shake:
game.triggerCameraShake(duration, amp)→ inanimate()wird die Kameraposition kurz zufällig versetzt (kein Postprocessing nötig). - Kontaktschaden (Gegner berührt Spieler) läuft in
Game.ts(Close-Encounter-Loop) mitcontactDps * dt; Dornen-Skill ebendort als Gegenschlag. playSounderzeugt pro Aufruf einen AudioBufferSourceNode — bei hochfrequenten Effekten (Aura-Ticks, Bumerang-Hits) ggf. throtteln.