Dungeon_Smasher / app(local).py
MelkortheCorrupt
Initial D&D Campaign Manager
237e002
raw
history blame
59.5 kB
# app.py - Fixed D&D Campaign and Character Creator with AI Agents
import gradio as gr
import logging
from typing import Dict, List, Tuple, Optional
import json
import os
from dotenv import load_dotenv
from dataclasses import dataclass
from enum import Enum
import random
# Load environment variables
load_dotenv()
# Set up logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Load OpenAI API key
try:
import openai
api_key = os.getenv("OPENAI_API_KEY")
if api_key:
openai.api_key = api_key
logger.info("βœ… OpenAI API key loaded")
else:
logger.warning("⚠️ No OpenAI API key found")
except ImportError:
logger.warning("⚠️ OpenAI package not installed")
# ===== DATA MODELS =====
class Alignment(Enum):
LAWFUL_GOOD = "Lawful Good"
NEUTRAL_GOOD = "Neutral Good"
CHAOTIC_GOOD = "Chaotic Good"
LAWFUL_NEUTRAL = "Lawful Neutral"
TRUE_NEUTRAL = "True Neutral"
CHAOTIC_NEUTRAL = "Chaotic Neutral"
LAWFUL_EVIL = "Lawful Evil"
NEUTRAL_EVIL = "Neutral Evil"
CHAOTIC_EVIL = "Chaotic Evil"
@dataclass
class CharacterClass:
name: str
hit_die: int
primary_ability: List[str]
saving_throws: List[str]
skills: List[str]
@dataclass
class Race:
name: str
ability_modifiers: Dict[str, int]
traits: List[str]
languages: List[str]
@dataclass
class Character:
name: str
race: str
character_class: str
level: int
gender: str
alignment: Alignment
abilities: Dict[str, int]
hit_points: int
skills: List[str]
background: str
backstory: str
portrait_url: Optional[str] = None
@dataclass
class Campaign:
name: str
theme: str
level_range: str
description: str
locations: List[str]
npcs: List[str]
plot_hooks: List[str]
@dataclass
class NPC:
name: str
race: str
occupation: str
personality: str
secret: str
voice_description: str
relationship_to_party: str
# ===== AI AGENT CLASSES =====
class DungeonMasterAgent:
"""AI agent that acts as a Dungeon Master"""
def __init__(self):
self.personality = "Creative, fair, and engaging storyteller"
self.knowledge_areas = ["D&D rules", "storytelling", "world-building", "character development"]
def generate_campaign_concept(self, theme: str, level: int, player_count: int) -> Dict:
"""Generate a complete campaign concept"""
try:
from openai import OpenAI
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
prompt = f"""As an experienced D&D Dungeon Master, create a campaign concept for:
Theme: {theme}
Player Level: {level}
Number of Players: {player_count}
Provide:
1. Campaign Name
2. Core Plot Hook (2-3 sentences)
3. Main Antagonist
4. 3 Key Locations
5. Central Conflict
6. Estimated Campaign Length
7. Unique Elements/Mechanics
Make it engaging and ready to play!"""
response = client.chat.completions.create(
model="gpt-4",
messages=[{"role": "system", "content": "You are an expert D&D Dungeon Master with 20 years of experience creating memorable campaigns."},
{"role": "user", "content": prompt}],
max_tokens=500,
temperature=0.8
)
content = response.choices[0].message.content
return {"success": True, "content": content}
except Exception as e:
logger.error(f"Campaign generation failed: {e}")
return {"success": False, "error": str(e), "content": f"Mock Campaign: {theme} adventure for {player_count} level {level} characters"}
def generate_session_content(self, campaign_context: str, session_number: int) -> Dict:
"""Generate content for a specific session"""
try:
from openai import OpenAI
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
prompt = f"""Create session {session_number} content for this campaign:
{campaign_context}
Generate:
1. Session Opening (scene description)
2. 3 Potential Encounters (combat, social, exploration)
3. Key NPCs for this session
4. Skill challenges or puzzles
5. Potential plot developments
6. Cliffhanger ending options
Make it detailed enough for a DM to run immediately."""
response = client.chat.completions.create(
model="gpt-4",
messages=[{"role": "system", "content": "You are a D&D Dungeon Master preparing detailed session content."},
{"role": "user", "content": prompt}],
max_tokens=600,
temperature=0.7
)
return {"success": True, "content": response.choices[0].message.content}
except Exception as e:
logger.error(f"Session generation failed: {e}")
return {"success": False, "error": str(e), "content": f"Mock Session {session_number}: Adventure continues..."}
class NPCAgent:
"""AI agent specialized in creating and roleplaying NPCs"""
def __init__(self):
self.personality = "Versatile character actor with deep understanding of motivations"
self.specializations = ["Character creation", "Dialogue", "Motivations", "Voice acting"]
def generate_npc(self, context: str, role: str, importance: str) -> Dict:
"""Generate a detailed NPC"""
try:
from openai import OpenAI
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
prompt = f"""Create a detailed NPC for:
Context: {context}
Role: {role}
Importance: {importance}
Generate:
1. Name and basic demographics
2. Personality traits (3-4 key traits)
3. Background and motivation
4. Speech patterns/accent description
5. Physical description
6. Secret or hidden agenda
7. How they react to different party approaches
8. Potential quest hooks they could provide
Make them memorable and three-dimensional!"""
response = client.chat.completions.create(
model="gpt-4",
messages=[{"role": "system", "content": "You are an expert at creating memorable, three-dimensional NPCs for D&D campaigns."},
{"role": "user", "content": prompt}],
max_tokens=400,
temperature=0.8
)
return {"success": True, "content": response.choices[0].message.content}
except Exception as e:
logger.error(f"NPC generation failed: {e}")
return {"success": False, "error": str(e), "content": f"Mock NPC: {role} character for {context}"}
def roleplay_npc(self, npc_description: str, player_input: str, context: str) -> Dict:
"""Roleplay as an NPC in response to player actions"""
try:
from openai import OpenAI
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
prompt = f"""You are roleplaying as this NPC:
{npc_description}
Context: {context}
Player says/does: {player_input}
Respond in character with:
1. Dialogue (in quotes)
2. Actions/body language (in italics)
3. Internal thoughts/motivations (in parentheses)
Stay true to the character's personality and motivations!"""
response = client.chat.completions.create(
model="gpt-4",
messages=[{"role": "system", "content": "You are a skilled voice actor and D&D player, staying in character as NPCs."},
{"role": "user", "content": prompt}],
max_tokens=200,
temperature=0.9
)
return {"success": True, "content": response.choices[0].message.content}
except Exception as e:
logger.error(f"NPC roleplay failed: {e}")
return {"success": False, "error": str(e), "content": "Mock NPC Response: The character responds appropriately to your action."}
class WorldBuilderAgent:
"""AI agent focused on creating consistent world elements"""
def __init__(self):
self.personality = "Detail-oriented architect of fictional worlds"
self.specializations = ["Geography", "Politics", "Culture", "History", "Economics"]
def generate_location(self, location_type: str, theme: str, purpose: str) -> Dict:
"""Generate a detailed location"""
try:
from openai import OpenAI
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
prompt = f"""Create a detailed {location_type} with:
Theme: {theme}
Purpose in campaign: {purpose}
Generate:
1. Name and general description
2. Key areas/rooms (at least 5)
3. Notable inhabitants
4. Hidden secrets or mysteries
5. Potential dangers or challenges
6. Valuable resources or rewards
7. Connections to broader world/campaign
8. Sensory details (sights, sounds, smells)
Make it feel lived-in and realistic!"""
response = client.chat.completions.create(
model="gpt-4",
messages=[{"role": "system", "content": "You are a master world-builder creating immersive D&D locations."},
{"role": "user", "content": prompt}],
max_tokens=500,
temperature=0.7
)
return {"success": True, "content": response.choices[0].message.content}
except Exception as e:
logger.error(f"Location generation failed: {e}")
return {"success": False, "error": str(e), "content": f"Mock Location: {theme} {location_type} for {purpose}"}
class LootMasterAgent:
"""AI agent specialized in creating balanced loot and magic items"""
def __init__(self):
self.personality = "Meticulous curator of magical treasures"
self.specializations = ["Game balance", "Magic item design", "Treasure distribution"]
def generate_loot_table(self, level: int, encounter_type: str, rarity: str) -> Dict:
"""Generate a balanced loot table"""
try:
from openai import OpenAI
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
prompt = f"""Create a balanced loot table for:
Party Level: {level}
Encounter Type: {encounter_type}
Rarity Level: {rarity}
Generate:
1. Gold/Currency amounts
2. Common items (consumables, gear)
3. Uncommon magical items (if appropriate)
4. Rare items (if high level)
5. Unique/plot-relevant items
6. Alternative treasures (information, allies, etc.)
Ensure balance and appropriateness for the level!"""
response = client.chat.completions.create(
model="gpt-4",
messages=[{"role": "system", "content": "You are an expert at D&D game balance and treasure design."},
{"role": "user", "content": prompt}],
max_tokens=300,
temperature=0.6
)
return {"success": True, "content": response.choices[0].message.content}
except Exception as e:
logger.error(f"Loot generation failed: {e}")
return {"success": False, "error": str(e), "content": f"Mock Loot: Level {level} {encounter_type} {rarity} treasures"}
def create_custom_magic_item(self, item_concept: str, power_level: str, campaign_theme: str) -> Dict:
"""Create a custom magic item"""
try:
from openai import OpenAI
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
prompt = f"""Design a custom magic item:
Concept: {item_concept}
Power Level: {power_level}
Campaign Theme: {campaign_theme}
Provide:
1. Item name and basic description
2. Mechanical effects (stats, abilities)
3. Activation requirements
4. Rarity and attunement needs
5. Physical appearance
6. Historical background/lore
7. Potential drawbacks or limitations
8. How it fits the campaign theme
Make it balanced and interesting!"""
response = client.chat.completions.create(
model="gpt-4",
messages=[{"role": "system", "content": "You are a master craftsperson of magical items for D&D, balancing power with narrative interest."},
{"role": "user", "content": prompt}],
max_tokens=400,
temperature=0.7
)
return {"success": True, "content": response.choices[0].message.content}
except Exception as e:
logger.error(f"Magic item creation failed: {e}")
return {"success": False, "error": str(e), "content": f"Mock Magic Item: {power_level} {item_concept} with {campaign_theme} theme"}
# ===== CHARACTER CREATOR CLASS =====
class CharacterCreator:
"""Enhanced character creator with AI integration"""
def __init__(self):
self.classes = self._get_default_classes()
self.races = self._get_default_races()
self.backgrounds = self._get_backgrounds()
def _get_default_classes(self) -> Dict[str, CharacterClass]:
return {
"Fighter": CharacterClass(
name="Fighter", hit_die=10,
primary_ability=["Strength", "Dexterity"],
saving_throws=["Strength", "Constitution"],
skills=["Acrobatics", "Animal Handling", "Athletics", "History", "Insight", "Intimidation", "Perception", "Survival"]
),
"Wizard": CharacterClass(
name="Wizard", hit_die=6,
primary_ability=["Intelligence"],
saving_throws=["Intelligence", "Wisdom"],
skills=["Arcana", "History", "Insight", "Investigation", "Medicine", "Religion"]
),
"Rogue": CharacterClass(
name="Rogue", hit_die=8,
primary_ability=["Dexterity"],
saving_throws=["Dexterity", "Intelligence"],
skills=["Acrobatics", "Athletics", "Deception", "Insight", "Intimidation", "Investigation", "Perception", "Performance", "Persuasion", "Sleight of Hand", "Stealth"]
),
"Cleric": CharacterClass(
name="Cleric", hit_die=8,
primary_ability=["Wisdom"],
saving_throws=["Wisdom", "Charisma"],
skills=["History", "Insight", "Medicine", "Persuasion", "Religion"]
),
"Barbarian": CharacterClass(
name="Barbarian", hit_die=12,
primary_ability=["Strength"],
saving_throws=["Strength", "Constitution"],
skills=["Animal Handling", "Athletics", "Intimidation", "Nature", "Perception", "Survival"]
),
"Bard": CharacterClass(
name="Bard", hit_die=8,
primary_ability=["Charisma"],
saving_throws=["Dexterity", "Charisma"],
skills=["Any three of your choice"]
),
"Druid": CharacterClass(
name="Druid", hit_die=8,
primary_ability=["Wisdom"],
saving_throws=["Intelligence", "Wisdom"],
skills=["Arcana", "Animal Handling", "Insight", "Medicine", "Nature", "Perception", "Religion", "Survival"]
),
"Monk": CharacterClass(
name="Monk", hit_die=8,
primary_ability=["Dexterity", "Wisdom"],
saving_throws=["Strength", "Dexterity"],
skills=["Acrobatics", "Athletics", "History", "Insight", "Religion", "Stealth"]
),
"Paladin": CharacterClass(
name="Paladin", hit_die=10,
primary_ability=["Strength", "Charisma"],
saving_throws=["Wisdom", "Charisma"],
skills=["Athletics", "Insight", "Intimidation", "Medicine", "Persuasion", "Religion"]
),
"Ranger": CharacterClass(
name="Ranger", hit_die=10,
primary_ability=["Dexterity", "Wisdom"],
saving_throws=["Strength", "Dexterity"],
skills=["Animal Handling", "Athletics", "Insight", "Investigation", "Nature", "Perception", "Stealth", "Survival"]
),
"Sorcerer": CharacterClass(
name="Sorcerer", hit_die=6,
primary_ability=["Charisma"],
saving_throws=["Constitution", "Charisma"],
skills=["Arcana", "Deception", "Insight", "Intimidation", "Persuasion", "Religion"]
),
"Warlock": CharacterClass(
name="Warlock", hit_die=8,
primary_ability=["Charisma"],
saving_throws=["Wisdom", "Charisma"],
skills=["Arcana", "Deception", "History", "Intimidation", "Investigation", "Nature", "Religion"]
)
}
def _get_default_races(self) -> Dict[str, Race]:
return {
"Human": Race(
name="Human",
ability_modifiers={"All": 1},
traits=["Extra Language", "Extra Skill", "Versatile"],
languages=["Common", "One other"]
),
"Elf": Race(
name="Elf",
ability_modifiers={"Dexterity": 2},
traits=["Darkvision", "Keen Senses", "Fey Ancestry", "Trance"],
languages=["Common", "Elvish"]
),
"Dwarf": Race(
name="Dwarf",
ability_modifiers={"Constitution": 2},
traits=["Darkvision", "Dwarven Resilience", "Stonecunning"],
languages=["Common", "Dwarvish"]
),
"Halfling": Race(
name="Halfling",
ability_modifiers={"Dexterity": 2},
traits=["Lucky", "Brave", "Halfling Nimbleness"],
languages=["Common", "Halfling"]
),
"Dragonborn": Race(
name="Dragonborn",
ability_modifiers={"Strength": 2, "Charisma": 1},
traits=["Draconic Ancestry", "Breath Weapon", "Damage Resistance"],
languages=["Common", "Draconic"]
),
"Gnome": Race(
name="Gnome",
ability_modifiers={"Intelligence": 2},
traits=["Darkvision", "Gnome Cunning"],
languages=["Common", "Gnomish"]
),
"Half-Elf": Race(
name="Half-Elf",
ability_modifiers={"Charisma": 2, "Choice": 1},
traits=["Darkvision", "Fey Ancestry", "Two Skills"],
languages=["Common", "Elvish", "One other"]
),
"Half-Orc": Race(
name="Half-Orc",
ability_modifiers={"Strength": 2, "Constitution": 1},
traits=["Darkvision", "Relentless Endurance", "Savage Attacks"],
languages=["Common", "Orc"]
),
"Tiefling": Race(
name="Tiefling",
ability_modifiers={"Intelligence": 1, "Charisma": 2},
traits=["Darkvision", "Hellish Resistance", "Infernal Legacy"],
languages=["Common", "Infernal"]
)
}
def _get_backgrounds(self) -> List[str]:
return [
"Acolyte", "Criminal", "Folk Hero", "Noble", "Sage", "Soldier",
"Charlatan", "Entertainer", "Guild Artisan", "Hermit", "Outlander", "Sailor"
]
def roll_ability_scores(self) -> Dict[str, int]:
"""Roll 4d6, drop lowest, for each ability score"""
abilities = {}
for ability in ["Strength", "Dexterity", "Constitution", "Intelligence", "Wisdom", "Charisma"]:
rolls = [random.randint(1, 6) for _ in range(4)]
rolls.sort(reverse=True)
abilities[ability] = sum(rolls[:3]) # Take top 3
return abilities
def calculate_ability_modifier(self, score: int) -> int:
"""Calculate ability modifier from score"""
return (score - 10) // 2
def calculate_hit_points(self, char_class: str, level: int, constitution_modifier: int) -> int:
"""Calculate hit points based on class, level, and CON modifier"""
hit_die = self.classes[char_class].hit_die
base_hp = hit_die + constitution_modifier # Max HP at level 1
# Add average HP for additional levels
for _ in range(level - 1):
base_hp += (hit_die // 2 + 1) + constitution_modifier
return max(1, base_hp) # Minimum 1 HP
def apply_racial_modifiers(self, base_abilities: Dict[str, int], race: str) -> Dict[str, int]:
"""Apply racial ability score modifiers"""
modified_abilities = base_abilities.copy()
race_data = self.races[race]
for ability, modifier in race_data.ability_modifiers.items():
if ability == "All":
for ability_name in modified_abilities:
modified_abilities[ability_name] += modifier
elif ability == "Choice":
# For simplicity, add to lowest score
lowest_ability = min(modified_abilities, key=modified_abilities.get)
modified_abilities[lowest_ability] += modifier
else:
modified_abilities[ability] += modifier
return modified_abilities
# ===== IMAGE GENERATION =====
def generate_image(prompt: str) -> str:
"""Generate image using OpenAI DALL-E"""
try:
from openai import OpenAI
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
response = client.images.generate(
model="dall-e-3",
prompt=prompt,
size="1024x1024",
quality="standard",
n=1,
)
return response.data[0].url
except Exception as e:
logger.error(f"Image generation failed: {e}")
return "https://via.placeholder.com/512x512/dc2626/ffffff?text=Image+Generation+Failed"
# ===== MAIN INTERFACE =====
def create_main_interface():
"""Create the main D&D Campaign Manager interface"""
# Initialize agents
dm_agent = DungeonMasterAgent()
npc_agent = NPCAgent()
world_agent = WorldBuilderAgent()
loot_agent = LootMasterAgent()
character_creator = CharacterCreator()
custom_css = """
.agent-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 20px;
border-radius: 15px;
margin: 10px 0;
color: white;
}
.output-box {
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 10px;
padding: 15px;
margin: 10px 0;
}
"""
with gr.Blocks(css=custom_css, title="D&D Campaign Manager") as demo:
gr.Markdown("""
# 🏰 Advanced D&D Campaign Manager
*Your AI-powered toolkit for epic adventures*
**Features:**
- 🎭 AI Dungeon Master Assistant
- πŸ‘₯ Intelligent NPC Creator & Roleplay
- πŸ—ΊοΈ World Builder Agent
- πŸ’° Loot Master & Magic Item Designer
- πŸ‰ Enhanced Character Creator
""")
with gr.Tabs():
# ===== CHARACTER CREATOR TAB =====
with gr.TabItem("πŸ‰ Character Creator"):
with gr.Row():
with gr.Column(scale=2):
gr.Markdown("## πŸ“ Character Details")
character_name = gr.Textbox(label="Character Name", placeholder="Enter name...")
with gr.Row():
race_dropdown = gr.Dropdown(
choices=list(character_creator.races.keys()),
label="Race", value="Human"
)
class_dropdown = gr.Dropdown(
choices=list(character_creator.classes.keys()),
label="Class", value="Fighter"
)
gender_dropdown = gr.Dropdown(
choices=["Male", "Female", "Non-binary", "Transgender Male", "Transgender Female", "Genderfluid", "Agender", "Other"],
label="Gender", value="Male"
)
with gr.Row():
level_slider = gr.Slider(minimum=1, maximum=20, step=1, value=1, label="Level")
alignment_dropdown = gr.Dropdown(
choices=[alignment.value for alignment in Alignment],
label="Alignment", value=Alignment.LAWFUL_GOOD.value
)
background_dropdown = gr.Dropdown(
choices=character_creator._get_backgrounds(),
label="Background", value="Folk Hero"
)
# AI Enhancement Buttons
with gr.Row():
generate_name_btn = gr.Button("✨ AI Generate Name", variant="secondary")
generate_backstory_btn = gr.Button("πŸ“š AI Generate Backstory", variant="secondary")
backstory = gr.Textbox(label="Backstory", lines=4, placeholder="Character background...")
# Ability Scores
gr.Markdown("## 🎲 Ability Scores")
roll_btn = gr.Button("🎲 Roll Ability Scores", variant="primary")
with gr.Row():
str_score = gr.Number(label="Strength", value=10, precision=0)
dex_score = gr.Number(label="Dexterity", value=10, precision=0)
con_score = gr.Number(label="Constitution", value=10, precision=0)
with gr.Row():
int_score = gr.Number(label="Intelligence", value=10, precision=0)
wis_score = gr.Number(label="Wisdom", value=10, precision=0)
cha_score = gr.Number(label="Charisma", value=10, precision=0)
with gr.Column(scale=1):
gr.Markdown("## πŸ“Š Character Summary")
character_summary = gr.Markdown("*Create character to see summary*")
gr.Markdown("## 🎨 Character Portrait")
portrait_btn = gr.Button("🎨 Generate AI Portrait", variant="primary")
character_portrait = gr.Image(label="Portrait", height=300)
gr.Markdown("## πŸ’Ύ Export")
export_btn = gr.Button("πŸ“₯ Export Character")
export_file = gr.File(label="Character JSON")
# Hidden state
character_data = gr.State()
# ===== CAMPAIGN CREATOR TAB =====
with gr.TabItem("🎭 AI Dungeon Master"):
gr.Markdown("## οΏ½οΏ½ Campaign Generation", elem_classes=["agent-card"])
with gr.Row():
with gr.Column():
campaign_theme = gr.Dropdown(
choices=["High Fantasy", "Dark Fantasy", "Urban Fantasy", "Steampunk", "Horror", "Comedy", "Political Intrigue", "Exploration"],
label="Campaign Theme", value="High Fantasy"
)
campaign_level = gr.Slider(minimum=1, maximum=20, value=5, label="Starting Level")
player_count = gr.Slider(minimum=1, maximum=8, value=4, label="Number of Players")
generate_campaign_btn = gr.Button("🎲 Generate Campaign Concept", variant="primary", size="lg")
with gr.Column():
campaign_visual_btn = gr.Button("πŸ–ΌοΈ Generate Campaign Art")
campaign_image = gr.Image(label="Campaign Visual", height=300)
campaign_output = gr.Textbox(label="Campaign Concept", lines=10, elem_classes=["output-box"])
gr.Markdown("## πŸ“… Session Planning")
with gr.Row():
session_number = gr.Number(label="Session Number", value=1, precision=0)
generate_session_btn = gr.Button("πŸ“ Generate Session Content", variant="secondary")
session_output = gr.Textbox(label="Session Content", lines=8, elem_classes=["output-box"])
# ===== NPC CREATOR TAB =====
with gr.TabItem("πŸ‘₯ NPC Agent"):
gr.Markdown("## 🎭 NPC Creator & Roleplay Assistant", elem_classes=["agent-card"])
with gr.Row():
with gr.Column():
gr.Markdown("### Create New NPC")
npc_context = gr.Textbox(label="Campaign/Scene Context", placeholder="Describe the setting or situation...")
npc_role = gr.Dropdown(
choices=["Ally", "Neutral", "Antagonist", "Quest Giver", "Merchant", "Authority Figure", "Mysterious Stranger"],
label="NPC Role", value="Neutral"
)
npc_importance = gr.Dropdown(
choices=["Minor", "Moderate", "Major", "Recurring"],
label="Importance Level", value="Moderate"
)
create_npc_btn = gr.Button("🎭 Generate NPC", variant="primary")
npc_portrait_btn = gr.Button("🎨 Generate NPC Portrait")
npc_portrait = gr.Image(label="NPC Portrait", height=300)
with gr.Column():
npc_output = gr.Textbox(label="Generated NPC", lines=12, elem_classes=["output-box"])
gr.Markdown("### πŸ’¬ NPC Roleplay")
with gr.Row():
with gr.Column(scale=2):
player_input = gr.Textbox(label="Player Action/Dialogue", placeholder="What does the player say or do?")
roleplay_context = gr.Textbox(label="Scene Context", placeholder="Describe the current situation...")
with gr.Column(scale=1):
roleplay_btn = gr.Button("🎭 NPC Response", variant="secondary", size="lg")
npc_response = gr.Textbox(label="NPC Response", lines=4, elem_classes=["output-box"])
# Hidden states for NPC
current_npc_data = gr.State()
# ===== WORLD BUILDER TAB =====
with gr.TabItem("πŸ—ΊοΈ World Builder"):
gr.Markdown("## πŸ—οΈ Location & World Generator", elem_classes=["agent-card"])
with gr.Row():
with gr.Column():
location_type = gr.Dropdown(
choices=["Tavern", "Dungeon", "City", "Forest", "Castle", "Temple", "Shop", "Wilderness", "Mansion", "Cave System"],
label="Location Type", value="Tavern"
)
location_theme = gr.Dropdown(
choices=["Standard Fantasy", "Dark/Gothic", "Mystical", "Abandoned/Ruined", "Luxurious", "Dangerous", "Peaceful", "Mysterious"],
label="Theme", value="Standard Fantasy"
)
location_purpose = gr.Textbox(label="Purpose in Campaign", placeholder="How will this location be used?")
generate_location_btn = gr.Button("πŸ—οΈ Generate Location", variant="primary")
location_art_btn = gr.Button("πŸ–ΌοΈ Generate Location Art")
with gr.Column():
location_image = gr.Image(label="Location Visual", height=300)
location_output = gr.Textbox(label="Location Details", lines=12, elem_classes=["output-box"])
# ===== LOOT MASTER TAB =====
with gr.TabItem("πŸ’° Loot Master"):
gr.Markdown("## πŸ’Ž Treasure & Magic Item Generator", elem_classes=["agent-card"])
with gr.Row():
with gr.Column():
gr.Markdown("### 🎲 Loot Table Generator")
loot_level = gr.Slider(minimum=1, maximum=20, value=5, label="Party Level")
encounter_type = gr.Dropdown(
choices=["Boss Fight", "Mini-boss", "Standard Combat", "Exploration Reward", "Quest Completion", "Treasure Hoard"],
label="Encounter Type", value="Standard Combat"
)
loot_rarity = gr.Dropdown(
choices=["Poor", "Standard", "Rich", "Legendary"],
label="Treasure Quality", value="Standard"
)
generate_loot_btn = gr.Button("πŸ’° Generate Loot Table", variant="primary")
with gr.Column():
gr.Markdown("### ✨ Custom Magic Item")
item_concept = gr.Textbox(label="Item Concept", placeholder="What kind of item? (sword, ring, staff, etc.)")
power_level = gr.Dropdown(
choices=["Common", "Uncommon", "Rare", "Very Rare", "Legendary", "Artifact"],
label="Power Level", value="Uncommon"
)
campaign_theme_item = gr.Textbox(label="Campaign Theme", placeholder="How should it fit your campaign?")
create_item_btn = gr.Button("✨ Create Magic Item", variant="secondary")
item_art_btn = gr.Button("🎨 Generate Item Art")
with gr.Row():
loot_output = gr.Textbox(label="Loot Table", lines=8, elem_classes=["output-box"])
magic_item_output = gr.Textbox(label="Magic Item Details", lines=8, elem_classes=["output-box"])
item_image = gr.Image(label="Magic Item Visual", height=250)
# ===== CAMPAIGN TOOLS TAB =====
with gr.TabItem("πŸ› οΈ Campaign Tools"):
gr.Markdown("## 🎯 Advanced Campaign Management")
with gr.Tabs():
with gr.TabItem("πŸ“Š Initiative Tracker"):
gr.Markdown("### βš”οΈ Combat Management")
with gr.Row():
add_character = gr.Textbox(label="Character Name", placeholder="Add to initiative...")
add_initiative = gr.Number(label="Initiative Roll", value=10)
add_btn = gr.Button("βž• Add to Initiative")
initiative_list = gr.Dataframe(
headers=["Name", "Initiative", "HP", "AC", "Status"],
label="Initiative Order",
interactive=True
)
with gr.Row():
next_turn_btn = gr.Button("⏭️ Next Turn", variant="primary")
reset_combat_btn = gr.Button("πŸ”„ Reset Combat")
with gr.TabItem("πŸ“ Session Notes"):
gr.Markdown("### πŸ“– Session Management")
session_date = gr.Textbox(label="Session Date", value="Session 1")
with gr.Row():
with gr.Column():
key_events = gr.Textbox(label="Key Events", lines=4, placeholder="What happened this session?")
npc_interactions = gr.Textbox(label="NPC Interactions", lines=3, placeholder="Who did they meet?")
with gr.Column():
player_actions = gr.Textbox(label="Notable Player Actions", lines=4, placeholder="What did the players do?")
next_session_prep = gr.Textbox(label="Next Session Prep", lines=3, placeholder="What to prepare for next time?")
save_notes_btn = gr.Button("πŸ’Ύ Save Session Notes", variant="primary")
notes_output = gr.File(label="Session Notes File")
with gr.TabItem("🎲 Random Generators"):
gr.Markdown("### 🎯 Quick Generators")
with gr.Row():
with gr.Column():
gr.Markdown("**Name Generators**")
name_type = gr.Dropdown(
choices=["Human Male", "Human Female", "Elven", "Dwarven", "Orcish", "Fantasy Place", "Tavern", "Shop"],
value="Human Male"
)
gen_name_btn = gr.Button("🎲 Generate Name")
random_name = gr.Textbox(label="Generated Name")
with gr.Column():
gr.Markdown("**Quick Encounters**")
encounter_level = gr.Slider(1, 20, value=5, label="Party Level")
encounter_difficulty = gr.Dropdown(
choices=["Easy", "Medium", "Hard", "Deadly"],
value="Medium"
)
gen_encounter_btn = gr.Button("βš”οΈ Generate Encounter")
random_encounter = gr.Textbox(label="Encounter", lines=3)
with gr.Row():
with gr.Column():
gr.Markdown("**Plot Hooks**")
hook_theme = gr.Dropdown(
choices=["Mystery", "Adventure", "Political", "Personal", "Rescue", "Exploration"],
value="Adventure"
)
gen_hook_btn = gr.Button("🎣 Generate Plot Hook")
plot_hook = gr.Textbox(label="Plot Hook", lines=2)
with gr.Column():
gr.Markdown("**Weather & Atmosphere**")
climate = gr.Dropdown(
choices=["Temperate", "Tropical", "Arctic", "Desert", "Mountainous"],
value="Temperate"
)
gen_weather_btn = gr.Button("🌀️ Generate Weather")
weather = gr.Textbox(label="Weather & Mood", lines=2)
# ===== EVENT HANDLERS =====
# Character Creator Events
def roll_abilities():
abilities = character_creator.roll_ability_scores()
return (
abilities["Strength"], abilities["Dexterity"], abilities["Constitution"],
abilities["Intelligence"], abilities["Wisdom"], abilities["Charisma"]
)
def generate_character_name(race, char_class, gender, alignment):
try:
from openai import OpenAI
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
prompt = f"Generate a {gender} {race} {char_class} name appropriate for D&D. Return only the name."
response = client.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": prompt}],
max_tokens=30
)
return response.choices[0].message.content.strip()
except Exception as e:
# Fallback to simple name generation
names = {
"Human": {
"Male": ["Garrett", "Marcus", "Thomas", "William", "James"],
"Female": ["Elena", "Sarah", "Miranda", "Catherine", "Rose"],
"Non-binary": ["Alex", "Jordan", "Riley", "Casey", "Taylor"],
"Other": ["River", "Sage", "Phoenix", "Ash", "Rowan"]
},
"Elf": {
"Male": ["Aelar", "Berrian", "Drannor", "Enna", "Galinndan"],
"Female": ["Adrie", "Althaea", "Anastrianna", "Andraste", "Antinua"],
"Non-binary": ["Aerdyl", "Ahvir", "Aramil", "Aranea", "Berris"],
"Other": ["Dayereth", "Enna", "Galinndan", "Hadarai", "Halimath"]
},
"Dwarf": {
"Male": ["Adrik", "Baern", "Darrak", "Delg", "Eberk"],
"Female": ["Amber", "Bardryn", "Diesa", "Eldeth", "Gunnloda"],
"Non-binary": ["Alberich", "Balin", "Dain", "Fundin", "GloΓ­n"],
"Other": ["HΓ‘var", "KΓ­li", "NΓ‘li", "Ori", "Thorek"]
},
"Halfling": {
"Male": ["Alton", "Ander", "Cade", "Corrin", "Eldon"],
"Female": ["Andry", "Bree", "Callie", "Cora", "Euphemia"],
"Non-binary": ["Finnan", "Garret", "Lindal", "Lyle", "Merric"],
"Other": ["Nedda", "Paela", "Portia", "Seraphina", "Shaena"]
}
}
# Handle various gender identities
gender_key = gender
if gender in ["Transgender Male", "Male"]:
gender_key = "Male"
elif gender in ["Transgender Female", "Female"]:
gender_key = "Female"
elif gender in ["Non-binary", "Genderfluid", "Agender"]:
gender_key = "Non-binary"
else:
gender_key = "Other"
race_names = names.get(race, names["Human"])
return random.choice(race_names.get(gender_key, race_names["Other"]))
def generate_character_backstory(name, race, char_class, gender, alignment, background):
try:
from openai import OpenAI
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
prompt = f"""Create a compelling backstory for {name}, a {gender} {race} {char_class} with {alignment} alignment and {background} background.
Write 2-3 paragraphs about their origins, motivations, and key life events. Be respectful and inclusive in your portrayal."""
response = client.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": prompt}],
max_tokens=300
)
return response.choices[0].message.content.strip()
except Exception as e:
return f"{name} is a {gender} {race} {char_class} with a {background} background. Their journey began in their homeland, where they learned the skills that would define their path as an adventurer, embracing their identity and forging their own destiny."
def generate_character_portrait(character_data):
if not character_data:
return "Please create a character first"
prompt = f"Fantasy portrait of a {character_data.gender.lower()} {character_data.race.lower()} {character_data.character_class.lower()}, professional D&D character art, respectful and inclusive representation"
return generate_image(prompt)
def update_character_summary(name, race, char_class, level, gender, alignment,
str_val, dex_val, con_val, int_val, wis_val, cha_val, background, backstory):
if not all([str_val, dex_val, con_val, int_val, wis_val, cha_val]):
return "*Roll ability scores to see summary*", None
base_abilities = {
"Strength": int(str_val), "Dexterity": int(dex_val), "Constitution": int(con_val),
"Intelligence": int(int_val), "Wisdom": int(wis_val), "Charisma": int(cha_val)
}
final_abilities = character_creator.apply_racial_modifiers(base_abilities, race)
con_modifier = character_creator.calculate_ability_modifier(final_abilities["Constitution"])
hit_points = character_creator.calculate_hit_points(char_class, int(level), con_modifier)
character = Character(
name=name or "Unnamed Character", race=race, character_class=char_class,
level=int(level), gender=gender, alignment=Alignment(alignment), abilities=final_abilities,
hit_points=hit_points, skills=character_creator.classes[char_class].skills[:2],
background=background, backstory=backstory
)
summary = f"""**{character.name}**
*Level {level} {gender} {race} {char_class} ({alignment})*
**Ability Scores:**
- STR: {final_abilities['Strength']} ({character_creator.calculate_ability_modifier(final_abilities['Strength']):+d})
- DEX: {final_abilities['Dexterity']} ({character_creator.calculate_ability_modifier(final_abilities['Dexterity']):+d})
- CON: {final_abilities['Constitution']} ({character_creator.calculate_ability_modifier(final_abilities['Constitution']):+d})
- INT: {final_abilities['Intelligence']} ({character_creator.calculate_ability_modifier(final_abilities['Intelligence']):+d})
- WIS: {final_abilities['Wisdom']} ({character_creator.calculate_ability_modifier(final_abilities['Wisdom']):+d})
- CHA: {final_abilities['Charisma']} ({character_creator.calculate_ability_modifier(final_abilities['Charisma']):+d})
**Combat Stats:**
- Hit Points: {hit_points}
- Hit Die: d{character_creator.classes[char_class].hit_die}
**Background:** {background}"""
return summary, character
# Campaign Events
def generate_campaign_concept(theme, level, players):
result = dm_agent.generate_campaign_concept(theme, level, players)
return result.get("content", "Error generating campaign")
def generate_campaign_art(theme, level):
prompt = f"{theme} D&D campaign art for level {level} adventurers, epic fantasy illustration"
return generate_image(prompt)
def generate_session_content(campaign_output, session_num):
if not campaign_output:
return "Please generate a campaign concept first"
result = dm_agent.generate_session_content(campaign_output, session_num)
return result.get("content", "Error generating session")
# NPC Events
def create_npc(context, role, importance):
result = npc_agent.generate_npc(context, role, importance)
return result.get("content", "Error creating NPC")
def generate_npc_portrait(npc_data):
if not npc_data:
return "Create an NPC first"
prompt = "Fantasy portrait of a D&D NPC character, detailed face, professional RPG art"
return generate_image(prompt)
def npc_roleplay_response(npc_data, player_input, context):
if not npc_data or not player_input:
return "Please create an NPC and enter player input"
result = npc_agent.roleplay_npc(npc_data, player_input, context)
return result.get("content", "Error in roleplay")
# World Builder Events
def create_location(loc_type, theme, purpose):
result = world_agent.generate_location(loc_type, theme, purpose)
return result.get("content", "Error creating location")
def generate_location_art(loc_type, theme):
prompt = f"{theme} {loc_type} fantasy location, detailed environment art, D&D setting"
return generate_image(prompt)
# Loot Events
def create_loot_table(level, encounter, rarity):
result = loot_agent.generate_loot_table(level, encounter, rarity)
return result.get("content", "Error creating loot")
def create_magic_item(concept, power, theme):
result = loot_agent.create_custom_magic_item(concept, power, theme)
return result.get("content", "Error creating item")
def generate_item_art(concept, power):
prompt = f"{power} magical {concept}, fantasy item illustration, glowing magical effects"
return generate_image(prompt)
# Random Generator Events
def generate_random_name(name_type):
names = {
"Human Male": ["Garrett", "Marcus", "Thomas", "William", "James"],
"Human Female": ["Elena", "Sarah", "Miranda", "Catherine", "Rose"],
"Elven": ["Aelar", "Berrian", "Drannor", "Enna", "Galinndan"],
"Dwarven": ["Adrik", "Baern", "Darrak", "Delg", "Eberk"],
"Orcish": ["Grul", "Thark", "Dench", "Feng", "Gell"],
"Fantasy Place": ["Ravenshollow", "Goldbrook", "Thornfield", "Mistral Keep"],
"Tavern": ["The Prancing Pony", "Dragon's Rest", "The Silver Tankard"],
"Shop": ["Magical Mysteries", "Fine Blades & More", "Potions & Remedies"]
}
return random.choice(names.get(name_type, ["Unknown"]))
def generate_random_encounter(level, difficulty):
encounters = [
f"Bandit ambush (adapted for level {level})",
f"Wild animal encounter (CR {max(1, level//4)})",
f"Mysterious traveler with a quest",
f"Ancient ruins with {difficulty.lower()} traps",
f"Rival adventuring party",
f"Magical phenomenon requiring investigation"
]
return random.choice(encounters)
def generate_plot_hook(theme):
hooks = {
"Mystery": "A beloved local figure has vanished without a trace, leaving behind only cryptic clues.",
"Adventure": "Ancient maps surface pointing to a legendary treasure thought lost forever.",
"Political": "A diplomatic envoy requests secret protection during dangerous negotiations.",
"Personal": "A character's past catches up with them in an unexpected way.",
"Rescue": "Innocent people are trapped in a dangerous situation and need immediate help.",
"Exploration": "Uncharted territories beckon with promises of discovery and danger."
}
return hooks.get(theme, "A mysterious stranger approaches with an urgent request.")
def generate_weather(climate):
weather_options = {
"Temperate": ["Sunny and mild", "Light rain showers", "Overcast skies", "Gentle breeze"],
"Tropical": ["Hot and humid", "Sudden thunderstorm", "Sweltering heat", "Monsoon rains"],
"Arctic": ["Bitter cold winds", "Heavy snowfall", "Blizzard conditions", "Icy fog"],
"Desert": ["Scorching sun", "Sandstorm approaching", "Cool desert night", "Rare rainfall"],
"Mountainous": ["Mountain mist", "Alpine winds", "Rocky terrain", "Sudden weather change"]
}
return random.choice(weather_options.get(climate, ["Pleasant weather"]))
# Wire up all events
roll_btn.click(roll_abilities, outputs=[str_score, dex_score, con_score, int_score, wis_score, cha_score])
generate_name_btn.click(
generate_character_name,
inputs=[race_dropdown, class_dropdown, gender_dropdown, alignment_dropdown],
outputs=[character_name]
)
generate_backstory_btn.click(
generate_character_backstory,
inputs=[character_name, race_dropdown, class_dropdown, gender_dropdown, alignment_dropdown, background_dropdown],
outputs=[backstory]
)
portrait_btn.click(
generate_character_portrait,
inputs=[character_data],
outputs=[character_portrait]
)
# Update character summary when inputs change
for component in [character_name, race_dropdown, class_dropdown, level_slider, gender_dropdown, alignment_dropdown,
str_score, dex_score, con_score, int_score, wis_score, cha_score, background_dropdown, backstory]:
component.change(
update_character_summary,
inputs=[character_name, race_dropdown, class_dropdown, level_slider, gender_dropdown, alignment_dropdown,
str_score, dex_score, con_score, int_score, wis_score, cha_score, background_dropdown, backstory],
outputs=[character_summary, character_data]
)
# Campaign events
generate_campaign_btn.click(
generate_campaign_concept,
inputs=[campaign_theme, campaign_level, player_count],
outputs=[campaign_output]
)
campaign_visual_btn.click(
generate_campaign_art,
inputs=[campaign_theme, campaign_level],
outputs=[campaign_image]
)
generate_session_btn.click(
generate_session_content,
inputs=[campaign_output, session_number],
outputs=[session_output]
)
# NPC events
create_npc_btn.click(
create_npc,
inputs=[npc_context, npc_role, npc_importance],
outputs=[npc_output]
)
npc_portrait_btn.click(
generate_npc_portrait,
inputs=[current_npc_data],
outputs=[npc_portrait]
)
roleplay_btn.click(
npc_roleplay_response,
inputs=[npc_output, player_input, roleplay_context],
outputs=[npc_response]
)
# World builder events
generate_location_btn.click(
create_location,
inputs=[location_type, location_theme, location_purpose],
outputs=[location_output]
)
location_art_btn.click(
generate_location_art,
inputs=[location_type, location_theme],
outputs=[location_image]
)
# Loot events
generate_loot_btn.click(
create_loot_table,
inputs=[loot_level, encounter_type, loot_rarity],
outputs=[loot_output]
)
create_item_btn.click(
create_magic_item,
inputs=[item_concept, power_level, campaign_theme_item],
outputs=[magic_item_output]
)
item_art_btn.click(
generate_item_art,
inputs=[item_concept, power_level],
outputs=[item_image]
)
# Random generator events
gen_name_btn.click(generate_random_name, inputs=[name_type], outputs=[random_name])
gen_encounter_btn.click(generate_random_encounter, inputs=[encounter_level, encounter_difficulty], outputs=[random_encounter])
gen_hook_btn.click(generate_plot_hook, inputs=[hook_theme], outputs=[plot_hook])
gen_weather_btn.click(generate_weather, inputs=[climate], outputs=[weather])
return demo
# Main entry point
if __name__ == "__main__":
logger.info("🏰 Starting Advanced D&D Campaign Manager...")
# Check for OpenAI API key
api_key = os.getenv("OPENAI_API_KEY")
if api_key:
logger.info("βœ… OpenAI API key found - All AI features enabled!")
else:
logger.warning("⚠️ No OpenAI API key found - AI features will be limited")
logger.info("πŸ’‘ Add OPENAI_API_KEY=your_key to a .env file to enable all AI features")
demo = create_main_interface()
try:
demo.launch(
share=True,
server_name="0.0.0.0",
server_port=7860,
inbrowser=True
)
logger.info("🌐 App launched successfully!")
logger.info("πŸ”— Local URL: http://localhost:7860")
except Exception as e:
logger.error(f"❌ Launch failed: {e}")
logger.info("πŸ’‘ Try: pip install --upgrade gradio")