def __init__( self, ctx: Context, attack: int, defense: int, width: int = 15, height: int = 15 ) -> None: self.ctx = ctx self.original_hp = attack * 100 self.original_enemy_hp = attack * 10 self.width = width self.height = height self.maze = Maze.generate(width=width, height=height) self.player_x = 0 self.player_y = 0 self.attack = attack self.defense = defense self.hp = attack * 100 self.heal_hp = round(attack * 0.25) or 1 self.min_dmg = round(attack * 0.5) self.max_dmg = round(attack * 1.5) self.enemy_hp: int | None = None self.message: discord.Message | None = None self.status_text: str | None = _("The active adventure has started.")
async def activeadventure(self, ctx): _( # xgettext: no-python-format """Active adventures will put you into a 15x15 randomly generated maze. You will begin in the top left corner (0,0) and your goal is to find the exit in the bottom right corner (14,14) You control your character with the arrow reactions below the message. You have 1000HP. The adventure ends when you find the exit or your HP drop to zero. You can lose HP by getting damaged by traps or enemies. The maze contains safe spaces and treasures but also traps and enemies. Each space has a 10% chance of being a trap. If a space does not have a trap, it has a 10% chance of having an enemy. Each maze has 5 treasure chests. Traps can damage you from 30 to 120 HP. Enemy damage is based on your own damage. During enemy fights, you can attack (⚔️), defend (🛡️) or recover HP (❤️) Treasure chests can have gold up to 25 times your attack + defense. If you reach the end, you will receive a special treasure with gold up to 100 times your attack + defense. (It is recommended to draw a map of the maze) (This command has a cooldown of 30 minutes)""") if not await ctx.confirm( _("You are going to be in a labyrinth of size 15x15. There are enemies," " treasures and hidden traps. Reach the exit in the bottom right corner" " for a huge extra bonus!\nAre you ready?\n\nTip: Use a silent channel" " for this, you may want to read all the messages I will send." )): return msg = await ctx.send(_("**Generating a maze...**")) maze = Maze.generate(15, 15) direction_emojis = { "n": "\U00002b06", "e": "\U000027a1", "s": "\U00002b07", "w": "\U00002b05", } direction_emojis_inverse = { val: key for key, val in direction_emojis.items() } direction_names = { "n": _("North"), "e": _("East"), "s": _("South"), "w": _("West"), } all_directions = set(direction_names.keys()) x = 0 y = 0 attack, defense = await self.bot.get_damage_armor_for(ctx.author) attack = int(attack) defense = int(defense) hp = 1000 def free(cell): return all_directions - cell.walls def fmt_direction(direction): return direction_names[direction] def player_pos(): return maze[x, y] def is_at_end(): return x == 14 and y == 14 def move(x, y, direction): if direction == "n": y = y - 1 elif direction == "e": x = x + 1 elif direction == "s": y = y + 1 elif direction == "w": x = x - 1 return x, y async def wait_for_move(): possible = free(player_pos()) needed = [direction_emojis[direction] for direction in possible] try: await msg.clear_reactions() except discord.Forbidden: for r in msg.reactions: if str(r.emoji) not in needed: await msg.remove_reaction(r, ctx.guild.me) for r in needed: if r not in [str(r.emoji) for r in msg.reactions]: await msg.add_reaction(r) else: for direction in possible: await msg.add_reaction(direction_emojis[direction]) def check(r, u): return (u == ctx.author and r.message.id == msg.id and direction_emojis_inverse.get(str(r.emoji), None) in possible) r, u = await self.bot.wait_for("reaction_add", check=check, timeout=30) return direction_emojis_inverse[str(r.emoji)] async def update(): text = "" pos = player_pos() for direction in ("n", "e", "s", "w"): side = fmt_direction(direction) fake_x, fake_y = move(x, y, direction) fake_cell = maze[fake_x, fake_y] if direction in pos.walls: text2 = _("To the {side} is a wall.").format(side=side) elif fake_cell.enemy: text2 = _("To the {side} is an enemy.").format(side=side) elif fake_cell.treasure: text2 = _("To the {side} is a treasure.").format(side=side) else: text2 = _("To the {side} is a floor.").format(side=side) text = f"{text}\n{text2}" text2 = _("You are on {hp} HP").format(hp=hp) text = f"{text}\n\n{text2}" await msg.edit(content=text) async def handle_specials(hp): cell = player_pos() if cell.trap: damage = random.randint(30, 120) await ctx.send( _("You stepped on a trap and took {damage} damage!"). format(damage=damage)) cell.trap = False # Remove the trap return hp - damage elif cell.treasure: val = attack + defense money = random.randint(val, val * 25) await self.bot.pool.execute( 'UPDATE profile SET "money"="money"+$1 WHERE "user"=$2;', money, ctx.author.id, ) await self.bot.log_transaction( ctx, from_=1, to=ctx.author.id, subject="money", data={"Amount": money}, ) await ctx.send( _("You found a treasure with **${money}** inside!").format( money=money)) cell.treasure = False elif cell.enemy: def to_bar(hp): fields = hp // 100 return f"[{'▯' * fields}{'▮' * (10 - fields)}]" def is_valid_move(r, u): return (r.message.id == msg.id and u == ctx.author and str(r.emoji) in emojis) emojis = { "\U00002694": "attack", "\U0001f6e1": "defend", "\U00002764": "recover", } enemy = _("Enemy") enemy_hp = 1000 heal_hp = round(attack * 0.25) or 1 min_dmg = round(attack * 0.5) max_dmg = round(attack * 1.5) status1 = _("The Fight started") status2 = "" await msg.clear_reactions() for emoji in emojis: await msg.add_reaction(emoji) while enemy_hp > 0 and hp > 0: await msg.edit(content=f"""\ ``` {ctx.author.name}{" " * (38 - len(ctx.author.name) - len(enemy))}{enemy} ------------++++++++++++++------------ {to_bar(hp)} {hp} {enemy_hp} {to_bar(enemy_hp)} {status1} {status2} ```""") r, u = await self.bot.wait_for("reaction_add", check=is_valid_move, timeout=30) try: await msg.remove_reaction(r, u) except discord.Forbidden: pass enemy_move = random.choice(["attack", "defend", "recover"]) player_move = emojis[str(r.emoji)] if enemy_move == "recover": enemy_hp += heal_hp enemy_hp = 1000 if enemy_hp > 1000 else enemy_hp status1 = _("The Enemy healed themselves for {hp} HP" ).format(hp=heal_hp) if player_move == "recover": hp += heal_hp hp = 1000 if hp > 1000 else hp status2 = _("You healed yourself for {hp} HP").format( hp=heal_hp) if (enemy_move == "attack" and player_move == "defend") or (enemy_move == "defend" and player_move == "attack"): status1 = _("Attack blocked.") status2 = "" if enemy_move == "attack" and player_move != "defend": eff = random.randint(min_dmg, max_dmg) hp -= eff status1 = _("The Enemy hit you for {dmg} damage" ).format(dmg=eff) if player_move == "attack" and enemy_move != "defend": enemy_hp -= attack status2 = _("You hit the enemy for {dmg} damage" ).format(dmg=attack) if enemy_hp <= 0: cell.enemy = False return hp while not is_at_end(): await update() try: direction = await wait_for_move() except asyncio.TimeoutError: await self.bot.reset_cooldown(ctx) return await msg.edit(content=_("Timed out.")) x, y = move(x, y, direction) # Python namespacing sucks, to be honest try: hp = await handle_specials(hp ) # Should've used a class for this except asyncio.TimeoutError: await self.bot.reset_cooldown(ctx) return await msg.edit(content=_("Timed out.")) if hp <= 0: return await ctx.send(_("You died.")) val = attack + defense money = random.randint(val * 5, val * 100) await self.bot.pool.execute( 'UPDATE profile SET "money"="money"+$1 WHERE "user"=$2;', money, ctx.author.id, ) await self.bot.log_transaction(ctx, from_=1, to=ctx.author.id, subject="money", data={"Amount": money}) await ctx.send( _("You have reached the exit and were rewarded **${money}** for getting" " out!").format(money=money))
async def activeadventure(self, ctx): _("""Go out on a docile adventure controlled by reactions.""") if not await ctx.confirm( _("You are going to be in a labyrinth of size 15x15. There are enemies, treasures and hidden traps. Reach the exit in the bottom right corner for a huge extra bonus!\nAre you ready?\n\nTip: Use a silent channel for this, you may want to read all the messages I will send." )): return msg = await ctx.send(_("**Generating a maze...**")) maze = Maze.generate(15, 15) direction_emojis = { "n": "\U00002b06", "e": "\U000027a1", "s": "\U00002b07", "w": "\U00002b05", } direction_emojis_inverse = { val: key for key, val in direction_emojis.items() } direction_names = { "n": _("North"), "e": _("East"), "s": _("South"), "w": _("West"), } all_directions = set(direction_names.keys()) x = 0 y = 0 sword, shield = await self.bot.get_equipped_items_for(ctx.author) attack, defense = await self.bot.generate_stats( ctx.author, float(sword["damage"] if sword else 0), float(shield["armor"] if shield else 0), ) attack = int(attack) defense = int(defense) hp = 1000 def free(cell): return all_directions - cell.walls def fmt_direction(direction): return direction_names[direction] def player_pos(): return maze[x, y] def is_at_end(): return x == 14 and y == 14 def move(x, y, direction): if direction == "n": y = y - 1 elif direction == "e": x = x + 1 elif direction == "s": y = y + 1 elif direction == "w": x = x - 1 return x, y async def wait_for_move(): possible = free(player_pos()) needed = [direction_emojis[direction] for direction in possible] try: await msg.clear_reactions() except discord.Forbidden: for r in msg.reactions: if str(r.emoji) not in needed: await msg.remove_reaction(r, ctx.guild.me) for r in needed: if r not in [str(r.emoji) for r in msg.reactions]: await msg.add_reaction(r) else: for direction in possible: await msg.add_reaction(direction_emojis[direction]) def check(r, u): return (u == ctx.author and r.message.id == msg.id and direction_emojis_inverse.get(str(r.emoji), None) in possible) r, u = await self.bot.wait_for("reaction_add", check=check, timeout=30) return direction_emojis_inverse[str(r.emoji)] async def update(): text = "" pos = player_pos() for direction in ("n", "e", "s", "w"): side = fmt_direction(direction) fake_x, fake_y = move(x, y, direction) fake_cell = maze[fake_x, fake_y] if direction in pos.walls: text2 = _("To the {side} is a wall.").format(side=side) elif fake_cell.enemy: text2 = _("To the {side} is an enemy.").format(side=side) elif fake_cell.treasure: text2 = _("To the {side} is a treasure.").format(side=side) else: text2 = _("To the {side} is a floor.").format(side=side) text = f"{text}\n{text2}" text2 = _("You are on {hp} HP").format(hp=hp) text = f"{text}\n\n{text2}" await msg.edit(content=text) async def handle_specials(hp): cell = player_pos() if cell.trap: damage = random.randint(30, 120) await ctx.send( _("You stepped on a trap and took {damage} damage!"). format(damage=damage)) cell.trap = False # Remove the trap return hp - damage elif cell.treasure: val = attack + defense money = random.randint(val, val * 25) await self.bot.pool.execute( 'UPDATE profile SET "money"="money"+$1 WHERE "user"=$2;', money, ctx.author.id, ) await ctx.send( _("You found a treasure with **${money}** inside!").format( money=money)) cell.treasure = False elif cell.enemy: def to_bar(hp): fields = hp // 100 return f"[{'▯' * fields}{'▮' * (10 - fields)}]" def is_valid_move(r, u): return (r.message.id == msg.id and u == ctx.author and str(r.emoji) in emojis) emojis = { "\U00002694": "attack", "\U0001f6e1": "defend", "\U00002764": "recover", } enemy = _("Enemy") enemy_hp = 1000 heal_hp = round(attack * 0.25) or 1 min_dmg = round(attack * 0.5) max_dmg = round(attack * 1.5) status1 = _("The Fight started") status2 = "" await msg.clear_reactions() for emoji in emojis: await msg.add_reaction(emoji) while enemy_hp > 0 and hp > 0: await msg.edit(content=f"""\ ``` {ctx.author.name}{" " * (38 - len(ctx.author.name) - len(enemy))}{enemy} ------------++++++++++++++------------ {to_bar(hp)} {hp} {enemy_hp} {to_bar(enemy_hp)} {status1} {status2} ```""") r, u = await self.bot.wait_for("reaction_add", check=is_valid_move, timeout=30) try: await msg.remove_reaction(r, u) except discord.Forbidden: pass enemy_move = random.choice(["attack", "defend", "recover"]) player_move = emojis[str(r.emoji)] if enemy_move == "recover": enemy_hp += heal_hp enemy_hp = 1000 if enemy_hp > 1000 else enemy_hp status1 = _("The Enemy healed themselves for {hp} HP" ).format(hp=heal_hp) if player_move == "recover": hp += heal_hp hp = 1000 if hp > 1000 else hp status2 = _("You healed yourself for {hp} HP").format( hp=heal_hp) if (enemy_move == "attack" and player_move == "defend") or (enemy_move == "defend" and player_move == "attack"): status1 = _("Attack blocked.") status2 = "" if enemy_move == "attack" and player_move != "defend": eff = random.randint(min_dmg, max_dmg) hp -= eff status1 = _("The Enemy hit you for {dmg} damage" ).format(dmg=eff) if player_move == "attack" and enemy_move != "defend": enemy_hp -= attack status2 = _("You hit the enemy for {dmg} damage" ).format(dmg=attack) if enemy_hp <= 0: cell.enemy = False return hp while not is_at_end(): await update() try: direction = await wait_for_move() except asyncio.TimeoutError: return await msg.edit(content=_("Timed out.")) x, y = move(x, y, direction) # Python namespacing sucks, to be honest try: hp = await handle_specials(hp ) # Should've used a class for this except asyncio.TimeoutError: return await msg.edit(content=_("Timed out.")) if hp <= 0: return await ctx.send(_("You died.")) val = attack + defense money = random.randint(val * 5, val * 100) await self.bot.pool.execute( 'UPDATE profile SET "money"="money"+$1 WHERE "user"=$2;', money, ctx.author.id, ) await ctx.send( _("You have reached the exit and were rewarded **${money}** for getting out!" ).format(money=money))
async def run(self): maze = Maze.generate(50, 50) for player in self.players: await self.bot.loop.create_task(player.run(maze, self.bot))