diff --git a/werewolf/game.py b/werewolf/game.py index 66b5245..b076b11 100644 --- a/werewolf/game.py +++ b/werewolf/game.py @@ -22,10 +22,12 @@ class Game: } morning_messages = [ - "**The sun rises on the village..**", - "**Morning has arrived..**" + "**The sun rises on day {} in the village..**", + "**Morning has arrived on day {}..**" ] + day_vote_count = 3 + # def __new__(cls, guild, game_code): # game_code = ["VanillaWerewolf", "Villager", "Villager"] @@ -47,6 +49,9 @@ class Game: self.can_vote = False self.used_votes = 0 + self.day_time = False + self.day_count = 0 + self.channel_category = None self.village_channel = None @@ -153,10 +158,13 @@ class Game: if self.game_over: return - embed=discord.Embed(title=random.choice(self.morning_messages)) + self.day_count += 1 + embed=discord.Embed(title=random.choice(self.morning_messages.format(self.day_count))) for result in self.night_results: embed.add_field(name=result, value="________", inline=False) - + + self.day_time = True + self.night_results = [] # Clear for next day await self.village_channel.send(embed=embed) @@ -174,7 +182,9 @@ class Game: await self.village_channel.send(embed=discord.Embed(title="**Two minutes of daylight remain...**")) await asyncio.sleep(120) # 4 minute days - if not self.can_vote or self.game_over: + # Need a loop here to wait for trial to end + + if not self.can_vote or not self.day_time or self.game_over: return await self._at_day_end() @@ -187,14 +197,14 @@ class Game: self.used_votes += 1 - await self.all_but_perms(self.village_channel, target) + await self.speech_perms(self.village_channel, target.member) await self.village_channel.send("**{} will be put to trial and has 30 seconds to defend themselves**".format(target.mention)) await asyncio.sleep(30) - await self.village_channel.set_permissions(target, read_messages=True) + await self.speech_perms(self.village_channel, target.member, undo=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)) + 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 both or neither to abstain, 15 seconds to vote*".format(target.mention)) await self.village_channel.add_reaction("šŸ‘") await self.village_channel.add_reaction("šŸ‘Ž") @@ -218,11 +228,17 @@ class Game: if len(down_votes) > len(up_votes): await self.village_channel.send("**Voted to lynch {}!**".format(target.mention)) - await self.kill(target) + await self.lynch(target) self.can_vote = False - elif self.used_votes >= 3: - self.can_vote = False - + else: + await self.village_channel.send("**{} has been spared!**".format(target.mention)) + + if self.used_votes >= self.day_vote_count: + await self.village_channel.send("**All votes have been used! Day is now over!**") + self.can_vote = False + else: + await self.village_channel.send("**{}**/**{}** of today's votes have been used!\nNominate carefully..".format(self.used_votes, self.day_vote_count)) + if not self.can_vote: await self._at_day_end() @@ -247,6 +263,7 @@ class Game: self.can_vote = False self.day_vote = {} self.vote_totals = {} + self.day_time = False await self.night_perms(self.village_channel) @@ -353,7 +370,7 @@ class Game: return "You're not in a game!" if self.started: - await self.kill(member) + await self._quit(player) await channel.send("{} has left the game".format(member.mention)) else: self.players = [player for player in self.players if player.member != member] @@ -384,6 +401,16 @@ class Game: await player.choose(ctx, data) + async def visit(self, target_id, source): + """ + Night visit target_id + Returns a target for role information (i.e. Seer) + """ + target = await self.get_night_target(target_id, source) + await target.role.visit(source) + await self._at_visit(target, source) + + return target async def vote(self, author, id, channel): @@ -443,6 +470,7 @@ class Game: self.vote_totals[id] += 1 required_votes = len([player for player in self.players if player.alive]) // 7 + 2 + if self.vote_totals[id] < required_votes: await self.village_channel.send("{} has voted to put {} to trial. {} more votes needed".format(author.mention, target.member.mention, required_votes - self.vote_totals[id])) else: @@ -452,7 +480,22 @@ class Game: async def eval_results(self, target, source=None, method = None): - return "{} was found dead".format(target.member.display_name) + if method is not None: + out = "**{ID}** - " + method + return out.format(ID=target.id, target=target.member.display_name) + else: + return "**{ID}** - {} was found dead".format(ID=target.id, target=target.member.display_name) + + async def _quit(self, player): + """ + Have player quit the game + """ + + player.alive = False + await self._at_kill(player) + player.alive = False # Do not allow resurrection + await self.dead_perms(player.member) + # Add a punishment system for quitting games later async def kill(self, target_id, source=None, method: str=None): """ @@ -461,18 +504,25 @@ class Game: Be sure to remove permissions appropriately Important to finish execution before triggering notify """ - target = await self.get_night_target(target_id, source) + + if source is None: + target = self.players[target_id] + elif self.day_time: + target = self.get_day_target(target_id, source) + else: + target = await self.get_night_target(target_id, source) if source is not None: if source.blocked: # Do nothing if blocked, blocker handles text return - else: - + if not target.protected: target.alive = False + await target.kill(source) await self._at_kill(target) if not target.alive: # Still dead after notifying - self.night_results.append(await self.eval_results(target, source, method)) + if not self.day_time: + self.night_results.append(await self.eval_results(target, source, method)) await self.dead_perms(target.member) else: target.protected = False @@ -530,7 +580,6 @@ class Game: 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) @@ -539,7 +588,6 @@ class Game: 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) @@ -556,4 +604,4 @@ class Game: async def _end_game(self): #ToDo - pass \ No newline at end of file + pass diff --git a/werewolf/player.py b/werewolf/player.py index 6d0ff98..4e93e0e 100644 --- a/werewolf/player.py +++ b/werewolf/player.py @@ -17,6 +17,7 @@ class Player: self.alive = True self.muted = False self.protected = False + self.mention = self.member.mention async def assign_role(self, role): """ diff --git a/werewolf/role.py b/werewolf/role.py index d4c73f2..94f273f 100644 --- a/werewolf/role.py +++ b/werewolf/role.py @@ -40,7 +40,7 @@ class Role: rand_choice = False # Determines if it can be picked as a random role (False for unusually disruptive roles) category = [0] # List of enrolled categories (listed above) - allignment = 0 # 1: Town, 2: Werewolf, 3: Neutral + alignment = 0 # 1: Town, 2: Werewolf, 3: Neutral channel_id = "" # Empty for no private channel unique = False # Only one of this role per game game_start_message= ( @@ -74,7 +74,7 @@ class Role: await self.action_list[event][0](data) - + async def assign_player(self, player): """ Give this role a player @@ -84,14 +84,29 @@ class Role: player.role = self self.player = player - async def _get_role(self, source=None): + async def get_alignment(self, source=None): + """ + Interaction for powerful access of alignment + (Village, Werewolf, Other) + Unlikely to be able to deceive this + """ + return self.alignment + + async def see_alignment(self, source=None): + """ + Interaction for investigative roles attempting + to see alignment (Village, Werewolf Other) + """ + return "Other" + + async def get_role(self, source=None): """ Interaction for powerful access of role Unlikely to be able to deceive this """ return "Default" - async def _see_role(self, source=None): + async def see_role(self, source=None): """ Interaction for investigative roles. More common to be able to deceive this action @@ -128,6 +143,14 @@ class Role: async def _at_visit(self, data=None): pass + async def kill(self, source): + """ + Called when someone is trying to kill you! + Can you do anything about it? + self.alive is now set to False, set to True to stay alive + """ + pass + async def visit(self, source): """ Called whenever a night action targets you diff --git a/werewolf/roles/seer.py b/werewolf/roles/seer.py index b03894e..2141dfa 100644 --- a/werewolf/roles/seer.py +++ b/werewolf/roles/seer.py @@ -6,7 +6,7 @@ class Seer(Role): rand_choice = False # Determines if it can be picked as a random role (False for unusually disruptive roles) category = [1,2] # List of enrolled categories (listed above) - allignment = 1 # 1: Town, 2: Werewolf, 3: Neutral + alignment = 1 # 1: Town, 2: Werewolf, 3: Neutral channel_id = "" # Empty for no private channel unique = False # Only one of this role per game game_start_message=( @@ -50,7 +50,21 @@ class Seer(Role): # player.role = self # self.player = player - + + # async def get_alignment(self, source=None): + # """ + # Interaction for power access of team (Village, Werewolf, Other) + # Unlikely to be able to deceive this + # """ + # return self.alignment + + async def see_alignment(self, source=None): + """ + Interaction for investigative roles attempting + to see team (Village, Werewolf Other) + """ + return "Village" + async def _get_role(self, source=None): """ Interaction for powerful access of role @@ -85,17 +99,44 @@ class Seer(Role): async def _at_night_start(self): await self.game.generate_targets(self.player.member) - await self.player.member.send("{}\n**Pick a target to see tonight**\n") + await self.player.send_dm("{}\n**Pick a target to see tonight**\n") async def _at_night_end(self): + target = await self.game.visit(self.see_target) + + alignment = await target.see_alignment(self.player) + + if alignment == "Werewolf": + out = "Your insight reveals this player to be a **Werewolf!**" + else: + out = "You fail to find anything suspicious about this player..." + + await self.player.send_dm(out) + + # async def _at_visit(self, data=None): + # pass + + # async def kill(self, source): + # """ + # Called when someone is trying to kill you! + # Can you do anything about it? + # self.alive is now set to False, set to True to stay alive + # """ + # pass + + # async def visit(self, source): + # """ + # Called whenever a night action targets you + # Source is the player who visited you + # """ + # pass - async def choose(self, ctx, data): """Handle night actions""" id = int(data) try: - target = game.players[id] + target = self.game.players[id] except IndexError: target = None diff --git a/werewolf/roles/vanillawerewolf.py b/werewolf/roles/vanillawerewolf.py index 5bccea7..75e28b4 100644 --- a/werewolf/roles/vanillawerewolf.py +++ b/werewolf/roles/vanillawerewolf.py @@ -9,7 +9,7 @@ class VanillaWerewolf(Role): rand_choice = True category = [11, 15] - allignment = 2 # 1: Town, 2: Werewolf, 3: Neutral + alignment = 2 # 1: Town, 2: Werewolf, 3: Neutral channel_id = "werewolves" unique = False game_start_message = ( @@ -53,6 +53,20 @@ class VanillaWerewolf(Role): # player.role = self # self.player = player + # async def get_alignment(self, source=None): + # """ + # Interaction for power access of team (Village, Werewolf, Other) + # Unlikely to be able to deceive this + # """ + # return self.alignment + + async def see_alignment(self, source=None): + """ + Interaction for investigative roles attempting + to see team (Village, Werewolf Other) + """ + return "Werewolf" + async def _get_role(self, source=None): """ Interaction for powerful access of role @@ -97,7 +111,15 @@ class VanillaWerewolf(Role): # async def _at_visit(self, data=None): # pass - + + # async def kill(self, source): + # """ + # Called when someone is trying to kill you! + # Can you do anything about it? + # self.alive is now set to False, set to True to stay alive + # """ + # pass + # async def visit(self, source): # """ # Called whenever a night action targets you diff --git a/werewolf/roles/villager.py b/werewolf/roles/villager.py index 396a8e8..d6c3e03 100644 --- a/werewolf/roles/villager.py +++ b/werewolf/roles/villager.py @@ -6,7 +6,7 @@ class Villager(Role): rand_choice = False # Determines if it can be picked as a random role (False for unusually disruptive roles) category = [1] # List of enrolled categories (listed above) - allignment = 1 # 1: Town, 2: Werewolf, 3: Neutral + alignment = 1 # 1: Town, 2: Werewolf, 3: Neutral channel_id = "" # Empty for no private channel unique = False # Only one of this role per game game_start_message=( @@ -50,7 +50,21 @@ class Villager(Role): # player.role = self # self.player = player - + + # async def get_alignment(self, source=None): + # """ + # Interaction for power access of team (Village, Werewolf, Other) + # Unlikely to be able to deceive this + # """ + # return self.alignment + + async def see_alignment(self, source=None): + """ + Interaction for investigative roles attempting + to see team (Village, Werewolf Other) + """ + return "Village" + async def _get_role(self, source=None): """ Interaction for powerful access of role @@ -91,7 +105,15 @@ class Villager(Role): # async def _at_visit(self, data=None): # pass - + + # async def kill(self, source): + # """ + # Called when someone is trying to kill you! + # Can you do anything about it? + # self.alive is now set to False, set to True to stay alive + # """ + # pass + # async def visit(self, source): # """ # Called whenever a night action targets you diff --git a/werewolf/votegroup.py b/werewolf/votegroup.py index a4eb768..27c4b30 100644 --- a/werewolf/votegroup.py +++ b/werewolf/votegroup.py @@ -11,7 +11,7 @@ class VoteGroup: Handles secret channels and group decisions """ - allignment = 0 # 1: Town, 2: Werewolf, 3: Neutral + alignment = 0 # 1: Town, 2: Werewolf, 3: Neutral channel_id = "" def __init__(self, game, channel): @@ -63,7 +63,7 @@ class VoteGroup: async def _at_night_start(self, data=None): if self.channel is None: return - + await self.game.generate_targets(self.channel) async def _at_night_end(self, data=None): @@ -80,6 +80,9 @@ class VoteGroup: # Do what you voted on pass + async def _at_visit(self, data=None): + pass + async def register_players(self, *players): """ Extend players by passed list diff --git a/werewolf/votegroups/wolfvote.py b/werewolf/votegroups/wolfvote.py index 80bc2a8..f5d2b35 100644 --- a/werewolf/votegroups/wolfvote.py +++ b/werewolf/votegroups/wolfvote.py @@ -11,7 +11,7 @@ class WolfVote(VoteGroup): Werewolf implementation of base VoteGroup class """ - allignment = 2 # 1: Town, 2: Werewolf, 3: Neutral + alignment = 2 # 1: Town, 2: Werewolf, 3: Neutral channel_id = "werewolves" kill_messages = [ @@ -35,7 +35,8 @@ class WolfVote(VoteGroup): (self._at_hang, 0), (self._at_day_end, 0), (self._at_night_start, 2), - (self._at_night_end, 5) # Kill priority + (self._at_night_end, 5), # Kill priority + (self._at_visit, 0) ] # async def on_event(self, event, data):