Discord.py D&D dice roll command has far too many issues

Viewed 1088

So all of my D&D party members are convinced that the current dice bot is "cursed", so I decided to take matters into my own hands and make my own bot. It's cool and all, but I have multiple issues with my dice roll command.

The command I currently have is

async def d(ctx, die:int):
    for x in range(1):
        await ctx.send("<:d20:748302353375166614> "+str(random.randint(1,die)))

The first issue is that it uses the format "|d 20" instead of "|d20", so the first request would be getting rid of that space between the "d" and the int that indicates the number of sides the die would have ("die" variable, "20" in this example).

On top of this, I'd like to add a feature where if one types "|[number of dice]d[number of sides]", it'd roll the requested die the requested amount of times. If the number of dice isn't specified (eg.: "|d20"), it should automatically assume that the number of dice is 1. The bot message should include all the rolled numbers, and their sum.

For example "|2d20" might return "<:d20:748302353375166614> 11 + 15 = 26", and "|10d2" might return "<:d20:748302353375166614> 1 + 1 + 2 + 1 + 2 + 2 + 2 + 1 + 1 + 2 = 15"

AND on top of that, I'd also like the ability to be able to add bonuses to rolls ("|[number of dice]d[number of sides]+[bonus]") and return both the dice roll, the bonus, and the final value in the bot's message.

For example "|d20+4" might return "<:d20:748302353375166614> 11 + 4 = 15", and "|2d10+2+3" might return "<:d20:748302353375166614> 9 + 4 + 2 + 3 = 18"

(Optionally, things like "|1d4+2d6" could be made possible if the person painfully writing the answer to this question feels like they aren't in enough agony yet from the sheer size of this request)

2 Answers

With the commands extension, you can't get rid of the space between your command and the die variable. In order to get rid of it, you'd have to use an on_message event to parse your command's content:

from random import sample

@client.event
async def on_message(message):
    content = message.content
    if content.startswith('|') and (content[1].isdigit() or content[1]=='d'):
        content = content.split('+')
        rolls = [int(content.pop(i)) for i in range(len(content)) if content[i].isdigit()]
        for elem in content:
            n, faces = elem.split('d') if elem.split('d')[0] != '' else (1, elem[1:])
            rolls += [randint(1, int(faces)) for _ in range(int(n))]
        rolls_str = ' + '.join([str(n) for n in rolls]) 
        await message.channel.send(f"{rolls_str} = {sum(rolls)}")
    else:
        pass

    await client.process_commands(message)

What about a format like !roll 3d20+6d8+1? If that's acceptable, you could do something like:

async def d(ctx, *args):
    dicestring = "".join(args)
    # Process dicestring here

Here, *args is a variable-length argument specifier. The way commands are parsed, !roll 5d6 + 6d12 will give you arguments ("5d6", "+", "6d12") but !roll 5d6+6d12 will give you arguments ("5d6+6d12").

The "".join(args) will simply mush all those arguments together, leaving you with a string you can parse however you'd like.

If it's important to you that the only prefix is a |, the thing you're trying to do doesn't fit as neatly into the command structure. I'd look into making it an on_message event like https://discordpy.readthedocs.io/en/latest/api.html#discord-api-events.

Related