Walking skeleton through Swing GUI: YAML-driven world (4 rooms, 4 items, 1 NPC), HashMap command dispatch with parser, three-tier item hierarchy (readable / switchable / plain), and end-to-end NPC give/receive flow. 67 tests green.
120 lines
3.0 KiB
Markdown
120 lines
3.0 KiB
Markdown
# NPCs
|
|
|
|
Nicht-Spieler-Figuren — optionaler Bonusteil laut Aufgabenstellung. Hier mit Talk- und Give-Interaktion modelliert.
|
|
|
|
## Domain-Modell
|
|
|
|
```mermaid
|
|
classDiagram
|
|
class Npc {
|
|
-String id
|
|
-String name
|
|
-String description
|
|
-String greeting
|
|
-Map~String, NpcReaction~ reactions
|
|
+talk(GameContext) void
|
|
+receive(Item, GameContext) boolean
|
|
}
|
|
|
|
class NpcReaction {
|
|
-Item consumes
|
|
-Item gives
|
|
-String response
|
|
+apply(Player) void
|
|
}
|
|
|
|
Npc "1" --> "*" NpcReaction : reactions
|
|
```
|
|
|
|
- `reactions` ist `HashMap<String, NpcReaction>` mit dem Trigger-Item-id als Key. O(1) Nachschlagen beim `gib X an Y`.
|
|
|
|
## Interaktionen
|
|
|
|
### `talk <npc>`
|
|
|
|
- Sucht NPC im aktuellen Raum.
|
|
- Wirft `greeting`-Text aus.
|
|
- Mutiert nichts.
|
|
|
|
### `gib <item> an <npc>`
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
actor Spieler
|
|
participant Cmd as GiveCommand
|
|
participant P as Player
|
|
participant N as Npc
|
|
participant IO
|
|
|
|
Spieler->>Cmd: give lamp old_man
|
|
Cmd->>P: hasItem("lamp")?
|
|
P-->>Cmd: yes
|
|
Cmd->>N: receive(lamp)
|
|
N->>N: reactions.get("lamp")
|
|
alt Reaktion existiert
|
|
N->>P: inventory.remove(lamp)
|
|
N->>P: inventory.add(key)
|
|
N-->>Cmd: response text
|
|
Cmd->>IO: write(response)
|
|
else keine Reaktion
|
|
N-->>Cmd: false
|
|
Cmd->>IO: write("The NPC does not react.")
|
|
end
|
|
```
|
|
|
|
## YAML-Beispiel
|
|
|
|
```yaml
|
|
- id: old_man
|
|
name: Old Man
|
|
description: A stooped old man with a grey beard.
|
|
greeting: |
|
|
"Hello traveller. If you bring me the lamp,
|
|
I will show you the way to the cellar."
|
|
reactions:
|
|
- onReceive: lamp
|
|
response: |
|
|
"Thank you! Here, take this key."
|
|
gives: key
|
|
consumes: lamp
|
|
```
|
|
|
|
Felder einer Reaktion:
|
|
|
|
| Feld | Pflicht | Bedeutung |
|
|
|---|---|---|
|
|
| `onReceive` | ja | Item-id, das ausgelöst werden muss |
|
|
| `response` | ja | Antworttext nach erfolgreicher Übergabe |
|
|
| `gives` | nein | Item-id, das der NPC zurückgibt |
|
|
| `consumes` | nein | Item-id, das aus Spielerinventar entfernt wird (oft = `onReceive`) |
|
|
|
|
## Speichern in Räumen
|
|
|
|
Räume halten ihre NPCs analog zu Items:
|
|
|
|
```java
|
|
private final LinkedHashMap<String, Npc> npcs = new LinkedHashMap<>();
|
|
```
|
|
|
|
- `LinkedHashMap` weil O(1)-Lookup beim `talk <npc>` *und* stabile Reihenfolge in der Raumbeschreibung („Here is: Old Man, Innkeeper").
|
|
|
|
## Erweiterungen (bewusst nicht im MVP)
|
|
|
|
- Bedingte Dialoge („wenn Quest X erledigt, sag Y")
|
|
- Kauf/Verkauf mit Gold
|
|
- NPC bewegt sich zwischen Räumen
|
|
- Mehrfache Reaktionsketten (NPC-Memory)
|
|
|
|
Diese würden ein eigenes Quest-/Event-System rechtfertigen. Für 3 Bonuspunkte überdimensioniert.
|
|
|
|
## Validierungsregeln
|
|
|
|
Wiederholung aus [yaml-schemas.md](yaml-schemas.md), hier explizit pro NPC:
|
|
|
|
1. `id` eindeutig in `npcs.yaml`
|
|
2. `greeting` nicht leer (sonst sinnloses NPC)
|
|
3. Jede `onReceive`-id existiert in `items.yaml`
|
|
4. Jede `gives`-id existiert in `items.yaml`
|
|
5. Jede `consumes`-id existiert in `items.yaml`
|
|
6. Innerhalb eines NPCs ist `onReceive` eindeutig (keine zwei Reaktionen auf dasselbe Item)
|