# Ashen Realms – NPC System Specification V1 ## Zweck dieser Dokumentation Dieses Dokument definiert das NPC-System von **Ashen Realms** für den ersten erweiterten Vertical Slice und die darauf aufbauende Spielarchitektur. Es beschreibt: - das gemeinsame NPC-Grundmodell - die Trennung zwischen NPC-Definition und spielerspezifischem Zustand - NPC-Fähigkeiten statt Klassenvererbung - Händler, Questgeber und gemischte Rollen - Dialoge und Dialogbedingungen - Ruf- und Freischaltbedingungen - Materialtausch und Verkaufslogik - Anbindung des Taschensystems - wiederverwendbare Conditions - technische Datenmodelle und Services - den empfohlenen V1-Scope Das System folgt den bestehenden Grundsätzen von Ashen Realms: - Content ist datengetrieben. - Spiellogik ist serverautoritativ. - Content-Definitionen und Player-State bleiben getrennt. - NPCs sollen Teil der Welt sein und nicht nur UI-Schaltflächen mit Portrait. - Neue Spezialfälle sollen möglichst über wiederverwendbare Bausteine modelliert werden. --- # 1. Designziel NPCs sind zentrale Interaktionspunkte der Welt. Sie können unter anderem: - Quests anbieten - Quests abschließen - Gegenstände verkaufen - Materialien ankaufen oder eintauschen - Ruf prüfen - besondere Waren freischalten - Taschen verkaufen oder vergeben - Informationen geben - Story und Lore vermitteln - Reisen oder andere Dienste anbieten Ein NPC darf mehrere dieser Funktionen gleichzeitig besitzen. Beispiel: **Elyra, die verbannte Jägerin** kann gleichzeitig: - Story-NPC sein - Jagdquests anbieten - Materialien annehmen - Dämmerwald-Ruf berücksichtigen - Gebietsausrüstung verkaufen - besondere Angebote nach Quests freischalten Grundsatz: > **NPCs sind Personen mit mehreren Fähigkeiten – keine voneinander getrennten NPC-Klassen.** --- # 2. Keine klassische NPC-Vererbung Für das NPC-System wird ausdrücklich keine tiefe Klassenhierarchie verwendet. Nicht vorgesehen: ```ts class Npc {} class MerchantNpc extends Npc {} class QuestNpc extends Npc {} class QuestMerchantNpc extends MerchantNpc {} ``` Dieses Modell skaliert schlecht, sobald ein NPC mehrere Rollen kombiniert. Stattdessen verwendet Ashen Realms: > **Composition / Capability-based NPC design** Ein NPC besitzt eine gemeinsame Identität und kann über verknüpfte Daten verschiedene Fähigkeiten erhalten. Beispiel: ```text NPC ├── Dialogue ├── Quest Assignments ├── Shop ├── Resource Exchange ├── Reputation Requirements └── Player-specific State ``` --- # 3. NPC Definition Jeder NPC besitzt eine persistierte Content-Definition. Vorgeschlagenes Modell: ```ts NpcDefinition { id: string; key: string; name: string; title?: string; description?: string; locationId: string; factionKey?: string; portraitPath: string; artworkPath?: string; dialogueProfileId?: string; enabled: boolean; createdAt: Date; updatedAt: Date; } ``` ## Pflichtfelder | Feld | Bedeutung | |---|---| | `id` | interne UUID | | `key` | stabiler fachlicher Key | | `name` | sichtbarer NPC-Name | | `locationId` | aktueller Standort | | `portraitPath` | Portrait für UI und Dialog | | `enabled` | NPC aktiv/inaktiv | ## Optionale Felder | Feld | Bedeutung | |---|---| | `title` | z. B. „Händler der Grenzwacht“ | | `description` | kurze Beschreibung | | `factionKey` | Fraktionszugehörigkeit | | `artworkPath` | größeres Artwork für lokale Ansicht | | `dialogueProfileId` | Dialogdefinition | --- # 4. Stabiler NPC-Key Jeder NPC benötigt einen stabilen fachlichen Key. Beispiele: ```text captain-garrick merchant-borin elyra-exiled-huntress brother-caelan ``` Dieser Key darf in: - Seeds - Quests - Dialogbedingungen - Shopbedingungen - Flags - Tests verwendet werden. Interne UUIDs sollen nicht zur fachlichen Referenzierung von Content benutzt werden. --- # 5. NPC Capabilities NPC-Fähigkeiten werden nicht über Vererbung bestimmt. Für V1 und spätere Erweiterungen sind folgende Capabilities vorgesehen: | Capability | Funktion | |---|---| | `DIALOGUE` | NPC kann angesprochen werden | | `QUEST_GIVER` | bietet Quests an | | `QUEST_TURN_IN` | nimmt Quests entgegen | | `MERCHANT` | besitzt normalen Shop | | `REPUTATION_MERCHANT` | Angebote hängen von Ruf ab | | `RESOURCE_EXCHANGE` | tauscht Materialien gegen Belohnungen | | `BAG_MERCHANT` | verkauft oder vergibt Taschen | | `LORE` | bietet Story-/Weltinformationen | | `TRAINER` | spätere Skills/Fähigkeiten | | `TRAVEL` | spätere Reiseleistung | | `SERVICE` | Heilung, Bank, Reparatur etc. | | `EVENT` | zeit- oder weltzustandsabhängige Funktion | Capabilities sind primär beschreibend. Die tatsächliche Funktion entsteht durch verknüpfte Daten wie: - `NpcShop` - `NpcQuestAssignment` - `NpcDialogueProfile` - `NpcExchangeProfile` --- # 6. NPC und Standort Ein NPC befindet sich grundsätzlich an einem Ort. V1: ```text NpcDefinition.locationId -> LocationDefinition.id ``` Später möglich: - NPC wechselt abhängig von Weltzustand den Ort - NPC erscheint nur während einer Quest - NPC wird nach Storyfortschritt ersetzt - NPC verschwindet temporär Für V1 wird der Standort statisch gespeichert. --- # 7. Trennung von Content und Player-State Ein NPC ist globaler Content. Der individuelle Zustand eines Spielers gegenüber diesem NPC ist separater Player-State. Nicht in `NpcDefinition` speichern: - ob ein Spieler den NPC bereits getroffen hat - persönliche Beziehung - individuelle Dialogflags - persönliche Freischaltungen Dafür existiert optional: ```ts CharacterNpcState { id: string; characterId: string; npcId: string; relationValue?: number; firstMetAt?: Date; lastInteractionAt?: Date; flags: Record; } ``` --- # 8. Ruf ist nicht automatisch NPC-Beziehung Ashen Realms unterscheidet ausdrücklich zwischen: ## Welt-/Gebiet-/Fraktionsruf Beispiele: ```text World Renown Graufurt Reputation Ashen Fields Reputation Duskwood Reputation Faction Reputation ``` und ## Persönlicher NPC-Beziehung Beispiele: ```text Borin kennt den Spieler Elyra vertraut dem Spieler Caelan reagiert auf eine Storyentscheidung ``` Ruf ist ein übergeordnetes Progressionssystem. NPC-Beziehung ist optionaler individueller Zustand. Ein Händler kann daher verlangen: ```text Graufurt Reputation >= 3 ``` ohne dass der Spieler automatisch „3 Ruf bei Borin“ besitzt. --- # 9. Character Reputation Ruf wird unabhängig von NPCs gespeichert. Beispielmodell: ```ts CharacterReputation { id: string; characterId: string; type: ReputationType; targetKey: string; value: number; } ``` Mögliche Typen: ```ts enum ReputationType { WORLD = 'WORLD', REGION = 'REGION', FACTION = 'FACTION', } ``` Beispiele: ```text WORLD / global / 4 REGION / graufurt / 3 REGION / duskwood / 2 FACTION / border-watch / 1 ``` --- # 10. NPC Dialogue System NPCs sollen nicht nur statische Texte besitzen. Dialoge reagieren auf: - Quests - Ruf - Items - Flags - besiegte Gegner oder Bosse - Storyfortschritt - erste Begegnung Vorgeschlagenes Modell: ```ts NpcDialogueProfile { id: string; key: string; npcId: string; } ``` ```ts DialogueNode { id: string; profileId: string; key: string; text: string; priority: number; conditions: GameCondition[]; actions: DialogueAction[]; responses: DialogueResponse[]; } ``` --- # 11. Dialogpriorität Mehrere Dialoge können gleichzeitig gültig sein. Daher besitzt jeder Node eine Priorität. Beispiel: ```text 1000 – Questabschluss verfügbar 900 – Quest aktiv 800 – neue wichtige Quest 500 – Ruf-basierter Dialog 100 – Standarddialog ``` Der Server ermittelt den höchstpriorisierten gültigen Dialog. Dadurch kann ein NPC situationsabhängig reagieren, ohne große If-/Else-Blöcke im Code. --- # 12. Dialogue Responses Ein Dialog kann mehrere auswählbare Antworten besitzen. ```ts DialogueResponse { id: string; nodeId: string; text: string; targetNodeKey?: string; conditions?: GameCondition[]; actions?: DialogueAction[]; } ``` Beispiele: ```text „Zeig mir deine Waren.“ „Was weißt du über die Aschengrube?“ „Ich habe die Felle gebracht.“ „Leb wohl.“ ``` --- # 13. Dialogue Actions Dialoge dürfen Aktionen auslösen. V1-relevante Actions: ```text START_QUEST COMPLETE_QUEST OPEN_SHOP OPEN_EXCHANGE GRANT_ITEM SET_FLAG ``` Später möglich: ```text START_TRAVEL OPEN_TRAINER OPEN_SERVICE CHANGE_RELATION DISCOVER_LOCATION START_EVENT ``` Beispiel: ```json { "type": "OPEN_SHOP", "shopKey": "borin-general-store" } ``` --- # 14. Quest-Zuordnung zu NPCs Questgeber und Questabgabe werden getrennt modelliert. Nicht vorgesehen: ```ts npc.isQuestGiver = true; ``` Stattdessen: ```ts NpcQuestAssignment { id: string; npcId: string; questId: string; role: NpcQuestRole; } ``` ```ts enum NpcQuestRole { OFFER = 'OFFER', TURN_IN = 'TURN_IN', PROGRESS = 'PROGRESS', } ``` Dadurch kann eine Quest mehrere NPCs verwenden. Beispiel: ```text Garrick -> OFFER Borin -> PROGRESS Garrick -> TURN_IN ``` --- # 15. Händler-System Ein Shop ist kein Spezialtyp von NPC. Ein NPC kann null, einen oder später mehrere Shops besitzen. ```ts NpcShop { id: string; key: string; npcId: string; name: string; enabled: boolean; } ``` ```ts ShopOffer { id: string; shopId: string; itemDefinitionId: string; currencyType: string; price: number; quantity?: number; repeatable: boolean; conditions: GameCondition[]; } ``` --- # 16. Rufbasierte Shop-Angebote Nicht mehrere Shops pro Rufstufe anlegen. Stattdessen erhält jedes Angebot eigene Conditions. Beispiel: ```text Großer Fellbeutel Preis: 140 Silber Bedingung: Graufurt Ruf >= 3 ``` Oder: ```text Dämmerjäger-Kapuze Preis: 28 Dämmermarken Bedingungen: - Dämmerwald Ruf >= 4 - Quest „Graufangs Spur“ abgeschlossen ``` Dadurch bleiben Shops vollständig datengetrieben. --- # 17. Resource Exchange Mit dem neuen Rufsystem können bestimmte NPCs Materialien annehmen. Dafür wird kein normaler Item-Shop missbraucht. Vorgesehen: ```ts NpcExchangeProfile { id: string; key: string; npcId: string; } ``` ```ts ExchangeRule { id: string; profileId: string; inputItemId: string; inputQuantity: number; silverReward?: number; worldRenownReward?: number; regionReputationReward?: number; factionReputationReward?: number; conditions: GameCondition[]; } ``` Beispiel: ```text 5 × Aschenfell → 18 Silber → 10 Graufurt-Ruf → 2 Weltruhm ``` Die genauen Werte werden separat gebalanced. --- # 18. Verbindung zum Taschensystem Das NPC-System muss das Taschensystem unterstützen. NPCs können: - Taschen verkaufen - Taschen einmalig vergeben - Taschen über Quests freischalten - größere Taschen erst ab Rufstufen anbieten Beispiel: ```text Borin ├── Einfacher Fellbeutel │ └── über Intro-Quest verfügbar │ ├── Verstärkter Fellbeutel │ └── Graufurt Ruf >= 2 │ └── Großer Jägerbeutel └── Graufurt Ruf >= 4 ``` Taschen selbst bleiben Teil des Item-/Bag-Systems. Der NPC kontrolliert nur: - Angebot - Freischaltung - Preis - Questbezug --- # 19. Game Conditions NPC-Systeme verwenden eine gemeinsame Condition Engine. V1 Condition Types: ```ts enum GameConditionType { QUEST_ACTIVE = 'QUEST_ACTIVE', QUEST_COMPLETED = 'QUEST_COMPLETED', HAS_ITEM = 'HAS_ITEM', REGION_REPUTATION = 'REGION_REPUTATION', WORLD_RENOWN = 'WORLD_RENOWN', FLAG_SET = 'FLAG_SET', BOSS_DEFEATED = 'BOSS_DEFEATED', LOCATION_DISCOVERED = 'LOCATION_DISCOVERED', } ``` Später möglich: ```text FACTION_REPUTATION NPC_RELATION HAS_EQUIPMENT BAG_OWNED MONSTER_KILL_COUNT ACHIEVEMENT_COMPLETED EVENT_ACTIVE TIME_WINDOW ``` --- # 20. Condition Struktur Conditions sollten möglichst generisch gespeichert werden. Beispiel: ```ts GameCondition { type: GameConditionType; key?: string; operator?: ComparisonOperator; value?: string | number | boolean; } ``` Beispiel: ```json { "type": "REGION_REPUTATION", "key": "graufurt", "operator": "GTE", "value": 3 } ``` --- # 21. Condition Evaluation Eine zentrale serverseitige Komponente wertet Conditions aus. ```ts GameConditionService { evaluate( characterId: string, conditions: GameCondition[], ): Promise; } ``` Sie kann wiederverwendet werden für: - Dialoge - Shop-Angebote - Quests - Materialtausch - Reiseverbindungen - Ortsinteraktionen - Bosszugänge - spätere Events Grundsatz: > **Keine separaten Condition-Systeme für jedes Feature bauen.** --- # 22. NPC Interaction API Der Client soll NPC-Logik nicht selbst auswerten. Mögliche API-Endpunkte: ```text GET /api/locations/:locationId/npcs GET /api/npcs/:npcId GET /api/npcs/:npcId/interaction POST /api/npcs/:npcId/dialogue/:nodeId/respond ``` Zusätzlich über separate Fachmodule: ```text GET /api/npcs/:npcId/shop POST /api/npcs/:npcId/shop/purchase GET /api/npcs/:npcId/exchange POST /api/npcs/:npcId/exchange ``` Questaktionen können über das Questmodul laufen. --- # 23. NPC Interaction Response Ein NPC-Interaction-Response kann enthalten: ```ts interface NpcInteractionDto { npc: { id: string; key: string; name: string; title?: string; portraitPath: string; }; dialogue?: DialogueNodeDto; availableActions: NpcActionDto[]; } ``` Beispiel Actions: ```text TALK OPEN_SHOP OPEN_EXCHANGE VIEW_QUESTS ``` Der Server entscheidet, welche Actions aktuell verfügbar sind. --- # 24. NPCs in der lokalen Ortsansicht NPCs werden in der lokalen Ortsansicht sichtbar dargestellt. Ein NPC-Eintrag zeigt mindestens: - Portrait oder Artwork - Name - Titel/Rolle - Interaktionsstatus Mögliche Marker: ```text ! neue Quest ? Quest abgabebereit ¤ Händler ↔ Materialtausch ● neue Dialoginformation ``` Die UI soll nicht jeden Capability-Typ als eigenes dauerhaftes Symbol erzwingen. Nur relevante Aktionen werden gezeigt. --- # 25. NPC-Verfügbarkeit V1 sind NPCs grundsätzlich dauerhaft verfügbar, solange: ```text enabled = true ``` Später können Conditions für NPC-Sichtbarkeit ergänzt werden. Beispiele: - NPC erscheint erst nach Quest - NPC stirbt oder verschwindet - anderer NPC ersetzt ihn - NPC wechselt Ort - Event-NPC erscheint temporär Dafür kann später ergänzt werden: ```ts NpcDefinition.visibilityConditions ``` Für V1 nicht erforderlich. --- # 26. Beispiel – Borin ```text Borin Händler der Grenzwacht Ort: Graufurt ``` Capabilities: ```text DIALOGUE MERCHANT RESOURCE_EXCHANGE BAG_MERCHANT ``` Funktionen: ```text Shop ├── Heiltrank ├── einfache Ausrüstung ├── Fellbeutel └── größere Taschen über Ruf Exchange ├── Aschenfell ├── zähes Fell └── weitere Tiermaterialien ``` Dialogzustände: ```text Erste Begegnung → reservierter Standarddialog Quest-Einführung aktiv → „Garrick schickt dich also.“ Graufurt-Ruf >= 2 → freundlicherer Dialog Graufurt-Ruf >= 4 → Spezialangebot verfügbar ``` --- # 27. Beispiel – Elyra ```text Elyra, die verbannte Jägerin Ort: Verfallener Jägerschrein ``` Capabilities: ```text DIALOGUE QUEST_GIVER QUEST_TURN_IN RESOURCE_EXCHANGE REPUTATION_MERCHANT LORE ``` Mögliche Funktionen: ```text Quests ├── Jagd auf Dämmerwölfe ├── Schwarzmähnen-Spur └── Graufang Exchange ├── Dämmerfell ├── Giftdrüse └── Elite-Trophäen Shop ├── Heiltrank ├── Gegengift ├── Bestienbeutel └── Dämmerjäger-Items ``` --- # 28. Beispiel – Bruder Caelan ```text Bruder Caelan Letzter Hüter Ort: Kapelle der letzten Wacht ``` Capabilities: ```text DIALOGUE QUEST_GIVER QUEST_TURN_IN REPUTATION_MERCHANT LORE ``` Mögliche Besonderheiten: - reagiert auf entdeckte Ruinen - erklärt Untote und Siegelbruchstücke - bietet spezielle Waren nach Sir Varos - besitzt Storydialoge zum Knochenfürsten --- # 29. Technische Modulstruktur Empfohlene Backend-Struktur: ```text apps/api/src/ ├── npcs/ │ ├── entities/ │ │ ├── npc-definition.entity.ts │ │ ├── npc-dialogue-profile.entity.ts │ │ ├── dialogue-node.entity.ts │ │ └── character-npc-state.entity.ts │ │ │ ├── npc.service.ts │ ├── npc.controller.ts │ └── npc.module.ts │ ├── shops/ │ ├── entities/ │ │ ├── npc-shop.entity.ts │ │ └── shop-offer.entity.ts │ └── ... │ ├── reputation/ │ ├── entities/ │ │ └── character-reputation.entity.ts │ └── ... │ ├── exchanges/ │ ├── entities/ │ │ ├── npc-exchange-profile.entity.ts │ │ └── exchange-rule.entity.ts │ └── ... │ ├── quests/ │ └── npc-quest-assignment.entity.ts │ └── conditions/ ├── game-condition.types.ts ├── game-condition.service.ts └── game-condition.service.spec.ts ``` --- # 30. Verantwortlichkeiten der Services ## `NpcService` Verantwortlich für: - NPCs eines Ortes laden - NPC-Definition laden - aktuell verfügbare Interaktionen bestimmen - relevanten Dialog ermitteln Nicht verantwortlich für: - Kauftransaktionen - Rufberechnung - Questfortschritt - Itemtransfer --- ## `GameConditionService` Verantwortlich für: - Conditions serverseitig auswerten - wiederverwendbare Freischaltlogik --- ## `ShopService` Verantwortlich für: - verfügbare Angebote bestimmen - Conditions prüfen - Preise validieren - Kauf atomar durchführen --- ## `ExchangeService` Verantwortlich für: - Materialbesitz prüfen - Taschen-/Inventarbezug beachten - Items entfernen - Silber/Ruf gewähren - Transaktion atomar durchführen --- # 31. Transaktionsregeln Folgende Aktionen müssen serverseitig und atomar ausgeführt werden: ```text Shop Purchase Material Exchange Quest Reward Item Grant Reputation Grant ``` Beispiel Materialtausch: ```text Material prüfen → Material entfernen → Silber gewähren → Regionalruf gewähren → Weltruhm gewähren → Commit ``` Ein Teilzustand darf nicht persistiert werden. --- # 32. Seeds NPC-Content wird über reproduzierbare Seeds angelegt. Empfohlene Dateien: ```text apps/api/src/database/seeds/ ├── npcs.seed.ts ├── npc-dialogues.seed.ts ├── npc-shops.seed.ts ├── npc-exchanges.seed.ts └── npc-quest-assignments.seed.ts ``` Seeds müssen idempotent sein. Fachliche Verknüpfungen verwenden stabile Keys. --- # 33. Tests Mindestens folgende Fälle sollten getestet werden: ## NPC - NPCs eines Ortes werden korrekt geladen - deaktivierte NPCs erscheinen nicht - NPC besitzt mehrere Capabilities gleichzeitig ## Dialog - Standarddialog wird angezeigt - höherpriorisierter Questdialog überschreibt Standarddialog - Rufdialog erscheint erst bei erfüllter Condition ## Shop - Rufanforderung wird serverseitig geprüft - Questanforderung wird geprüft - gesperrtes Item kann nicht direkt über API gekauft werden ## Exchange - benötigte Materialien werden geprüft - Materialien werden entfernt - Silber wird vergeben - Ruf wird vergeben - Transaktion bleibt atomar ## Quest - ein NPC kann Questgeber sein - anderer NPC kann Questfortschritt auslösen - dritter oder erster NPC kann Questabschluss übernehmen --- # 34. V1 Scope Für die erste Implementierung wird bewusst nur ein kleiner Teil benötigt. ## Implementieren ```text NpcDefinition NpcService NpcController NpcQuestAssignment NpcShop ShopOffer NpcExchangeProfile ExchangeRule GameConditionService Conditions: - QUEST_ACTIVE - QUEST_COMPLETED - REGION_REPUTATION - WORLD_RENOWN - FLAG_SET ``` Dialogsystem zunächst einfach: ```text priorisierte DialogNodes + Conditions + OPEN_SHOP + OPEN_EXCHANGE + START_QUEST + COMPLETE_QUEST ``` --- # 35. Noch nicht für V1 implementieren Noch nicht notwendig: - komplexe persönliche NPC-Beziehungen - Romance - NPC-Tagesabläufe - freie NPC-Bewegung - KI-gesteuerte NPCs - prozedurale Dialoge - Sprachausgabe - NPC-Inventare als physische Simulation - dynamische Preise - Verhandlungssystem - Diebstahl - Begleiter - NPC-Tod mit komplexen Weltfolgen - Echtzeit-Weltzustände `CharacterNpcState` kann vorbereitet, aber erst später vollständig genutzt werden. --- # 36. UI-Prinzip Ein NPC-Screen oder NPC-Panel soll weiterhin den visuellen Grundsätzen von Ashen Realms folgen. NPCs werden über: - großes Portrait oder Artwork - Namen - Titel - Dialogtext - klar sichtbare aktuelle Aktionen präsentiert. Nicht geeignet: - reine Tabellenansicht - generische Admin-Card - zehn gleichgewichtete Buttons Der NPC soll wie ein Teil der Welt wirken. --- # 37. Architekturregeln 1. **NPC-Funktionen werden über Composition modelliert, nicht über Klassenvererbung.** 2. **NPC-Definition und Player-State bleiben getrennt.** 3. **Ruf ist nicht automatisch persönliche NPC-Beziehung.** 4. **Ein NPC darf mehrere Rollen gleichzeitig besitzen.** 5. **Questgeber und Questabgabe sind getrennte Zuordnungen.** 6. **Shopangebote besitzen eigene Conditions.** 7. **Materialtausch ist ein eigenes System und kein normaler Shopverkauf.** 8. **Conditions werden zentral wiederverwendbar ausgewertet.** 9. **Der Client entscheidet nicht über Freischaltungen.** 10. **Alle relevanten Transaktionen sind serverautoritativ und atomar.** 11. **Content verwendet stabile fachliche Keys.** 12. **NPC-Spezialverhalten soll möglichst datengetrieben entstehen.** --- # 38. Zielbild Das NPC-System soll langfristig folgende Interaktionen ermöglichen, ohne für jeden NPC neuen Spezialcode zu schreiben: ```text Spieler spricht mit NPC ↓ Server prüft Quest-, Ruf- und Weltzustand ↓ passender Dialog wird gewählt ↓ verfügbare Aktionen werden angezeigt ↓ Spieler kann z. B. ├── Quest annehmen ├── Quest abgeben ├── Materialien eintauschen ├── Shop öffnen ├── Tasche kaufen └── Lore lesen ``` Der gleiche technische Unterbau kann für Borin, Elyra, Bruder Caelan und zukünftige NPCs wiederverwendet werden. --- # 39. Kurzfassung Ashen Realms verwendet ein gemeinsames NPC-Grundmodell. NPC-Typen wie Händler oder Questgeber werden **nicht** über Vererbung umgesetzt. Stattdessen besitzt ein NPC kombinierbare Funktionen: ```text NPC + Dialogue + Quests + Shop + Resource Exchange + Reputation Conditions + Bag Offers + Player State ``` Ruf bleibt ein separates Charakter-Progressionssystem. Persönliche NPC-Beziehung kann später zusätzlich ergänzt werden. Eine zentrale Condition Engine verbindet: - NPCs - Dialoge - Quests - Shops - Ruf - Materialtausch - Taschen - Weltfortschritt Der zentrale Grundsatz lautet: > **NPCs sind datengetriebene Charaktere mit kombinierbaren Interaktionen – keine voneinander getrennten Spezialklassen.** --- # 40. Implementierungsstand (Slice 0.8) Der V1-Scope aus §34 ist umgesetzt, mit drei bewussten Abweichungen. Sie sind hier festgehalten statt still aufgelöst zu werden. ## Umgesetzt ```text NpcDefinition apps/api/src/npcs/entities/ DialogueNode priorisiert, mit Conditions (§11) CharacterNpcState vorbereitet, genutzt für first-met + Flags (§7, §35) NpcShop / ShopOffer apps/api/src/shops/ NpcExchangeProfile / ExchangeRule apps/api/src/exchanges/ GameConditionService apps/api/src/conditions/ ``` Erster NPC: **Borin, Quartermaster** an `south-gate` (Graufurt) mit DIALOGUE + MERCHANT + RESOURCE_EXCHANGE gleichzeitig — der Kompositionsfall aus §2/§26 in echt. ## Abweichung 1 — kein `NpcQuestAssignment` §34 listet es im V1-Scope, aber es gibt noch kein Questsystem (Slice 0.9). Eine Tabelle mit Fremdschlüssel auf eine nicht existierende `quests`-Tabelle ist nicht baubar, und ein `questKey`-String ohne Validierung wäre spekulative Architektur (AGENTS §1.7). Nachzuholen mit Slice 0.9, zusammen mit den Dialog-Actions `START_QUEST` / `COMPLETE_QUEST`. ## Abweichung 2 — kein `NpcDialogueProfile` §10 skizziert `NpcDialogueProfile` als Zwischenebene zwischen NPC und `DialogueNode`. §34 verlangt dagegen nur "priorisierte DialogNodes + Conditions", und das Profil hätte in V1 keine eigenen Felder. Nodes hängen deshalb direkt am NPC. Ein Profil lässt sich später einziehen, ohne die Nodes neu zu schreiben. ## Abweichung 3 — Conditions, die (noch) nichts beantworten kann `GameConditionType` enthält die vollständige V1-Liste aus §19, aber nur `REGION_REPUTATION`, `WORLD_RENOWN`, `FLAG_SET` und `HAS_ITEM` sind auswertbar. `QUEST_ACTIVE`, `QUEST_COMPLETED`, `BOSS_DEFEATED` und `LOCATION_DISCOVERED` haben noch kein System dahinter. Sie werten **fail-closed** aus, also immer "nicht erfüllt". Für ein Gate ist die sichere Richtung eines Fehlers zu, nicht offen — ein Quest-Gate darf niemals aufgehen, nur weil es keine Quests gibt. ## Offene Designfrage — World Renown im Tausch Slice 0.8 §4/§9 skizziert `worldRenownPerUnit`. Das ist mit dem implementierten Renown-System nicht verträglich: Renown ist ein Rang 1–15, der bei jeder Änderung baseHp/baseAttack aus einer festen Kurve neu setzt (Slice 0.6.5 §4). Renown pro Fell würde einen Spieler in wenigen Trips ans Statmaximum bringen. `ExchangeRule.renownMilestoneKey` verweist deshalb auf einen `RenownMilestoneDefinition` — die "batch rule"-Variante aus 0.8 §4, und deckungsgleich mit 0.6.5 §6, das "first meaningful trophy returned" als Renown-2-Meilenstein nennt. Nicht wiederholbar: der erste Tausch löst ihn aus, jeder weitere zahlt weiter Silber und Reputation, ohne den Rang anzufassen. Falls Renown später doch als Punktwährung gedacht ist, muss das zuerst in 0.6.5 geändert werden — nicht hier.