-
Notifications
You must be signed in to change notification settings - Fork 0
/
discordBot.py
421 lines (345 loc) · 16.2 KB
/
discordBot.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
import discord
import asyncio
import pickle
import re
import time
from DotH import *
from CardBase import *
from CardDaybreak import *
from discord.ext import commands
from Game import Game
description = '''!help: display help message
To do in the server:
!new: start a new game
!start: start the game once everyone has joined
!end: end the current game
!status: display the status of the game
!join: join the current game
!leave: leave the current game
!role `card`: add a copy of `card` to the game
To do in PM:
!target `gameId` [`player1`] [`player2`]: use your role action from the game with id `gameId` on players 1 and 2. Leave the players blank if you don't/can't target multiple people
!vote `gameId` `player`: vote for `player` to die in the game with id `gameId`'''
bot = commands.Bot(command_prefix='!', description=description)
def save_game(game, server):
with open("games/"+server.id, 'wb') as f:
# Pickle the 'data' dictionary using the highest protocol available.
pickle.dump(game, f, pickle.HIGHEST_PROTOCOL)
def load_game(server):
try:
with open("games/"+server.id, 'rb') as f:
# The protocol version used is detected automatically, so we do not
# have to specify it.
return pickle.load(f)
except FileNotFoundError:
return None
@bot.event
@asyncio.coroutine
def on_ready():
print('Logged in as')
print(bot.user.name)
print(bot.user.id)
print('------')
@bot.command(pass_context=True)
@asyncio.coroutine
def new(ctx):
g = load_game(ctx.message.server)
if g is None:
g = Game([], [])
g.channel = ctx.message.channel
save_game(g, ctx.message.server)
yield from bot.say("New game created")
else:
yield from bot.say("Game already running")
@bot.command(pass_context=True)
@asyncio.coroutine
def end(ctx):
save_game(None, ctx.message.server)
yield from bot.say("Game ended")
@bot.command(pass_context=True)
@asyncio.coroutine
def status(ctx):
g = load_game(ctx.message.server)
if g is None:
yield from bot.say("No active game")
else:
yield from bot.say("There is a current game at stage " + str(g.stage) + " running with players " + ", ".join(map(lambda x: x.mention, g.players)) + " and roles " + ", ".join(map(lambda x: str(x), g.roles)))
yield from bot.say("The game's id is " + ctx.message.server.id)
@bot.command(pass_context=True)
@asyncio.coroutine
def join(ctx):
g = load_game(ctx.message.server)
if g is None:
yield from bot.say("There is no game to join")
elif g.stage == STAGE_BEFORE:
if ctx.message.author in g.players:
yield from bot.say("You are already in this game")
else:
g.add_players([ctx.message.author])
save_game(g, ctx.message.server)
yield from bot.say("Added " + ctx.message.author.mention + " to the current game")
else:
yield from bot.say("Sorry, the game has already started")
@bot.command(pass_context=True)
@asyncio.coroutine
def leave(ctx):
g = load_game(ctx.message.server)
if g is None:
yield from bot.say("There is no game to join")
elif g.stage == STAGE_BEFORE:
if ctx.message.author not in g.players:
yield from bot.say("You aren't in this game")
else:
g.remove_player(ctx.message.author)
save_game(g, ctx.message.server)
yield from bot.say("Removed " + ctx.message.author.mention + " from the current game")
else:
yield from bot.say("Sorry, the game has already started")
@bot.command(pass_context=True)
@asyncio.coroutine
def role(ctx, role: str):
g = load_game(ctx.message.server)
if g is None:
yield from bot.say("There is no game to add roles to")
else:
if role.lower() in role_conversions:
g.add_roles([role_conversions[role.lower()]])
save_game(g, ctx.message.server)
yield from bot.say("Added a " + role.lower() + " to the game")
@bot.command(pass_context=True)
@asyncio.coroutine
def target(ctx, server_id: str, *args):
# Find the server matching the id
server = bot.get_server(server_id)
if server:
g = load_game(server)
else:
g = None
if g is None or ctx.message.author not in g.players:
yield from bot.say("There is no game on that server or you are not in that game")
elif g.stage != STAGE_NIGHT:
yield from bot.say("It is not night time, you can't target anyone")
elif len(args) > 2:
yield from bot.say("Too many targets")
else:
# Check if the action is legal
players_to_use = []
valid = True
for arg in args:
found = False
if arg in centre_cards:
players_to_use.append(arg)
found = True
for player in g.players:
if player.name == arg:
players_to_use.append(player)
found = True
break
if not found:
valid = False
if not valid:
yield from bot.say("At least one of those targets was not a player in this game")
else:
if len(players_to_use) < 2:
arg2 = None
else:
arg2 = players_to_use[1]
if len(players_to_use) < 1:
arg1 = None
else:
arg1 = players_to_use[0]
try:
g.add_action(ctx.message.author, arg1, arg2)
save_game(g, server)
yield from bot.say("Targetting successful")
except IsNotLegalError:
yield from bot.say("That action is not legal, try again")
if g.actions_for_all():
g.do_actions()
save_game(g, server)
yield from bot.send_message(g.channel, "Game has progressed to day")
yield from bot.send_message(g.channel, "Send in votes via person message whenever you want to")
yield from bot.send_message(g.channel, "vote with `!vote {} <player>`".format(server_id))
for player in g.players:
yield from bot.send_message(player, g.print_player_action(player))
@bot.command(pass_context=True)
@asyncio.coroutine
def vote(ctx, server_id: str, votee: str):
# Find the server matching the id
server = bot.get_server(server_id)
if server:
g = load_game(server)
else:
g = None
if g is None or ctx.message.author not in g.players:
yield from bot.say("There is no game on that server or you are not in that game")
elif g.stage != STAGE_DAY:
yield from bot.say("It is not day time, you can't vote")
else:
# Check if the action is legal
player_to_use = None
found = False
if votee in centre_cards:
player_to_use = votee
found = True
for player in g.players:
if player.name == votee:
player_to_use = player
found = True
break
if not found:
yield from bot.say("That person is not part of the game")
else:
try:
g.add_vote(ctx.message.author, player_to_use)
save_game(g, server)
yield from bot.say("Vote successful")
except SelfVoteError:
yield from bot.say("You can't vote for yourself, try again")
except InvalidPlayerVoteError:
yield from bot.say("Invalid vote, try again")
if g.votes_for_all():
killed = g.count_votes()
winning_teams = []
if not killed:
# Circle vote, if a werewolf is alive then werewolves win otherwise villagers win
if g.team_in_play(Team.Werewolf):
yield from bot.send_message(g.channel, "After all was said and done, the villagers were far too conflicted to kill anyone. What a shame "
"too, as there was still a werewolf.")
winning_teams = [Team.Werewolf]
else:
yield from bot.send_message(g.channel, "Eventually, the villagers realised there was not a werewolf in their midst and did not kill anyone.")
winning_teams = [Team.Villager]
else:
total_killed = []
also_killed = []
dying_teams = []
for player in killed:
role = g.matchup[player]
if role.death_team == Team.Hunter:
also_killed.append(g.votes[player])
if role.death_team not in dying_teams:
dying_teams.append(role.death_team)
total_killed.append(player)
killed = copy.deepcopy(also_killed)
# Find all of the teams that died, repeat if more die due to hunter
while killed:
also_killed = []
for player in killed:
role = g.matchup[player]
if role.death_team == Team.Hunter:
# Kill whoever the hunter voted for
also_killed.append(g.votes[player])
if role.death_team not in dying_teams:
# Add all of the teams that died, mainly just for werewolf but also tanner
# (currently used for whether Tanner wins while there are no roles that can create multiple tanners,
# will later be used for apprentice tanner)
dying_teams.append(role.death_team)
total_killed.append(player)
killed = copy.deepcopy(also_killed)
if Team.Werewolf in dying_teams:
# Villagers win as they killed a werewolf
winning_teams = [Team.Villager]
yield from bot.send_message(g.channel, ("After some reasoned arguments, the villagers finally chose to kill {}. As the blood spurted, a "
"deathly howl was heard. It appeared the villagers had chosen "
"wisely.").format(" and ".join(map(lambda x: x.mention, total_killed))))
else:
winning_teams = [Team.Werewolf]
yield from bot.send_message(g.channel, "The villagers finally decided on who must be the werewolf. They prepared the nooses and hung {}. "
"Unfortunately, the villagers were not the best at judging character and did not get a werewolf. "
"Looks like it's feeding time...".format(" and ".join(map(lambda x: x.mention, total_killed))))
if Team.Tanner in dying_teams:
# Tanner wins. This does not affect the villagers
if Team.Werewolf in winning_teams:
# Werewolves lose because they must not let the tanner die
winning_teams.remove(Team.Werewolf)
yield from bot.send_message(g.channel, "However, a horrible stench filled the air. It turned out that the Tanner had been hung. This "
"sent the werewolves into a blind raging, leading them to decimate the whole town but also "
"ruining their appetite. Looks like everyone was unfortunate, except for the tanner.")
else:
yield from bot.send_message(g.channel, "But along with the werewolf, the villagers had also hung up the Tanner. The villagers moved as "
"fast as possible to bury the Tanner. And thus the villagers survived, with little more problems "
"than an irritated nose. And for once in their life the Tanner achieved what they wanted. Looks "
"like all the humans live happily ever after...")
winners = []
losers = []
# Print out the end of game summary with everyone's current roles
for player, role in g.matchup.items():
if player in g.players:
if role.win_team == Team.Tanner:
# Tanner only wins if they die
if player in total_killed:
winners.append(player.mention + " " + str(role))
else:
losers.append(player.mention + " " + str(role))
elif role.win_team in winning_teams:
winners.append(player.mention+" "+str(role))
else:
losers.append(player.mention+" "+str(role))
# Show who won and lost
yield from bot.send_message(g.channel, "The winners were: {}".format(", ".join(winners)))
yield from bot.send_message(g.channel, "The losers were: {}".format(", ".join(losers)))
# Print out everyone's original roles
origins = []
for player, role in g.original.items():
if isinstance(player, str):
origins.append(player+" "+str(role))
else:
origins.append(player.mention+" "+str(role))
yield from bot.send_message(g.channel, "The original roles were: {}".format(", ".join(origins)))
@bot.command(pass_context=True)
@asyncio.coroutine
def start(ctx):
g = load_game(ctx.message.server)
if g is None:
yield from bot.say("There is no game to start")
elif g.stage != STAGE_BEFORE:
yield from bot.say("Game has already started")
else:
try:
g.assign()
yield from bot.say("Game started, check your PMs")
save_game(g, ctx.message.server)
for player in g.players:
yield from bot.send_message(player, g.player_role_text(player))
yield from bot.send_message(player, "Do your action with `!target {} <player1> <player2>` (without the anglebrackets)".format(ctx.message.server.id))
yield from bot.send_message(player, "You can target the centre cards as 'left', 'right' and 'centre' and you should leave players blank if your role tells you to target fewer than 2 people")
yield from bot.send_message(player, "the players are {}".format(", ".join(map(lambda x: x.name, g.players))))
except ValueError:
yield from bot.say("There aren't the right number of roles for players (there should be players+3 roles)")
@bot.command(pass_context=True)
@asyncio.coroutine
def clear(ctx):
while True:
yield from bot.change_nickname(ctx.message.author, None)
print("{} cleared".format(ctx.message.author))
time.sleep(0.5)
@bot.command(pass_context=True)
@asyncio.coroutine
def away(ctx):
server = ctx.message.server
# Find afk role
desired_role = None
for role in server.roles:
if "afk" in role.name.lower():
desired_role = role
break
while True:
for user in server.members:
if user.status != discord.Status.online and user.status != discord.Status.offline:
try:
yield from bot.add_roles(user, desired_role)
except discord.errors.Forbidden:
print("Cannot change {}".format(user))
else:
try:
yield from bot.remove_roles(user, desired_role)
except discord.errors.Forbidden:
print("Cannot change {}".format(user))
print("Set AFK Status")
yield from asyncio.sleep(60)
@bot.command()
@asyncio.coroutine
def self_awareness():
yield from bot.say("It's alive!")
bot.run(open("secret.txt", "r").read().strip())