data/dialogs.pyΒΆ
Part of Dungeon Explorer.
1"""NPC dialog trees: Python dicts with callable conditions and actions.
2
3Each NPC has a dialog tree: dict of node_id -> {text, portrait, choices}.
4Choices can have `condition` (callable taking game_state) and `action` (string key).
5"""
6
7SHOPKEEPER = {
8 "_npc_name": "Shopkeeper",
9 "greeting": {
10 "text": "Welcome, adventurer! Looking to buy or sell?",
11 "portrait": "shopkeeper",
12 "choices": [
13 {"text": "Show me your wares.", "next": "shop_open", "action": "open_shop"},
14 {"text": "I'd like to sell something.", "next": "sell_open", "action": "open_sell"},
15 {"text": "Just browsing. Goodbye.", "next": None},
16 ],
17 },
18 "shop_open": {
19 "text": "Take your time. I've got the finest gear this side of the dungeon.",
20 "portrait": "shopkeeper",
21 "choices": [
22 {"text": "Thanks.", "next": None},
23 ],
24 },
25 "sell_open": {
26 "text": "Let's see what you've got. I'll give you a fair price.",
27 "portrait": "shopkeeper",
28 "choices": [
29 {"text": "Thanks.", "next": None},
30 ],
31 },
32}
33
34INNKEEPER = {
35 "_npc_name": "Innkeeper",
36 "greeting": {
37 "text": "Weary traveller! A rest costs 50 gold and will fully restore your health.",
38 "portrait": "innkeeper",
39 "choices": [
40 {
41 "text": "Rest (50 gold, full heal)",
42 "next": "rested",
43 "action": "inn_rest",
44 "condition": lambda gs: gs.get("gold", 0) >= 50,
45 },
46 {
47 "text": "I can't afford that. (Need 50 gold)",
48 "next": None,
49 "condition": lambda gs: gs.get("gold", 0) < 50,
50 },
51 {"text": "No thanks.", "next": None},
52 ],
53 },
54 "rested": {
55 "text": "There you go, good as new! The dungeon awaits.",
56 "portrait": "innkeeper",
57 "choices": [{"text": "Thanks!", "next": None}],
58 },
59}
60
61BLACKSMITH = {
62 "_npc_name": "Blacksmith",
63 "greeting": {
64 "text": "I can forge or repair your gear. Forging combines 3 same-rarity items. Repairs cost 50 gold.",
65 "portrait": "blacksmith",
66 "choices": [
67 {"text": "Forge (combine 3 same-rarity items)", "next": "forge_done", "action": "open_blacksmith"},
68 {
69 "text": "Repair all equipment (50 gold)",
70 "next": "repair_done",
71 "action": "repair_equipment",
72 "condition": lambda gs: gs.get("gold", 0) >= 50,
73 },
74 {"text": "I can't afford repairs right now.", "next": None, "condition": lambda gs: gs.get("gold", 0) < 50},
75 {"text": "Not right now.", "next": None},
76 ],
77 },
78 "forge_done": {
79 "text": "There you go! Come back when you have more materials.",
80 "portrait": "blacksmith",
81 "choices": [{"text": "Thanks!", "next": None}],
82 },
83 "repair_done": {
84 "text": "Good as new! Take care of your gear out there.",
85 "portrait": "blacksmith",
86 "choices": [{"text": "Thanks!", "next": None}],
87 },
88}
89
90
91def _well_cost(gs):
92 """Compute well cost dynamically from game state."""
93 from nodes.well import Well
94
95 return Well.get_cost(gs.get("max_dungeon_reached", 0))
96
97
98WELL_KEEPER = {
99 "_npc_name": "Well Keeper",
100 "greeting": {
101 "text": "The Well of Fate... Toss a coin and the dungeon reshapes itself. "
102 "But beware, your checkpoints will be lost.",
103 "portrait": "well_keeper",
104 "choices": [
105 {
106 "text": "Toss a coin. (Re-roll dungeon, lose checkpoints)",
107 "next": "rerolled",
108 "action": "well_reroll",
109 "condition": lambda gs: gs.get("gold", 0) >= _well_cost(gs),
110 },
111 {
112 "text": "I can't afford that right now.",
113 "next": None,
114 "condition": lambda gs: gs.get("gold", 0) < _well_cost(gs),
115 },
116 {"text": "That's too risky. Nevermind.", "next": None},
117 ],
118 },
119 "rerolled": {
120 "text": "The waters stir... The dungeon has been reborn. Your checkpoints are gone. "
121 "May fate be kinder this time.",
122 "portrait": "well_keeper",
123 "choices": [{"text": "Into the depths once more.", "next": None}],
124 },
125}
126
127# Quest Board NPC
128QUEST_BOARD = {
129 "_npc_name": "Quest Board",
130 "greeting": {
131 "text": "The Quest Board stands before you. Adventurers post requests here.",
132 "portrait": "quest_board",
133 "choices": [
134 {"text": "View available quests.", "next": "board_open", "action": "open_quest_board"},
135 {"text": "Not right now.", "next": None},
136 ],
137 },
138 "board_open": {
139 "text": "Good luck, adventurer. Return when you've completed a task!",
140 "portrait": "quest_board",
141 "choices": [{"text": "Thanks.", "next": None}],
142 },
143}
144
145# Enchanter NPC
146ENCHANTER = {
147 "_npc_name": "Enchanter",
148 "greeting": {
149 "text": "I can imbue your equipment with magical properties... for a price.",
150 "portrait": "enchanter",
151 "choices": [
152 {"text": "Enchant my gear.", "next": "enchant_open", "action": "open_enchanter"},
153 {"text": "Maybe later.", "next": None},
154 ],
155 },
156 "enchant_open": {
157 "text": "Choose wisely. The enchantment is random, but always powerful.",
158 "portrait": "enchanter",
159 "choices": [{"text": "I see.", "next": None}],
160 },
161}
162
163# Registry: NPC id -> dialog tree
164DIALOGS = {
165 "shopkeeper": SHOPKEEPER,
166 "innkeeper": INNKEEPER,
167 "blacksmith": BLACKSMITH,
168 "well_keeper": WELL_KEEPER,
169 "quest_board": QUEST_BOARD,
170 "enchanter": ENCHANTER,
171}