async def _http_error_handler(ctx: Context, error: HTTPException): """Handle HTTP errors.""" if error.code == 50006: # empty message await safe_send( ctx, "Uh oh! Unable to send an empty message." + _report_bug_message, ) if safe_bug_report(ctx): await safe_send(ctx, f"```{error}```") raise error if error.code == 50035: # invalid body await safe_send( ctx, ("Uh oh! The response had an invalid body, likely due to length." + _report_bug_message), ) if safe_bug_report(ctx): await safe_send(ctx, f"```{error}```") raise error if error.code == 30003: # pin limit await safe_send(ctx, "Uh oh! The pin limit has been reached.") raise error
async def end(self, ctx: "DayContext"): """End the day.""" # cleanup effects for player in ctx.bot.game.seating_order: effect_list = [x for x in player.effects] for effect in effect_list: effect.evening_cleanup(ctx.bot.game) # remove the current vote if self.current_vote: await self.current_vote.cancel(ctx) # announcement await safe_send( ctx.bot.channel, f"{ctx.bot.player_role.mention}, go to sleep!", ) # message tally await self._send_message_tally(ctx) # remove the day ctx.bot.game.past_days.append(self) ctx.bot.game.current_day = None # complete if safe_bug_report(ctx): await safe_send(ctx, "Successfully ended the day.") # new night await ctx.bot.game.start_night(ctx)
async def execute(self, ctx: "GameContext"): """Execute the player.""" message_text = f"{self.nick} has been executed, " if self.ghost(ctx.bot.game): await safe_send( ctx.bot.channel, message_text + "but is already dead.", ) elif self.is_status(ctx.bot.game, "safe"): await safe_send( ctx.bot.channel, message_text + "but does not die.", ) else: await safe_send(ctx.bot.channel, message_text + "and dies.") self.add_effect(ctx.bot.game, Dead, self) # Day.end has a "successfully ended the day" message so this is above that if safe_bug_report(ctx): await safe_send(ctx, f"Successfully executed {self.nick}.") if ctx.bot.game.current_day: # TODO: currently doesn't support extra-nomination effects await ctx.bot.game.current_day.end(ctx)
async def inner_wrapper(*args, **kwargs): if args[0].parent.functioning(args[1]): return await func(*args, **kwargs) if safe_bug_report(args[1]): if args[0].parent.ghost(args[1]): status = "dead" elif args[0].parent.is_status(args[1], "drunk"): status = "drunk" elif args[0].parent.is_status(args[1], "poisoned"): status = "poisoned" else: status = "not functioning" if run_if_drunkpoisoned and status in ("drunk", "poisoned"): kwargs["enabled"] = False kwargs["epithet_string"] = f"({status})" return await func(*args, **kwargs) pronouns = load_preferences(args[0].parent).pronouns await safe_send( args[1], "Skipping {epithet}, as {pronoun} {verb} {status}.".format( epithet=args[0].parent.epithet, pronoun=pronouns[0], verb=("is", "are")[pronouns[5]], status=status, ), ) # this return is hella janky but basically we want this to work for any # of the character methods (ex morning, evening) and they have different # return types so we need to grab whatever the generic return is. # the character initializes with no parent to let us check in the method # if it's actually being called or just being called to get the return # so we can hide side effects in an "if self.parent" block return await getattr(Character(None), func.__name__)(*args[1:], **kwargs)
async def current_step(self, ctx: "GameContext"): """Send a reminder of the current step in the night.""" message_text = await self._current_character.morning_call(ctx) if message_text: if safe_bug_report(ctx): await safe_send(ctx, message_text) else: await self._increment_night(ctx)
async def wrapper(*args, **kwargs): if not args[0].parent.is_status(args[1], "used_ability"): return await func(*args, **kwargs) if safe_bug_report(args[1]): pronouns = load_preferences(args[0].parent).pronouns await safe_send( args[1], ("Skipping {epithet}, as " "{subjective} {verb} used {posessive} ability.").format( epithet=args[0].parent.epithet, subjective=pronouns[0], verb=["has", "have"][pronouns[5]], posessive=pronouns[2], ), ) return await getattr(Character(None), func.__name__)(*args[1:], **kwargs)
async def wrapper(character: Character, ctx: "GameContext", *args, **kwargs): if not character.parent.is_status(ctx.bot.game, "used_ability"): return await func(character, ctx, *args, **kwargs) if safe_bug_report(ctx): pronouns = load_preferences(character.parent).pronouns await safe_send( ctx, ("Skipping {epithet}, as " "{subjective} {verb} used {posessive} ability.").format( epithet=character.parent.epithet, subjective=pronouns[0], verb=["has", "have"][pronouns[5]], posessive=pronouns[2], ), ) return await getattr(Character(None), func.__name__)( # type: ignore ctx, *args, **kwargs)
async def _end(self, ctx: "GameContext"): for player in ctx.bot.game.seating_order: player.morning(ctx.bot.inactive_role) effect_list = [x for x in player.effects] for effect in effect_list: effect.morning_cleanup(ctx) # announcements # kills shuffle(self._kills) text = list_to_plural_string([x.nick for x in self._kills], alt="No one") await safe_send( ctx.bot.channel, "{text} {verb} died.".format(text=text[0], verb=("has", "have")[text[1]]), ) # other for content in self._messages: if content: await safe_send(ctx.bot.channel, content, pin=True) # start day await safe_send( ctx.bot.channel, f"{ctx.bot.player_role.mention}, wake up!", pin=bool(self._kills), ) # end night ctx.bot.game.past_nights.append(self) ctx.bot.game.current_night = None # make the day ctx.bot.game.current_day = Day() # complete if safe_bug_report(ctx): await safe_send(ctx, "Successfully started the day.")
async def _generic_error_handler(ctx: Context, error, text): await safe_send(ctx, f"Uh oh! {text} {_report_bug_message}") if safe_bug_report(ctx): await safe_send(ctx, f"```{error}```") raise error
async def on_command_error(self, ctx: Context, error: Exception): """Handle command errors.""" # Ignore commands with local handling if hasattr(ctx.command, "on_error"): return # Check original exceptions for commands.CommandInvokeError error = getattr(error, "original", error) if isinstance(error, commands.CheckFailure): # commands which fail contextual checks if isinstance(error, commands.NotOwner): # commands.is_owner() return await safe_send( ctx, "Stop trying to play around with debug tools, please! ;)") if str(error): # most checks return await safe_send(ctx, str(error)) # checks.is_dm() and checks.is_in_channel() return if isinstance(error, ValueError): # value errors if str(error) == "cancelled": # raised by lib.utils.get_input if input is cancel await safe_send(ctx, "Cancelled!") return if str(error) == "command called": # raised by lib.utils.get_input if another command is called return if str(error) == "player not found": # raised by lib.utils.get_player raise error if str(error) == "unmatched seating order length": # raised by lib.logic.Game.Game.reseat await safe_send( ctx, "The new and old seating orders have differing lengths.") return elif isinstance(error, HTTPException): # errors in HTTP request operations await _http_error_handler(ctx, error) elif isinstance(error, SyntaxError): # syntax errors await safe_send( ctx, "Uh oh! There's a syntax error somewhere." + _report_bug_message, ) if safe_bug_report(ctx): await safe_send(ctx, f"```{error}```") raise error elif isinstance(error, asyncio.TimeoutError): # timeout error return await safe_send(ctx, "Timed out.") elif isinstance(error, commands.CommandNotFound): # unknown commands if ctx.guild is None: return await safe_send( ctx, (f"Command not found: {ctx.invoked_with}. " f"For a list of commands, try `{ctx.prefix}help`."), ) return elif isinstance(error, commands.errors.MissingRequiredArgument): # not enough arguments await safe_send( ctx, "Uh oh! You're missing arguments. Hopefully this helps:") return await ctx.send_help(ctx.command) elif isinstance(error, commands.BadArgument): # converter error return await safe_send(ctx, str(error)) elif isinstance(error, commands.DisabledCommand): # disabled command return await safe_send(ctx, f"{ctx.command.name} has been disabled.") await safe_send( ctx, "Uh oh! An unknown error occured." + _report_bug_message, ) if safe_bug_report(ctx): await safe_send(ctx, f"```{error}```") raise error
async def start_game(self, ctx: "Context", script: "Script"): """Handle startgame logic.""" await safe_send(ctx, f"Starting a {script.name} game.") # ask for the list of players users = await to_member_list( ctx, (await get_input( ctx, ("What is the seating order? (Separate " "users with line breaks. Do not include " "travelers.)"), )).split("\n"), ) # ask for the list of characters characters = to_character_list( ctx, (await get_input( ctx, ("What are the corresponding characters? " "(Separate characters with line breaks.)"), )).split("\n"), script, ) with ctx.typing(): # doing a lot of computation here # verify 1:1 user:character ratio if len(users) != len(characters): raise commands.BadArgument( "There are a different number of players and characters.") # role cleanup await self._startgame_role_cleanup(users) # generate seating order seating_order = [ Player(person, characters[index], index) for index, person in enumerate(users) ] # script message posts = [] for content in list(script.info(ctx)): posts.append(await safe_send(self.channel, content)) for post in posts[:: -1]: # Reverse the _order so the pins are right await post.pin() # welcome message await safe_send( self.channel, (f"{self.player_role.mention}, " "welcome to Blood on the Clocktower! Go to " "sleep."), ) # seating order message seating_order_message = await safe_send( self.channel, generate_game_info_message(seating_order, ctx.bot.game), pin=True, ) # storytellers storytellers = [ Player(person, Storyteller, None) for person in self.storyteller_role.members ] # start the game self.game = Game(seating_order, seating_order_message, script, storytellers) if safe_bug_report(ctx): await safe_send(ctx, "Started the game successfully.") await self.game.start_night(ctx)