create_campaign
Create a new Dungeons & Dragons 5e campaign with configurable rules versions and interaction modes for text, audio, or voice-enabled gameplay.
Instructions
Create a new D&D campaign.
The rules_version parameter selects which edition of the D&D 5e rules to use for this campaign. '2024' uses the revised 2024 rules, '2014' uses the original 5th edition rules.
The interaction_mode parameter controls how the DM communicates:
classic: Text-only, no voice dependencies required.
narrated: DM responses delivered as TTS audio + text via WebSocket.
immersive: Narrated + player STT input from browser.
Interaction mode and model profile are independent axes — any combination is valid.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| name | Yes | Campaign name | |
| description | Yes | Brief decription of the campaign, or a tagline | |
| dm_name | No | Dungeon Master name | |
| setting | No | Campaign setting - a full description of the setting of the campaign in markdown format, or the path to a `.txt` or `.md` file containing the same. | |
| rules_version | No | D&D rules version: '2014' or '2024' (default: '2024') | 2024 |
| interaction_mode | No | Interaction mode: 'classic' (text-only), 'narrated' (TTS audio + text), 'immersive' (narrated + STT input). Default: 'classic' | classic |
Implementation Reference
- src/dm20_protocol/storage.py:447-502 (handler)DnDStorage.create_campaign method handles campaign creation logic by delegating to a split backend and initializing campaign-specific metadata and directory structures.
def create_campaign(self, name: str, description: str, dm_name: str | None = None, setting: str | Path | None = None, rules_version: str = "2024", interaction_mode: str = "classic") -> Campaign: """Create a new campaign using split storage format. Args: name: Campaign name description: Campaign description dm_name: Dungeon Master name setting: Campaign setting rules_version: D&D rules version ('2014' or '2024', default '2024') interaction_mode: Interaction mode ('classic', 'narrated', or 'immersive', default 'classic') """ logger.info(f"✨ Creating new campaign: '{name}' (rules: {rules_version}, mode: {interaction_mode})") # Use split backend to create the campaign campaign = self._split_backend.create_campaign( name=name, description=description, dm_name=dm_name, setting=setting ) # Sync to main storage self._current_campaign = campaign self._current_format = StorageFormat.SPLIT # Store rules_version and interaction_mode in campaign metadata self._rules_version = rules_version self._interaction_mode = interaction_mode # Create rulebooks directory structure campaign_dir = self._split_backend._get_campaign_dir(name) rulebooks_dir = campaign_dir / "rulebooks" rulebooks_dir.mkdir(exist_ok=True) (rulebooks_dir / "custom").mkdir(exist_ok=True) logger.debug(f"📂 Created rulebooks directory structure at {rulebooks_dir}") # Save rules_version and interaction_mode to campaign.json metadata self._save_rules_version(campaign_dir, rules_version) self._save_interaction_mode(campaign_dir, interaction_mode) # Rebuild indexes for new campaign self._rebuild_character_index() # Update campaign hash self._campaign_hash = self._compute_campaign_hash() # Initialize library bindings for the new campaign self._library_bindings = LibraryBindings(campaign_id=campaign.id) logger.debug(f"📚 Created empty library bindings for campaign '{name}'") # Initialize discovery tracker for the new campaign self._discovery_tracker = DiscoveryTracker(campaign_dir) logger.debug(f"Initialized empty DiscoveryTracker for campaign '{name}'") logger.info(f"✅ Campaign '{name}' created and set as active using {self._current_format} format (rules: {rules_version}, mode: {interaction_mode}).") return campaign - src/dm20_protocol/storage.py:2113-2143 (handler)SplitStorageBackend.create_campaign method performs the actual file system operations to create a new campaign directory and store initialized JSON files.
def create_campaign(self, name: str, description: str, dm_name: str | None = None, setting: str | Path | None = None) -> Campaign: """Create a new campaign. Args: name: Campaign name description: Campaign description dm_name: Dungeon Master name setting: Campaign setting (string or path to file) Returns: New Campaign object """ logger.info(f"✨ Creating new campaign: '{name}'") # Ensure directory structure exists self._ensure_campaign_structure(name) # Create campaign object game_state = GameState(campaign_name=name) campaign = Campaign( name=name, description=description, dm_name=dm_name, setting=setting, game_state=game_state ) self._current_campaign = campaign self.save_all(force=True) # Force save for new campaign logger.info(f"✅ Campaign '{name}' created and set as active.") return campaign