From 268c24d4e30de03d5567c44e1027a9ca01938e36 Mon Sep 17 00:00:00 2001 From: Jean-Luc Makiola Date: Sun, 31 May 2026 21:55:15 +0200 Subject: [PATCH] feat: conditional NPC dialogue Co-Authored-By: Claude Opus 4.8 (1M context) --- .../adventure/command/impl/TalkCommand.java | 12 +++++- .../adventure/loader/ReferenceResolver.java | 7 +++- .../jeanluc/adventure/loader/dto/NpcDto.java | 8 +++- .../java/thb/jeanluc/adventure/model/Npc.java | 15 ++++++++ .../adventure/command/impl/DialogueTest.java | 37 +++++++++++++++++++ 5 files changed, 76 insertions(+), 3 deletions(-) create mode 100644 Semesterprojekt/src/test/java/thb/jeanluc/adventure/command/impl/DialogueTest.java diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/command/impl/TalkCommand.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/command/impl/TalkCommand.java index 30409b6..5c249b5 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/command/impl/TalkCommand.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/command/impl/TalkCommand.java @@ -1,7 +1,9 @@ package thb.jeanluc.adventure.command.impl; import thb.jeanluc.adventure.command.Command; +import thb.jeanluc.adventure.game.Conditions; import thb.jeanluc.adventure.game.GameContext; +import thb.jeanluc.adventure.model.DialogueLine; import thb.jeanluc.adventure.model.Npc; import java.util.List; @@ -24,7 +26,15 @@ public class TalkCommand implements Command { ctx.getIo().write("There is no '" + npcId + "' here to talk to."); return; } - ctx.getIo().write(npc.get().getName() + " says: " + npc.get().getGreeting()); + Npc found = npc.get(); + String line = found.getGreeting(); + for (DialogueLine dl : found.getDialogue()) { + if (Conditions.all(dl.when(), ctx)) { + line = dl.text(); + break; + } + } + ctx.getIo().write(found.getName() + " says: " + line); } @Override diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/ReferenceResolver.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/ReferenceResolver.java index fc10a46..17eda2a 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/ReferenceResolver.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/ReferenceResolver.java @@ -113,10 +113,15 @@ public class ReferenceResolver { */ public void resolveNpcs(List npcDtos) { for (NpcDto dto : npcDtos) { + Npc npc = npcs.get(dto.id()); + if (dto.dialogue() != null) { + for (DialogueLineDto dl : dto.dialogue()) { + npc.addDialogue(new DialogueLine(ConditionDto.toModelList(dl.when()), dl.text())); + } + } if (dto.reactions() == null) { continue; } - Npc npc = npcs.get(dto.id()); for (ReactionDto r : dto.reactions()) { if (r.onReceive() == null) { throw new WorldLoadException( diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/NpcDto.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/NpcDto.java index 23afcb1..2b37c2c 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/NpcDto.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/NpcDto.java @@ -10,12 +10,18 @@ import java.util.List; * @param description examine description * @param greeting text the NPC says on {@code talk} * @param reactions list of trigger/response definitions; may be null + * @param dialogue optional condition-gated dialogue lines; may be null */ public record NpcDto( String id, String name, String description, String greeting, - List reactions + List reactions, + List dialogue ) { + /** Backward-compatible constructor without dialogue. */ + public NpcDto(String id, String name, String description, String greeting, List reactions) { + this(id, name, description, greeting, reactions, null); + } } diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/Npc.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/Npc.java index f6e9435..8e07457 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/Npc.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/Npc.java @@ -3,7 +3,9 @@ package thb.jeanluc.adventure.model; import lombok.Builder; import lombok.Getter; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Optional; @@ -34,6 +36,19 @@ public class Npc { */ private final Map reactions; + /** Optional condition-gated dialogue lines, first match wins; else {@link #greeting}. */ + @Builder.Default + private final List dialogue = new ArrayList<>(); + + /** + * Adds a conditional dialogue line. + * + * @param line the dialogue line + */ + public void addDialogue(DialogueLine line) { + dialogue.add(line); + } + /** * Looks up a reaction triggered by an item. * diff --git a/Semesterprojekt/src/test/java/thb/jeanluc/adventure/command/impl/DialogueTest.java b/Semesterprojekt/src/test/java/thb/jeanluc/adventure/command/impl/DialogueTest.java new file mode 100644 index 0000000..13e4ffe --- /dev/null +++ b/Semesterprojekt/src/test/java/thb/jeanluc/adventure/command/impl/DialogueTest.java @@ -0,0 +1,37 @@ +package thb.jeanluc.adventure.command.impl; + +import org.junit.jupiter.api.Test; +import thb.jeanluc.adventure.game.GameContext; +import thb.jeanluc.adventure.io.TestIO; +import thb.jeanluc.adventure.model.Condition; +import thb.jeanluc.adventure.model.DialogueLine; +import thb.jeanluc.adventure.model.Npc; +import thb.jeanluc.adventure.model.Player; +import thb.jeanluc.adventure.model.Room; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +class DialogueTest { + @Test + void dialogueSwitchesWithFlagElseGreeting() { + Room room = new Room("k", "K", "d"); + Npc man = Npc.shell("man", "Old Man", "stooped", "Hello there."); + man.addDialogue(new DialogueLine( + List.of(new Condition(Condition.Type.FLAG, "power_on")), + "The lights are back, bless you!")); + room.addNpc(man); + Player player = new Player(room, 0); + TestIO io = new TestIO(); + GameContext ctx = new GameContext(null, player, io); + + new TalkCommand().execute(ctx, List.of("man")); + assertThat(io.allOutput()).contains("Hello there"); + + io.outputs().clear(); + ctx.getState().set("power_on"); + new TalkCommand().execute(ctx, List.of("man")); + assertThat(io.allOutput()).contains("lights are back").doesNotContain("Hello there"); + } +}