1334 lines
22 KiB
Markdown
1334 lines
22 KiB
Markdown
# 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<string, boolean | string | number>;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
# 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<boolean>;
|
||
}
|
||
```
|
||
|
||
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.**
|