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}