From 704c7c4741d6118fb6da248074d767fc18780bcc Mon Sep 17 00:00:00 2001 From: Hex Ripley Date: Fri, 4 Apr 2025 13:58:24 -0700 Subject: [PATCH] initial commit --- README.md | 25 ++++++ bot.py | 218 +++++++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 2 + 3 files changed, 245 insertions(+) create mode 100644 README.md create mode 100644 bot.py create mode 100644 requirements.txt diff --git a/README.md b/README.md new file mode 100644 index 0000000..ef85315 --- /dev/null +++ b/README.md @@ -0,0 +1,25 @@ +# Discord Dice Bot + +A Discord bot that handles both traditional dice rolling and the Genesys/Star Wars dice system. + +## Features +- Traditional dice rolling using standard notation (e.g., `/roll 2d6 + 1d8`) +- Genesys/Star Wars dice system support (e.g., `/genesys 2dg + 1dy`) + - Supports dice types: g (green), p (purple), b (blue), k (black), r (red), y (yellow) + - Calculates net Success/Failure and Advantage/Disadvantage + +## Setup +1. Create a `.env` file with your Discord bot token: +``` +DISCORD_TOKEN=your_token_here +``` + +2. Install dependencies: +```bash +pip install -r requirements.txt +``` + +3. Run the bot: +```bash +python bot.py +``` diff --git a/bot.py b/bot.py new file mode 100644 index 0000000..edddc4d --- /dev/null +++ b/bot.py @@ -0,0 +1,218 @@ +import os +import re +import random +from typing import Dict, List, Tuple +import discord +from discord import app_commands +from dotenv import load_dotenv + +# Load environment variables +load_dotenv() +TOKEN = os.getenv('DISCORD_TOKEN') + +# Initialize Discord client +class DiceBot(discord.Client): + def __init__(self): + super().__init__(intents=discord.Intents.default()) + self.tree = app_commands.CommandTree(self) + + async def setup_hook(self): + await self.tree.sync() + +client = DiceBot() + +# Genesys dice definitions +GENESYS_DICE = { + 'g': [ # Green (Ability) + ('', ''), + ('s', ''), + ('s', ''), + ('ss', ''), + ('a', ''), + ('a', ''), + ('s,a', ''), + ('aa', ''), + ], + 'y': [ # Yellow (Proficiency) + ('', ''), + ('s', ''), + ('s', ''), + ('ss', ''), + ('ss', ''), + ('a', ''), + ('s,a', ''), + ('s,a', ''), + ('s,a', ''), + ('aa', ''), + ('aa', ''), + ('t', ''), # Triumph counts as success + ], + 'p': [ # Purple (Difficulty) + ('', ''), + ('f', ''), + ('f,d', ''), + ('d', ''), + ('d', ''), + ('d', ''), + ('dd', ''), + ('f', ''), + ], + 'r': [ # Red (Challenge) + ('', ''), + ('f', ''), + ('f', ''), + ('f,d', ''), + ('f,d', ''), + ('d', ''), + ('d', ''), + ('dd', ''), + ('dd', ''), + ('d,f', ''), + ('d,f', ''), + ('d', ''), # Despair counts as failure + ], + 'b': [ # Blue (Boost) + ('', ''), + ('', ''), + ('s', ''), + ('s,a', ''), + ('aa', ''), + ('a', ''), + ], + 'k': [ # Black (Setback) + ('', ''), + ('', ''), + ('f', ''), + ('f', ''), + ('d', ''), + ('d', ''), + ] +} + +def parse_dice_notation(notation: str) -> List[Tuple[int, str]]: + """Parse dice notation into a list of (count, die_type) tuples.""" + parts = notation.lower().replace(' ', '').split('+') + dice = [] + + for part in parts: + match = re.match(r'(\d+)d([0-9gpbkry]+)', part) + if match: + count = int(match.group(1)) + die_type = match.group(2) + dice.append((count, die_type)) + + return dice + +def roll_traditional_dice(count: int, sides: int) -> List[int]: + """Roll traditional dice.""" + return [random.randint(1, sides) for _ in range(count)] + +def roll_genesys_die(die_type: str) -> Tuple[str, str]: + """Roll a single Genesys die and return its result.""" + return random.choice(GENESYS_DICE[die_type]) + +def calculate_genesys_results(results: List[Tuple[str, str]]) -> Dict[str, int]: + """Calculate net results from Genesys dice rolls.""" + success = 0 + advantage = 0 + triumph = 0 + despair = 0 + + for result, _ in results: + for symbol in result.split(','): + if symbol == 's': + success += 1 + elif symbol == 'f': + success -= 1 + elif symbol == 'a': + advantage += 1 + elif symbol == 'd': + advantage -= 1 + elif symbol == 't': + triumph += 1 + success += 1 + elif symbol == 'x': + despair += 1 + success -= 1 + + return { + 'success': success, + 'advantage': advantage, + 'triumph': triumph, + 'despair': despair + } + +@client.tree.command(name="roll", description="Roll traditional dice (e.g., 2d6 + 1d8)") +async def roll(interaction: discord.Interaction, dice: str): + try: + dice_sets = parse_dice_notation(dice) + if not dice_sets: + await interaction.response.send_message("Invalid dice notation. Use format like '2d6 + 1d8'") + return + + results = [] + total = 0 + + for count, die in dice_sets: + if not die.isdigit(): + await interaction.response.send_message(f"Invalid die type: {die} did you include +? Use format like '2d6 + 1d8'") + return + + sides = int(die) + rolls = roll_traditional_dice(count, sides) + results.append(f"{count}d{sides}: {rolls} = {sum(rolls)}") + total += sum(rolls) + + response = "\n".join(results) + if len(dice_sets) > 1: + response += f"\nTotal: {total}" + + await interaction.response.send_message(response) + except Exception as e: + await interaction.response.send_message(f"Error: {str(e)}") + +@client.tree.command(name="genesys", description="Roll Genesys/Star Wars dice (e.g., 2dg + 1dy)") +async def genesys(interaction: discord.Interaction, dice: str): + try: + dice_sets = parse_dice_notation(dice) + if not dice_sets: + await interaction.response.send_message("Invalid dice notation. Use format like '2dg + 1dy'") + return + + all_results = [] + for count, die_type in dice_sets: + if die_type not in GENESYS_DICE: + await interaction.response.send_message(f"Invalid die type: {die_type}, did you include +? Use format like '2dg + 1dy'") + return + + for _ in range(count): + all_results.append(roll_genesys_die(die_type)) + + net_results = calculate_genesys_results(all_results) + + response = [] + if net_results['success'] > 0: + response.append(f"{net_results['success']} Success{'es' if net_results['success'] > 1 else ''}") + elif net_results['success'] < 0: + response.append(f"{abs(net_results['success'])} Failure{'s' if abs(net_results['success']) > 1 else ''}") + + if net_results['advantage'] > 0: + response.append(f"{net_results['advantage']} Advantage{'s' if net_results['advantage'] > 1 else ''}") + elif net_results['advantage'] < 0: + response.append(f"{abs(net_results['advantage'])} Disadvantage{'s' if abs(net_results['advantage']) > 1 else ''}") + + if net_results['triumph'] > 0: + response.append(f"{net_results['triumph']} Triumph{'s' if net_results['triumph'] > 1 else ''}") + if net_results['despair'] > 0: + response.append(f"{net_results['despair']} Despair{'s' if net_results['despair'] > 1 else ''}") + + await interaction.response.send_message(" | ".join(response) if response else "No symbols rolled") + except Exception as e: + await interaction.response.send_message(f"Error: {str(e)}") + +@client.event +async def on_ready(): + print(f'{client.user} has connected to Discord!') + +if __name__ == "__main__": + client.run(TOKEN) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..34347e3 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +discord.py>=2.3.2 +python-dotenv>=1.0.0