You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
399 lines
13 KiB
399 lines
13 KiB
7 years ago
|
import asyncio
|
||
|
|
||
|
import discord
|
||
|
|
||
7 years ago
|
from datetime import datetime, timedelta
|
||
7 years ago
|
|
||
7 years ago
|
from random import shuffle
|
||
|
|
||
7 years ago
|
# from .builder import parse_code
|
||
|
|
||
7 years ago
|
|
||
|
class Game:
|
||
|
"""
|
||
7 years ago
|
Base class to run a single game of Werewolf
|
||
7 years ago
|
"""
|
||
7 years ago
|
|
||
|
default_secret_channel = {
|
||
|
"channel": None,
|
||
|
"players": [],
|
||
|
"votegroup": None
|
||
|
}
|
||
|
|
||
7 years ago
|
def __new__(cls, game_code):
|
||
7 years ago
|
game_code = ["DefaultWerewolf", "Villager", "Villager"]
|
||
7 years ago
|
return Game(game_code)
|
||
7 years ago
|
|
||
7 years ago
|
def __init__(self, guild, game_code):
|
||
|
self.guild = guild
|
||
7 years ago
|
self.game_code = game_code
|
||
7 years ago
|
|
||
7 years ago
|
self.roles = []
|
||
|
|
||
7 years ago
|
if self.game_code:
|
||
7 years ago
|
self.get_roles()
|
||
|
|
||
|
self.players = []
|
||
7 years ago
|
self.day_vote = {} # ID, votes
|
||
7 years ago
|
|
||
|
self.started = False
|
||
7 years ago
|
self.game_over = False
|
||
7 years ago
|
self.can_vote = False
|
||
7 years ago
|
self.used_votes = 0
|
||
7 years ago
|
|
||
7 years ago
|
self.channel_category = None
|
||
7 years ago
|
self.village_channel = None
|
||
7 years ago
|
|
||
|
self.p_channels = {}
|
||
|
self.vote_groups = {}
|
||
7 years ago
|
|
||
7 years ago
|
self.loop = asyncio.get_event_loop()
|
||
7 years ago
|
|
||
|
async def setup(self, ctx):
|
||
|
"""
|
||
|
Runs the initial setup
|
||
7 years ago
|
|
||
|
1. Assign Roles
|
||
|
2. Create Channels
|
||
|
2a. Channel Permissions :eyes:
|
||
|
3. Check Initial role setup (including alerts)
|
||
|
4. Start game
|
||
7 years ago
|
"""
|
||
7 years ago
|
if len(self.players) != self.roles:
|
||
7 years ago
|
ctx.send("Player count does not match role count, cannot start")
|
||
7 years ago
|
return False
|
||
7 years ago
|
# Create category and channel with individual overwrites
|
||
7 years ago
|
overwrite = {
|
||
7 years ago
|
self.guild.default_role: discord.PermissionOverwrite(read_messages=False, send_messages=True),
|
||
|
self.guild.me: discord.PermissionOverwrite(read_messages=True, send_messages=True)
|
||
7 years ago
|
}
|
||
|
|
||
|
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
|
||
7 years ago
|
await self._at_day_start() # This will queue channels and votegroups to be made
|
||
7 years ago
|
|
||
|
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)
|
||
|
|
||
7 years ago
|
channel = await self.guild.create_text_channel(channel_id, overwrites=overwrite, reason="Werewolf secret channel", category=self.channel_category)
|
||
7 years ago
|
|
||
|
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)
|
||
|
|
||
7 years ago
|
############START Notify structure############
|
||
7 years ago
|
async def _cycle(self):
|
||
|
"""
|
||
|
Each event calls the next event
|
||
|
|
||
|
|
||
7 years ago
|
|
||
7 years ago
|
_at_day_start()
|
||
7 years ago
|
_at_voted()
|
||
7 years ago
|
_at_kill()
|
||
|
_at_day_end()
|
||
|
_at_night_begin()
|
||
|
_at_night_end()
|
||
|
|
||
7 years ago
|
and repeat with _at_day_start() again
|
||
7 years ago
|
"""
|
||
7 years ago
|
await self._at_day_start()
|
||
7 years ago
|
|
||
7 years ago
|
async def _at_game_start(self): # ID 0
|
||
|
if self.game_over:
|
||
|
return
|
||
7 years ago
|
await self.village_channel.send("Game is starting, please wait for setup to complete")
|
||
7 years ago
|
|
||
7 years ago
|
await self._notify(0)
|
||
|
|
||
7 years ago
|
async def _at_day_start(self): # ID 1
|
||
|
if self.game_over:
|
||
|
return
|
||
7 years ago
|
await self.village_channel.send("The sun rises on a new day in the village")
|
||
|
|
||
|
await self.day_perms(self.village_channel)
|
||
7 years ago
|
await self._notify(1)
|
||
|
|
||
7 years ago
|
self.can_vote = True
|
||
|
|
||
7 years ago
|
asyncio.sleep(240) # 4 minute days
|
||
7 years ago
|
|
||
|
if not self.can_vote or self.game_over:
|
||
|
return
|
||
|
|
||
7 years ago
|
await self._at_day_end()
|
||
|
|
||
7 years ago
|
async def _at_voted(self, target): # ID 2
|
||
7 years ago
|
if self.game_over:
|
||
|
return
|
||
7 years ago
|
data = {"player": target}
|
||
|
await self._notify(2, data)
|
||
7 years ago
|
|
||
|
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)
|
||
|
|
||
7 years ago
|
if len(down_votes) > len(up_votes):
|
||
7 years ago
|
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)
|
||
|
|
||
7 years ago
|
if len(down_votes) > len(up_votes):
|
||
7 years ago
|
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()
|
||
7 years ago
|
|
||
|
async def _at_kill(self, target): # ID 3
|
||
|
if self.game_over:
|
||
|
return
|
||
7 years ago
|
data = {"player": target}
|
||
|
await self._notify(3, data)
|
||
7 years ago
|
|
||
7 years ago
|
async def _at_hang(self, target): # ID 4
|
||
|
if self.game_over:
|
||
|
return
|
||
7 years ago
|
data = {"player": target}
|
||
|
await self._notify(4, data)
|
||
7 years ago
|
|
||
|
async def _at_day_end(self): # ID 5
|
||
|
if self.game_over:
|
||
|
return
|
||
7 years ago
|
|
||
7 years ago
|
self.can_vote = False
|
||
7 years ago
|
await self.night_perms(self.village_channel)
|
||
|
|
||
|
await self.village_channel.send("**The sun sets on the village...**")
|
||
7 years ago
|
|
||
7 years ago
|
await self._notify(5)
|
||
7 years ago
|
asyncio.sleep(30)
|
||
7 years ago
|
await self._at_night_start()
|
||
|
|
||
7 years ago
|
async def _at_night_start(self): # ID 6
|
||
|
if self.game_over:
|
||
|
return
|
||
|
await self._notify(6)
|
||
|
|
||
7 years ago
|
asyncio.sleep(120) # 2 minutes
|
||
7 years ago
|
|
||
7 years ago
|
asyncio.sleep(90) # 1.5 minutes
|
||
7 years ago
|
|
||
7 years ago
|
asyncio.sleep(30) # .5 minutes
|
||
7 years ago
|
|
||
7 years ago
|
await self._at_night_end()
|
||
|
|
||
7 years ago
|
async def _at_night_end(self): # ID 7
|
||
|
if self.game_over:
|
||
|
return
|
||
|
await self._notify(7)
|
||
|
|
||
7 years ago
|
asyncio.sleep(15)
|
||
|
await self._at_day_start()
|
||
7 years ago
|
|
||
7 years ago
|
async def _notify(self, event, data=None):
|
||
7 years ago
|
for i in range(10):
|
||
|
tasks = []
|
||
7 years ago
|
# Role priorities
|
||
7 years ago
|
role_order = [role for role in self.roles if role.action_list[event][1]==i]
|
||
7 years ago
|
for role in role_order:
|
||
7 years ago
|
tasks.append(asyncio.ensure_future(role.on_event(event, data)))
|
||
7 years ago
|
# VoteGroup priorities
|
||
7 years ago
|
vote_order = [votes for votes in self.vote_groups.values() if votes.action_list[event][1]==i]
|
||
7 years ago
|
for vote_group in vote_order:
|
||
7 years ago
|
tasks.append(asyncio.ensure_future(vote_group.on_event(event, data)))
|
||
7 years ago
|
|
||
7 years ago
|
self.loop.run_until_complete(asyncio.gather(*tasks))
|
||
7 years ago
|
# Run same-priority task simultaneously
|
||
7 years ago
|
|
||
|
############END Notify structure############
|
||
|
|
||
7 years ago
|
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*"
|
||
7 years ago
|
embed.add_field(name="ID# **{}**".format(i), value="{} {}".format(status, player.member.display_name), inline=True)
|
||
7 years ago
|
|
||
|
return await channel.send(embed=embed)
|
||
7 years ago
|
|
||
|
|
||
|
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()
|
||
7 years ago
|
|
||
7 years ago
|
await asyncio.sleep(1)
|
||
7 years ago
|
|
||
7 years ago
|
self.p_channels[channel_id]["players"].append(player)
|
||
|
|
||
|
if votegroup:
|
||
|
self.p_channels[channel_id]["votegroup"] = votegroup
|
||
|
|
||
7 years ago
|
async def join(self, member: discord.Member, channel: discord.TextChannel):
|
||
7 years ago
|
"""
|
||
7 years ago
|
Have a member join a game
|
||
7 years ago
|
"""
|
||
|
if self.started:
|
||
7 years ago
|
await channel.send("**Game has already started!**")
|
||
|
return
|
||
7 years ago
|
|
||
|
if member in self.players:
|
||
7 years ago
|
await channel.send("{} is already in the game!".format(member.mention))
|
||
|
return
|
||
7 years ago
|
|
||
|
self.started.append(member)
|
||
|
|
||
7 years ago
|
channel.send("{} has been added to the game, total players is **{}**".format(member.mention, len(self.players)))
|
||
7 years ago
|
|
||
|
async def quit(self, member: discord.Member):
|
||
|
"""
|
||
|
Have a member quit a game
|
||
|
"""
|
||
|
player = await self.get_player_by_member(member)
|
||
|
|
||
|
if not player:
|
||
|
return "You're not in a game!"
|
||
7 years ago
|
|
||
7 years ago
|
if self.started:
|
||
|
await self.kill()
|
||
7 years ago
|
|
||
|
async def vote(self, author, id, channel):
|
||
|
"""
|
||
|
Member attempts to cast a vote (usually to lynch)
|
||
|
"""
|
||
|
player = self._get_player(author)
|
||
7 years ago
|
|
||
7 years ago
|
if player is None:
|
||
|
channel.send("You're not in this game!")
|
||
7 years ago
|
return
|
||
7 years ago
|
|
||
|
if not player.alive:
|
||
|
channel.send("Corpses can't vote")
|
||
7 years ago
|
return
|
||
|
|
||
|
if channel == self.village_channel:
|
||
|
if not self.can_vote:
|
||
|
channel.send("Voting is not allowed right now")
|
||
|
return
|
||
7 years ago
|
elif channel not in self.p_channels.values():
|
||
|
# Not part of the game
|
||
|
return # Don't say anything
|
||
|
|
||
7 years ago
|
try:
|
||
|
target = self.players[id]
|
||
7 years ago
|
except IndexError:
|
||
|
target = None
|
||
|
|
||
|
if target is None:
|
||
|
channel.send("Not a valid target")
|
||
|
return
|
||
7 years ago
|
|
||
|
# Now handle village vote or send to votegroup
|
||
7 years ago
|
|
||
7 years ago
|
async def kill(self, target, source=None, method: str=None):
|
||
|
"""
|
||
|
Attempt to kill a target
|
||
|
Source allows admin override
|
||
7 years ago
|
Be sure to remove permissions appropriately
|
||
7 years ago
|
Important to finish execution before triggering notify
|
||
|
"""
|
||
7 years ago
|
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
|
||
7 years ago
|
|
||
|
async def lynch(self, target):
|
||
|
"""
|
||
|
Attempt to lynch a target
|
||
|
Important to finish execution before triggering notify
|
||
7 years ago
|
"""
|
||
7 years ago
|
target.alive = False
|
||
|
await self._at_hang(target)
|
||
|
if not target.alive: # Still dead after notifying
|
||
|
await self.dead_perms(target.member)
|
||
7 years ago
|
|
||
7 years ago
|
async def get_roles(self, game_code=None):
|
||
|
if game_code:
|
||
|
self.game_code=game_code
|
||
7 years ago
|
|
||
7 years ago
|
if not self.game_code:
|
||
7 years ago
|
return False
|
||
|
|
||
7 years ago
|
self.roles = await parse_code(self.game_code)
|
||
7 years ago
|
|
||
|
if not self.roles:
|
||
7 years ago
|
return False
|
||
|
|
||
7 years ago
|
async def dead_perms(self, channel, member):
|
||
|
await channel.set_permissions(member, read_messages=True, send_message=False, add_reactions=False)
|
||
|
|
||
|
|
||
7 years ago
|
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)
|
||
7 years ago
|
else:
|
||
7 years ago
|
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)
|