Discord Bot - Using Dynamic Commands Based on Game State

I’m working on a Discord bot that manages a mini-game with two commands. Players use !startgame to kick things off, and then they can use !play to join in. Both commands should only function within a designated channel called “game-room”.

Here’s the plan:

  • The !startgame command should only be effective in the game-room channel.
  • The !play command must only work after the game has commenced and in the correct channel.
  • The bot should provide error messages if a user tries to use the commands at the wrong time or in the wrong place.

Currently, I’m facing an issue where the !play command does not work when executed after !startgame has been used.

First Method I Tried:

bot.gameActive = False

@bot.command()
async def play(ctx):
    if ctx.channel.name != "game-room":
        await ctx.reply("Please use this command in the **game-room** channel only")
    elif ctx.channel.name == "game-room":
        if bot.gameActive:
            await ctx.reply("Game is already running")
        else:
            bot.gameActive = True
            await ctx.channel.send('Starting...')
            await asyncio.sleep(2)
            await ctx.channel.send('Go!')
            
            @bot.command(name="innerplay")
            async def play(ctx):
                print("Command executed successfully")

Second Method I Attempted:

bot.gameActive = False

@bot.event
async def on_message(message):
    if message.content == "!play":
        if message.channel.name != "game-room":
            await message.reply("Wrong channel for this command")
        elif message.channel.name == "game-room":
            if not bot.gameActive:
                await message.reply("Game hasn't started yet. Use **!startgame** first")

@bot.command()
async def startgame(ctx):
    if ctx.channel.name != "game-room":
        await ctx.reply("Use this command in **game-room** only")
    elif ctx.channel.name == "game-room":
        if bot.gameActive:
            await ctx.reply("Game already active")
        else:
            bot.gameActive = True
            await ctx.channel.send('Starting...')
            await asyncio.sleep(2)
            
            @bot.command()
            async def play(ctx):
                print("It worked")

The second approach results in an error saying “Command ‘play’ is not found.” How can I fix this? What’s the best way to manage commands that should only be active under certain game conditions?

Your problem is defining commands inside other commands - discord.py doesn’t work that way. Commands get registered when the bot starts, not while it’s running.

I’ve hit this same issue before. Best fix is defining both commands upfront and using a game state variable to control what they do:

bot.gameActive = False

@bot.command()
async def startgame(ctx):
    if ctx.channel.name != "game-room":
        await ctx.reply("Use this command in **game-room** only")
        return
    
    if bot.gameActive:
        await ctx.reply("Game already active")
        return
        
    bot.gameActive = True
    await ctx.channel.send('Starting...')
    await asyncio.sleep(2)
    await ctx.channel.send('Game ready! Use !play to join')

@bot.command()
async def play(ctx):
    if ctx.channel.name != "game-room":
        await ctx.reply("Please use this command in the **game-room** channel only")
        return
        
    if not bot.gameActive:
        await ctx.reply("Game hasn't started yet. Use **!startgame** first")
        return
        
    # Your game logic here
    await ctx.reply("You joined the game!")

Keeps everything simple but still handles state properly.

Yeah, nested commands catch tons of people. Discord.py loads all commands when it starts up, so you can’t create them while the bot’s running. Just use a flag instead - set up both commands normally, then check your gameActive variable in the play command to make sure startgame ran first.