import asyncio import discord from datetime import datetime, timedelta from random import shuffle from werewolf.player import Player from werewolf.builder import parse_code class Game: """ Base class to run a single game of Werewolf """ default_secret_channel = { "channel": None, "players": [], "votegroup": None } # def __new__(cls, guild, game_code): # game_code = ["DefaultWerewolf", "Villager", "Villager"] # return super().__new__(cls, guild, game_code) def __init__(self, guild, game_code): self.guild = guild self.game_code = ["VanillaWerewolf", "Villager", "Villager"] self.roles = [] self.players = [] self.day_vote = {} # ID, votes self.started = False self.game_over = False self.can_vote = False self.used_votes = 0 self.channel_category = None self.village_channel = None self.p_channels = {} self.vote_groups = {} self.loop = asyncio.get_event_loop() async def setup(self, ctx): """ Runs the initial setup 1. Assign Roles 2. Create Channels 2a. Channel Permissions :eyes: 3. Check Initial role setup (including alerts) 4. Start game """ if self.game_code: await self.get_roles() else: await ctx.send("Number of players does not match number of roles, adjust before starting") return False if len(self.players) != self.roles: await ctx.send("Player count does not match role count, cannot start") self.roles = [] return False # Create category and channel with individual overwrites overwrite = { self.guild.default_role: discord.PermissionOverwrite(read_messages=False, send_messages=True), self.guild.me: discord.PermissionOverwrite(read_messages=True, send_messages=True) } self.channel_category = await self.guild.create_category("ww-game", overwrites=overwrite, reason="New game of werewolf") for player in self.players: overwrite[player.member] = discord.PermissionOverwrite(read_messages=True) self.village_channel = await self.guild.create_text_channel("Village Square", overwrites=overwrite, reason="New game of werewolf", category=self.channel_category) # Assuming everything worked so far await self._at_day_start() # This will queue channels and votegroups to be made for channel_id in self.p_channels: overwrite = { self.guild.default_role: discord.PermissionOverwrite(read_messages=False), self.guild.me: discord.PermissionOverwrite(read_messages=True) } for member in self.p_channels[channel_id]["players"]: overwrite[member] = discord.PermissionOverwrite(read_messages=True) channel = await self.guild.create_text_channel(channel_id, overwrites=overwrite, reason="Werewolf secret channel", category=self.channel_category) self.p_channels[channel_id]["channel"] = channel if self.p_channels[channel_id]["votegroup"] is not None: vote_group = self.p_channels[channel_id]["votegroup"](self, channel) await vote_group.register_player() self.vote_groups[channel_id] = self.p_channels[channel_id]["votegroup"](self, channel) ############START Notify structure############ async def _cycle(self): """ Each event calls the next event _at_day_start() _at_voted() _at_kill() _at_day_end() _at_night_begin() _at_night_end() and repeat with _at_day_start() again """ await self._at_day_start() async def _at_game_start(self): # ID 0 if self.game_over: return await self.village_channel.send("Game is starting, please wait for setup to complete") await self._notify(0) async def _at_day_start(self): # ID 1 if self.game_over: return await self.village_channel.send("The sun rises on a new day in the village") await self.day_perms(self.village_channel) await self._notify(1) self.can_vote = True asyncio.sleep(240) # 4 minute days if not self.can_vote or self.game_over: return await self._at_day_end() async def _at_voted(self, target): # ID 2 if self.game_over: return data = {"player": target} await self._notify(2, data) self.used_votes += 1 await self.all_but_perms(self.village_channel, target) await self.village_channel.send("{} will be put to trial and has 30 seconds to defend themselves".format(target.mention)) asyncio.sleep(30) await self.village_channel.set_permissions(target, read_messages=True) message = await self.village_channel.send("Everyone will now vote whether to lynch {}\nšŸ‘ to save, šŸ‘Ž to lynch\n*Majority rules, no-lynch on ties, vote for both or neither to abstain, 15 seconds to vote*".format(target.mention)) await self.village_channel.add_reaction("šŸ‘") await self.village_channel.add_reaction("šŸ‘Ž") asyncio.sleep(15) reaction_list = message.reactions up_votes = sum(p.emoji == "šŸ‘" and not p.me for p in reaction_list) down_votes = sum(p.emoji == "šŸ‘Ž" and not p.me for p in reaction_list) if len(down_votes) > len(up_votes): embed=discord.Embed(title="Vote Results", color=0xff0000) else: embed=discord.Embed(title="Vote Results", color=0x80ff80) embed.add_field(name="šŸ‘Ž", value="**{}**".format(len(up_votes)), inline=True) embed.add_field(name="šŸ‘", value="**{}**".format(len(down_votes)), inline=True) await self.village_channel.send(embed=embed) if len(down_votes) > len(up_votes): await self.village_channel.send("Voted to lynch {}!".format(target.mention)) await self.kill(target) self.can_vote = False elif self.used_votes >= 3: self.can_vote = False if not self.can_vote: await self._at_day_end() async def _at_kill(self, target): # ID 3 if self.game_over: return data = {"player": target} await self._notify(3, data) async def _at_hang(self, target): # ID 4 if self.game_over: return data = {"player": target} await self._notify(4, data) async def _at_day_end(self): # ID 5 if self.game_over: return self.can_vote = False await self.night_perms(self.village_channel) await self.village_channel.send("**The sun sets on the village...**") await self._notify(5) asyncio.sleep(30) await self._at_night_start() async def _at_night_start(self): # ID 6 if self.game_over: return await self._notify(6) asyncio.sleep(120) # 2 minutes asyncio.sleep(90) # 1.5 minutes asyncio.sleep(30) # .5 minutes await self._at_night_end() async def _at_night_end(self): # ID 7 if self.game_over: return await self._notify(7) asyncio.sleep(15) await self._at_day_start() async def _notify(self, event, data=None): for i in range(10): tasks = [] # Role priorities role_order = [role for role in self.roles if role.action_list[event][1]==i] for role in role_order: tasks.append(asyncio.ensure_future(role.on_event(event, data))) # VoteGroup priorities vote_order = [votes for votes in self.vote_groups.values() if votes.action_list[event][1]==i] for vote_group in vote_order: tasks.append(asyncio.ensure_future(vote_group.on_event(event, data))) self.loop.run_until_complete(asyncio.gather(*tasks)) # Run same-priority task simultaneously ############END Notify structure############ async def generate_targets(self, channel): embed=discord.Embed(title="Remaining Players") for i in range(len(self.players)): player = self.players[i] if player.alive: status="" else: status="*Dead*" embed.add_field(name="ID# **{}**".format(i), value="{} {}".format(status, player.member.display_name), inline=True) return await channel.send(embed=embed) async def register_channel(self, channel_id, player, votegroup=None): """ Queue a channel to be created by game_start """ if channel_id not in self.p_channels: self.p_channels[channel_id] = self.default_secret_channel.copy() await asyncio.sleep(1) self.p_channels[channel_id]["players"].append(player) if votegroup: self.p_channels[channel_id]["votegroup"] = votegroup async def join(self, member: discord.Member, channel: discord.TextChannel): """ Have a member join a game """ if self.started: await channel.send("**Game has already started!**") return if await self.get_player_by_member(member): await channel.send("{} is already in the game!".format(member.mention)) return self.players.append(Player(member)) await channel.send("{} has been added to the game, total players is **{}**".format(member.mention, len(self.players))) async def quit(self, member: discord.Member, channel: discord.TextChannel = None): """ Have a member quit a game """ player = await self.get_player_by_member(member) if not player: return "You're not in a game!" if self.started: await self.kill(member) await channel.send("{} has left the game".format(member.mention)) else: self.players = [player for player in self.players if player.member != member] await channel.send("{} chickened out, player count is now **{}**".format(member.mention, len(self.players))) async def vote(self, author, id, channel): """ Member attempts to cast a vote (usually to lynch) """ player = self._get_player(author) if player is None: await channel.send("You're not in this game!") return if not player.alive: await channel.send("Corpses can't vote") return if channel == self.village_channel: if not self.can_vote: await channel.send("Voting is not allowed right now") return elif channel not in self.p_channels.values(): # Not part of the game return # Don't say anything try: target = self.players[id] except IndexError: target = None if target is None: await channel.send("Not a valid target") return # Now handle village vote or send to votegroup async def kill(self, target, source=None, method: str=None): """ Attempt to kill a target Source allows admin override Be sure to remove permissions appropriately Important to finish execution before triggering notify """ if not target.protected: target.alive = False await self._at_kill(target) if not target.alive: # Still dead after notifying await self.dead_perms(target.member) else: target.protected = False async def lynch(self, target): """ Attempt to lynch a target Important to finish execution before triggering notify """ target.alive = False await self._at_hang(target) if not target.alive: # Still dead after notifying await self.dead_perms(target.member) async def get_roles(self, game_code=None): if game_code: self.game_code=game_code if not self.game_code: return False self.roles = await parse_code(self.game_code) if not self.roles: return False async def get_player_by_member(self, member): for player in self.players: if player.member == member: return player return False async def dead_perms(self, channel, member): await channel.set_permissions(member, read_messages=True, send_message=False, add_reactions=False) async def night_perms(self, channel): await channel.set_permissions(self.guild.default_role, read_messages=False, send_messages=False) async def day_perms(self, channel): await channel.set_permissions(self.guild.default_role, read_messages=False) async def speech_perms(self, channel, member, undo=False): if undo: await channel.set_permissions(self.guild.default_role, read_messages=False) await channel.set_permissions(member, read_messages=True) else: await channel.set_permissions(self.guild.default_role, read_messages=False, send_messages=False) await channel.set_permissions(member, read_messages=True, send_messages=True) async def normal_perms(self, channel, member_list): await channel.set_permissions(self.guild.default_role, read_messages=False) for member in member_list: await channel.set_permissions(member, read_messages=True)