"""D&D Adventure scenario data and sequence generation. This module provides: - DND_ENCOUNTERS: Dictionary of encounter templates by type - generate_dnd_sequence: Function to generate adventure sequences """ import json import random # Encounter templates organized by type DND_ENCOUNTERS = { "combat": [ ("Goblin Ambush", "A band of goblin raiders springs from hiding, blocking the path ahead", ["Goblin", "Goblin Boss"]), ("Wolf Pack Hunt", "Hungry wolves circle the party, eyes gleaming in the darkness", ["Wolf", "Dire Wolf"]), ("Orc War Party", "War drums echo as an orc raiding party charges forward", ["Orc", "Orc War Chief"]), ("Undead Rising", "The ground trembles as skeletal warriors claw their way to the surface", ["Skeleton", "Zombie"]), ("Bandit Roadblock", "Rough-looking bandits demand toll or blood", ["Bandit", "Bandit Captain"]), ("Giant Spider Lair", "Webs glisten in the torchlight as massive spiders descend", ["Giant Spider", "Phase Spider"]), ("Troll Bridge", "A massive troll guards the only crossing, demanding tribute", ["Troll"]), ("Dragon Wyrmling", "A young dragon defends its growing hoard with fierce determination", ["Dragon Wyrmling"]), ("Cultist Ritual", "Robed figures chant around a dark altar, summoning something terrible", ["Cultist", "Cult Fanatic"]), ("Elemental Fury", "An elemental creature of pure destructive force bars the way", ["Fire Elemental", "Earth Elemental"]), ], "social": [ ("Tavern Rumors", "Locals share whispered tales over ale in a dimly lit tavern", ["Commoner", "Noble"]), ("Noble's Request", "A wealthy patron offers gold for a dangerous task", ["Noble", "Knight"]), ("Merchant Dispute", "Two traders argue over goods, seeking an arbiter", ["Merchant", "Guard"]), ("Temple Audience", "A high priest offers guidance... for a price", ["Priest", "Acolyte"]), ("Guild Meeting", "The local guild has work, but demands loyalty first", ["Guild Master", "Apprentice"]), ("Royal Court", "The party gains audience with local nobility", ["King", "Advisor"]), ("Mysterious Stranger", "A hooded figure offers cryptic warnings and hidden knowledge", ["Spy", "Assassin"]), ("Festival Celebration", "The town celebrates, but something lurks beneath the merriment", ["Commoner", "Entertainer"]), ], "exploration": [ ("Ancient Ruins", "Crumbling stone walls hint at a civilization long forgotten", ["trap", "puzzle"]), ("Dark Cave", "The cave mouth yawns before you, promising secrets in the darkness", ["hazard", "treasure"]), ("Haunted Manor", "The abandoned estate creaks and groans with supernatural presence", ["ghost", "trap"]), ("Underground River", "A subterranean waterway offers passage deeper into the earth", ["hazard", "discovery"]), ("Forest Clearing", "Fey magic permeates this impossibly perfect glade", ["fey", "mystery"]), ("Mountain Pass", "Treacherous cliffs and howling winds test every step", ["hazard", "environmental"]), ("Sunken Temple", "Flooded chambers hide ancient treasures and guardians", ["trap", "treasure"]), ("Wizard's Tower", "Arcane energy crackles through abandoned magical laboratories", ["magic", "puzzle"]), ], "puzzle": [ ("The Riddle Door", "Ancient text poses a riddle - only the wise may pass", ["intelligence", "lore"]), ("Pressure Plates", "The floor is a grid of stone plates, some safe, some deadly", ["dexterity", "perception"]), ("Mirror Maze", "Reflections shift and change, hiding the true path", ["wisdom", "investigation"]), ("Elemental Locks", "Four elements must be combined in the correct order", ["arcana", "elements"]), ("Ancestor's Trial", "Spirits of the past judge worth through tests of virtue", ["charisma", "history"]), ("Clockwork Mechanism", "Gears and levers control something vital within", ["intelligence", "mechanics"]), ("Symbol Sequence", "Ancient symbols must be activated in the prophesied order", ["religion", "arcana"]), ("Weight Balance", "Treasures and counterweights must be perfectly balanced", ["wisdom", "mathematics"]), ], "boss": [ ("Dragon's Lair", "The great wyrm awakens, fire building in its throat", ["Adult Dragon"]), ("Lich's Sanctum", "The undead sorcerer rises from its throne of bones", ["Lich"]), ("Beholder's Den", "Multiple eyes swivel toward you, each crackling with deadly magic", ["Beholder"]), ("Giant King", "The massive ruler of giants hefts a weapon the size of a tree", ["Giant King"]), ("Vampire Lord", "The ancient vampire regards you with cold, eternal hunger", ["Vampire"]), ("Mind Flayer Colony", "Alien intellects probe your mind as tentacled horrors advance", ["Mind Flayer"]), ("Demon Portal", "A rift to the Abyss spews forth a demon of terrible power", ["Demon Lord"]), ("Corrupted Paladin", "Once a hero, now fallen to darkness and seeking to spread it", ["Death Knight"]), ], } # Difficulty modifiers DIFFICULTY_LEVELS = { "easy": { "adjective": "manageable", "enemies": "few", "treasure": "modest", }, "medium": { "adjective": "challenging", "enemies": "several", "treasure": "valuable", }, "hard": { "adjective": "dangerous", "enemies": "many", "treasure": "significant", }, "deadly": { "adjective": "deadly", "enemies": "overwhelming", "treasure": "legendary", }, } # Location flavors LOCATIONS = [ "dungeon depths", "ancient forest", "mountain fortress", "coastal caves", "desert tomb", "frozen tundra", "volcanic lair", "feywild crossing", "shadowfell edge", "planar rift", "underwater ruins", "floating citadel", ] def generate_dnd_sequence(encounter_count, difficulty, include_combat, include_social, include_exploration, include_puzzle, include_boss, force_boss_end=True): """Generate a D&D adventure sequence. Args: encounter_count: Number of encounters (3-10) difficulty: One of 'easy', 'medium', 'hard', 'deadly' include_combat: Include combat encounters include_social: Include social encounters include_exploration: Include exploration encounters include_puzzle: Include puzzle encounters include_boss: Include boss encounters force_boss_end: Force sequence to end with a boss fight Returns: Tuple of (list_output, json_output, prompts_output) """ difficulty_info = DIFFICULTY_LEVELS.get(difficulty, DIFFICULTY_LEVELS["medium"]) location = random.choice(LOCATIONS) # Build available encounters pool available = [] if include_combat: available.extend([(e[0], e[1], "combat", e[2]) for e in DND_ENCOUNTERS["combat"]]) if include_social: available.extend([(e[0], e[1], "social", e[2]) for e in DND_ENCOUNTERS["social"]]) if include_exploration: available.extend([(e[0], e[1], "exploration", e[2]) for e in DND_ENCOUNTERS["exploration"]]) if include_puzzle: available.extend([(e[0], e[1], "puzzle", e[2]) for e in DND_ENCOUNTERS["puzzle"]]) if not available and not include_boss: return "Select at least one encounter type!", "{}", "" sequence = [] # Reserve boss for end if requested boss_encounter = None if force_boss_end and include_boss: boss_encounter = random.choice(DND_ENCOUNTERS["boss"]) encounter_count -= 1 # Fill with random encounters random.shuffle(available) for enc in available[:encounter_count]: sequence.append(enc) # Add boss at end if boss_encounter: sequence.append((boss_encounter[0], boss_encounter[1], "boss", boss_encounter[2])) if not sequence: return "No encounters available!", "{}", "" # Format as list list_output = f"## D&D Adventure in the {location.title()}\n" list_output += f"**Difficulty:** {difficulty.title()} ({difficulty_info['adjective']})\n\n" for i, (name, desc, enc_type, extras) in enumerate(sequence, 1): extras_str = ", ".join(extras) if isinstance(extras, list) else str(extras) list_output += f"**{i}. {name}** [{enc_type}]\n{desc}\n*Features: {extras_str}*\n\n" # Format as config JSON config = {"adventure": {}} for i, (name, desc, enc_type, extras) in enumerate(sequence, 1): state_name = name.lower().replace(" ", "_").replace("'", "").replace("-", "_") current_state_id = f"encounter_{i}_{state_name}" extras_str = ", ".join(extras) if isinstance(extras, list) else str(extras) # Determine next state if i < len(sequence): next_name = sequence[i][0] next_state_name = next_name.lower().replace(" ", "_").replace("'", "").replace("-", "_") next_state_id = f"encounter_{i+1}_{next_state_name}" else: next_state_id = "adventure_complete" # Create choices based on encounter type if enc_type == "combat": choices = ["Fight", "Attempt Diplomacy", "Try to Sneak Past"] elif enc_type == "social": choices = ["Engage in Conversation", "Observe Silently", "Make an Offer"] elif enc_type == "exploration": choices = ["Investigate Carefully", "Rush Forward", "Search for Traps"] elif enc_type == "puzzle": choices = ["Study the Puzzle", "Try a Solution", "Look for Clues"] elif enc_type == "boss": choices = ["Prepare for Battle", "Attempt Negotiation", "Launch Surprise Attack"] else: choices = ["Continue"] # All choices lead to same next state for simplicity transitions = {choice: next_state_id for choice in choices} config["adventure"][current_state_id] = { "description": f"[{enc_type.upper()} - {difficulty.upper()}] {desc}\n\nYou notice: {extras_str}", "media_prompt": f"Fantasy RPG scene in {location}: {name} - {desc}, {difficulty_info['adjective']} atmosphere, featuring {extras_str}", "choices": choices, "transitions": transitions } # Add completion state first_enc = sequence[0][0].lower().replace(" ", "_").replace("'", "").replace("-", "_") config["adventure"]["adventure_complete"] = { "description": f"Victory! The {location} has been conquered. {difficulty_info['treasure'].title()} treasures await the brave adventurers.", "choices": ["Embark on New Adventure"], "transitions": { "Embark on New Adventure": f"encounter_1_{first_enc}" } } json_output = json.dumps(config, indent=2) # Format prompts prompts_output = f"## D&D Adventure Prompts ({location.title()})\n\n" for i, (name, desc, enc_type, extras) in enumerate(sequence, 1): extras_str = ", ".join(extras) if isinstance(extras, list) else str(extras) prompts_output += f"Fantasy RPG scene: {name} - {desc}, featuring {extras_str}\n" return list_output, json_output, prompts_output # Quick test if __name__ == "__main__": list_out, json_out, prompts_out = generate_dnd_sequence( 5, "medium", True, True, True, False, True ) print(list_out) print(json_out)