diff --git a/cah/cah.py b/cah/cah.py new file mode 100644 index 0000000..88a7ad0 --- /dev/null +++ b/cah/cah.py @@ -0,0 +1,1690 @@ +import asyncio +import discord +import re +import os +import random +import string +import json +import time +import html +import codecs +from random import shuffle +from discord.ext import commands + + +class CardsAgainstHumanity: + + # Init with the bot reference, and a reference to the deck file + def __init__(self, bot, file = None): + self.bot = bot + self.games = [] + self.maxBots = 5 # Max number of bots that can be added to a game - don't count toward max players + self.maxPlayers = 10 # Max players for ranjom joins + self.maxDeadTime = 3600 # Allow an hour of dead time before killing a game + self.checkTime = 300 # 5 minutes between dead time checks + self.winAfter = 10 # 10 wins for the game + self.botWaitMin = 5 # Minimum number of seconds before the bot makes a decision (default 5) + self.botWaitMax = 30 # Max number of seconds before a bot makes a decision (default 30) + self.userTimeout = 300 # 5 minutes to timeout + self.utCheck = 30 # Check timeout every 30 seconds + self.utWarn = 60 # Warn the user if they have 60 seconds or less before being kicked + self.charset = "1234567890" + self.botName = 'Rando Cardrissian' + self.minMembers = 3 + self.loopsleep = 0.05 + if file == None: + file = "/data/cah/deck.json" + # Let's load our deck file + # Can be found at http://www.crhallberg.com/cah/json + if os.path.exists(file): + f = open(file,'r') + filedata = f.read() + f.close() + + self.deck = json.loads(filedata) + else: + # File doesn't exist - create a placeholder + self.deck = {} + self.bot.loop.create_task(self.checkDead()) + self.bot.loop.create_task(self.checkUserTimeout()) + + def cleanJson(self, json): + json = html.unescape(json) + # Clean out html formatting + json = json.replace('_','[blank]') + json = json.replace('
','\n') + json = json.replace('
','\n') + json = json.replace('', '*') + json = json.replace('', '*') + return json + + def displayname(self, member : discord.Member): + # A helper function to return the member's display name + nick = name = None + try: + nick = member.nick + except AttributeError: + pass + try: + name = member.name + except AttributeError: + pass + if nick: + return nick + if name: + return name + return None + + def memberforname(self, name, server): + # Check nick first - then name + for member in server.members: + if member.nick: + if member.nick.lower() == name.lower(): + return member + for member in server.members: + if member.name.lower() == name.lower(): + return member + # No member yet - try ID + memID = ''.join(list(filter(str.isdigit, name))) + newMem = memberForID(memID, server) + if newMem: + return newMem + return None + + def getreadabletimebetween(first, last): + # A helper function to make a readable string between two times + timeBetween = int(last-first) + weeks = int(timeBetween/604800) + days = int((timeBetween-(weeks*604800))/86400) + hours = int((timeBetween-(days*86400 + weeks*604800))/3600) + minutes = int((timeBetween-(hours*3600 + days*86400 + weeks*604800))/60) + seconds = int(timeBetween-(minutes*60 + hours*3600 + days*86400 + weeks*604800)) + msg = "" + + if weeks > 0: + if weeks == 1: + msg = '{}{} week, '.format(msg, str(weeks)) + else: + msg = '{}{} weeks, '.format(msg, str(weeks)) + if days > 0: + if days == 1: + msg = '{}{} day, '.format(msg, str(days)) + else: + msg = '{}{} days, '.format(msg, str(days)) + if hours > 0: + if hours == 1: + msg = '{}{} hour, '.format(msg, str(hours)) + else: + msg = '{}{} hours, '.format(msg, str(hours)) + if minutes > 0: + if minutes == 1: + msg = '{}{} minute, '.format(msg, str(minutes)) + else: + msg = '{}{} minutes, '.format(msg, str(minutes)) + if seconds > 0: + if seconds == 1: + msg = '{}{} second, '.format(msg, str(seconds)) + else: + msg = '{}{} seconds, '.format(msg, str(seconds)) + + if not msg: + return "0 seconds" + else: + return msg[:-2] + + async def checkUserTimeout(self): + while not self.bot.is_closed: + # Wait first - then check + await asyncio.sleep(self.utCheck) + for game in self.games: + if not game['Timeout']: + continue + if len(game['Members']) >= self.minMembers: + # Game is started + for member in game['Members']: + if member['IsBot']: + continue + if game['Judging']: + if not member == game['Members'][game['Judge']]: + # Not the judge - don't hold against the user + member['Time'] = int(time.time()) + continue + else: + # Not judging + if member == game['Members'][game['Judge']]: + # The judge - don't hold that against them + member['Time'] = int(time.time()) + continue + currentTime = int(time.time()) + userTime = member['Time'] + downTime = currentTime - userTime + # Check if downTime results in a kick + if downTime >= self.userTimeout: + # You gettin kicked, son. + await self.removeMember(member['User']) + self.checkGame(game) + continue + # Check if downTime is in warning time + if downTime >= (self.userTimeout - self.utWarn): + # Check if we're at warning phase + if self.userTimeout - downTime >= (self.utWarn - self.utCheck): + kickTime = self.userTimeout - downTime + if kickTime % self.utCheck: + # Kick time isn't exact time - round out to the next loop + kickTime = kickTime-(kickTime % self.utCheck)+self.utCheck + # Warning time! + timeString = self.getreadabletimebetween(0, kickTime) + msg = '**WARNING** - You will be kicked from the game if you do not make a move in *{}!*'.format(timeString) + await self.bot.send_message(member['User'], msg) + else: + for member in game['Members']: + # Reset timer + member['Time'] = int(time.time()) + + + async def checkDead(self): + while not self.bot.is_closed: + # Wait first - then check + await asyncio.sleep(self.checkTime) + for game in self.games: + gameTime = game['Time'] + currentTime = int(time.time()) + timeRemain = currentTime - gameTime + if timeRemain > self.maxDeadTime: + # Game is dead - quit it and alert members + for member in game['Members']: + if member['IsBot']: + # Clear pending tasks and set to None + if not member['Task'] == None: + task = member['Task'] + if not task.done(): + task.cancel() + member['Task'] = None + continue + msg = "Game id: *{}* has been closed due to inactivity.".format(game['ID']) + await self.bot.send_message(member['User'], msg) + + # Set running to false + game['Running'] = False + self.games.remove(game) + + async def checkPM(self, message): + # Checks if we're talking in PM, and if not - outputs an error + if message.channel.is_private: + # PM + return True + else: + # Not in PM + await self.bot.send_message(message.channel, 'Cards Against Humanity commands must be run in PM.') + return False + + + def randomID(self, length = 8): + # Create a random id that doesn't already exist + while True: + # Repeat until found + newID = ''.join(random.choice(self.charset) for i in range(length)) + exists = False + for game in self.games: + if game['ID'] == newID: + exists = True + break + if not exists: + break + return newID + + def randomBotID(self, game, length = 4): + # Returns a random id for a bot that doesn't already exist + while True: + # Repeat until found + newID = ''.join(random.choice(self.charset) for i in range(length)) + exists = False + for member in game['Members']: + if member['ID'] == newID: + exists = True + break + if not exists: + break + return newID + + def userGame(self, user): + # Returns the game the user is currently in + if not type(user) is str: + # Assume it's a discord.Member/User + user = user.id + + for game in self.games: + for member in game['Members']: + if member['ID'] == user: + # Found our user + return game + return None + + def gameForID(self, id): + # Returns the game with the passed id + for game in self.games: + if game['ID'] == id: + return game + return None + + async def removeMember(self, user, game = None): + if not type(user) is str: + # Assume it's a discord.Member/User + user = user.id + outcome = False + removed = None + if not game: + game = self.userGame(user) + if game: + for member in game['Members']: + if member['ID'] == user: + removed = member + outcome = True + judgeChanged = False + # Reset judging flag to retrigger actions + game['Judging'] = False + # Get current Judge - only if game has started + if len(game['Members']) >= self.minMembers: + judge = game['Members'][game['Judge']] + game['Members'].remove(member) + # Check if we're removing the current judge + if judge == member: + # Judge will change + judgeChanged = True + # Find out if our member was the last in line + if game['Judge'] >= len(game['Members']): + game['Judge'] = 0 + # Reset judge var + judge = game['Members'][game['Judge']] + else: + # Judge didn't change - so let's reset judge index + index = game['Members'].index(judge) + game['Judge'] = index + else: + judge = None + # Just remove the member + game['Members'].remove(member) + + if member['Creator']: + # We're losing the game creator - pick a new one + for newCreator in game['Members']: + if not newCreator['IsBot']: + newCreator['Creator'] = True + await self.bot.send_message(newCreator['User'], 'The creator of this game left. **YOU** are now the creator.') + break + + # Remove submissions + for sub in game['Submitted']: + # Remove deleted member and new judge's submissions + if sub['By'] == member or sub['By'] == judge: + # Found it! + game['Submitted'].remove(sub) + break + if member['IsBot']: + if not member['Task'] == None: + task = member['Task'] + if not task.done(): + task.cancel() + member['Task'] = None + else: + msg = '**You were removed from game id:** ***{}.***'.format(game['ID']) + await self.bot.send_message(member['User'], msg) + # Removed, no need to finish the loop + break + if not outcome: + return outcome + # We removed someone - let's tell the world + for member in game['Members']: + if member['IsBot']: + continue + if removed['IsBot']: + msg = '***{} ({})*** **left the game - reorganizing...**'.format(self.botName, removed['ID']) + else: + msg = '***{}*** **left the game - reorganizing...**'.format(self.displayname(removed['User'])) + # Check if the judge changed + if judgeChanged: + # Judge changed + newJudge = game['Members'][game['Judge']] + if newJudge['IsBot']: + msg += '\n\n***{} ({})*** **is now judging!**'.format(self.botName, newJudge['ID']) + # Schedule judging task + else: + if newJudge == member: + msg += '\n\n***YOU*** **are now judging!**' + else: + msg += '\n\n***{}*** **is now judging!**'.format(self.displayname(newJudge['User'])) + await self.bot.send_message(member['User'], msg) + return game + + + def checkGame(self, game): + for member in game['Members']: + if not member['IsBot']: + return True + # If we got here - only bots, or empty game + # Kill all bots' loops + for member in game['Members']: + if member['IsBot']: + # Clear pending tasks and set to None + if not member['Task'] == None: + task = member['Task'] + if not task.done(): + task.cancel() + member['Task'] = None + # Set running to false + game['Running'] = False + self.games.remove(game) + return False + + async def typing(self, game, typeTime = 5): + # Allows us to show the bot typing + waitTime = random.randint(self.botWaitMin, self.botWaitMax) + preType = waitTime-typeTime + if preType > 0: + await asyncio.sleep(preType) + for member in game['Members']: + if member['IsBot']: + continue + # Show that we're typing + await self.bot.send_typing(member['User']) + await asyncio.sleep(self.loopsleep) + await asyncio.sleep(typeTime) + else: + for member in game['Members']: + if member['IsBot']: + continue + # Show that we're typing + await self.bot.send_typing(member['User']) + await asyncio.sleep(self.loopsleep) + await asyncio.sleep(waitTime) + + async def botPick(self, ctx, bot, game): + # Has the bot pick their card + blackNum = game['BlackCard']['Pick'] + if blackNum == 1: + cardSpeak = 'card' + else: + cardSpeak = 'cards' + i = 0 + cards = [] + while i < blackNum: + randCard = random.randint(0, len(bot['Hand'])-1) + cards.append(bot['Hand'].pop(randCard)['Text']) + i += 1 + + await self.typing(game) + + # Make sure we haven't laid any cards + if bot['Laid'] == False and game['Judging'] == False: + newSubmission = { 'By': bot, 'Cards': cards } + game['Submitted'].append(newSubmission) + # Shuffle cards + shuffle(game['Submitted']) + bot['Laid'] = True + game['Time'] = currentTime = int(time.time()) + await self.checkSubmissions(ctx, game, bot) + + + async def botPickWin(self, ctx, game): + totalUsers = len(game['Members'])-1 + submitted = len(game['Submitted']) + if submitted >= totalUsers: + # Judge is a bot - and all cards are in! + await self.typing(game) + # Pick a winner + winner = random.randint(0, totalUsers-1) + await self.winningCard(ctx, game, winner) + + + async def checkSubmissions(self, ctx, game, user = None): + totalUsers = len(game['Members'])-1 + submitted = len(game['Submitted']) + for member in game['Members']: + msg = '' + # Is the game running? + if len(game['Members']) < self.minMembers: + if member['IsBot']: + # Clear pending tasks and set to None + if not member['Task'] == None: + task = member['Task'] + if not task.done(): + # Task isn't finished - we're on a new hand, cancel it + task.cancel() + member['Task'] = None + continue + # not enough members - send the embed + stat_embed = discord.Embed(color=discord.Color.red()) + stat_embed.set_author(name='Not enough players to continue! ({}/{})'.format(len(game['Members']), self.minMembers)) + stat_embed.set_footer(text='Have other users join with: {}joincah {}'.format(ctx.prefix, game['ID'])) + await self.bot.send_message(member['User'], embed=stat_embed) + continue + if member['IsBot'] == True: + continue + # Check if we have a user + if user: + blackNum = game['BlackCard']['Pick'] + if blackNum == 1: + card = 'card' + else: + card = 'cards' + if user['IsBot']: + msg = '*{} ({})* submitted their {}! '.format(self.botName, user['ID'], card) + else: + if not member == user: + # Don't say this to the submitting user + msg = '*{}* submitted their {}! '.format(self.displayname(user['User']), card) + if submitted < totalUsers: + msg += '{}/{} cards submitted...'.format(submitted, totalUsers) + if len(msg): + # We have something to say + await self.bot.send_message(member['User'], msg) + await asyncio.sleep(self.loopsleep) + + + async def checkCards(self, ctx, game): + while not self.bot.is_closed: + if not game['Running']: + break + # wait for 1 second + await asyncio.sleep(1) + # Check for all cards + if len(game['Members']) < self.minMembers: + # Not enough members + continue + # Enough members - let's check if we're judging + if game['Judging']: + continue + # Enough members, and not judging - let's check cards + totalUsers = len(game['Members'])-1 + submitted = len(game['Submitted']) + if submitted >= totalUsers: + game['Judging'] = True + # We have enough cards + for member in game['Members']: + if member['IsBot']: + continue + msg = 'All cards have been submitted!' + # if + await self.bot.send_message(member['User'], msg) + await self.showOptions(ctx, member['User']) + await asyncio.sleep(self.loopsleep) + + # Check if a bot is the judge + judge = game['Members'][game['Judge']] + if not judge['IsBot']: + continue + # task = self.bot.loop.create_task(self.botPickWin(ctx, game)) + task = asyncio.ensure_future(self.botPickWin(ctx, game)) + judge['Task'] = task + + + + async def winningCard(self, ctx, game, card): + # Let's pick our card and alert everyone + winner = game['Submitted'][card] + if winner['By']['IsBot']: + winnerName = '{} ({})'.format(self.botName, winner['By']['ID']) + winner['By']['Points'] += 1 + winner['By']['Won'].append(game['BlackCard']['Text']) + else: + winnerName = self.displayname(winner['By']['User']) + for member in game['Members']: + if member['IsBot']: + continue + stat_embed = discord.Embed(color=discord.Color.gold()) + stat_embed.set_footer(text='Cards Against Humanity - id: {}'.format(game['ID'])) + index = game['Members'].index(member) + if index == game['Judge']: + stat_embed.set_author(name='You picked {}\'s card!'.format(winnerName)) + elif member == winner['By']: + stat_embed.set_author(name='YOU WON!!') + member['Points'] += 1 + member['Won'].append(game['BlackCard']['Text']) + else: + stat_embed.set_author(name='{} won!'.format(winnerName)) + if len(winner['Cards']) == 1: + msg = 'The **Winning** card was:\n\n{}'.format('{}'.format(' - '.join(winner['Cards']))) + else: + msg = 'The **Winning** cards were:\n\n{}'.format('{}'.format(' - '.join(winner['Cards']))) + await self.bot.send_message(member['User'], embed=stat_embed) + await self.bot.send_message(member['User'], msg) + await asyncio.sleep(self.loopsleep) + + # await self.nextPlay(ctx, game) + + # Start the game loop + event = game['NextHand'] + self.bot.loop.call_soon_threadsafe(event.set) + game['Time'] = currentTime = int(time.time()) + + async def gameCheckLoop(self, ctx, game): + task = game['NextHand'] + while True: + if not game['Running']: + break + # Clear the pending task + task.clear() + # Queue up the next hand + await self.nextPlay(ctx, game) + # Wait until our next clear + await task.wait() + + async def messagePlayers(self, ctx, message, game, judge = False): + # Messages all the users on in a game + for member in game['Members']: + if member['IsBot']: + continue + # Not bots + if member is game['Members'][game['Judge']]: + # Is the judge + if judge: + await self.bot.send_message(member['User'], message) + else: + # Not the judge + await self.bot.send_message(member['User'], message) + + ################################################ + + async def showPlay(self, ctx, user): + # Creates an embed and displays the current game stats + stat_embed = discord.Embed(color=discord.Color.blue()) + game = self.userGame(user) + if not game: + return + # Get the judge's name + if game['Members'][game['Judge']]['User'] == user: + judge = '**YOU** are' + else: + if game['Members'][game['Judge']]['IsBot']: + # Bot + judge = '*{} ({})* is'.format(self.botName, game['Members'][game['Judge']]['ID']) + else: + judge = '*{}* is'.format(self.displayname(game['Members'][game['Judge']]['User'])) + + # Get the Black Card + try: + blackCard = game['BlackCard']['Text'] + blackNum = game['BlackCard']['Pick'] + except Exception: + blackCard = 'None.' + blackNum = 0 + + msg = '{} the judge.\n\n'.format(judge) + msg += '__Black Card:__\n\n**{}**\n\n'.format(blackCard) + + totalUsers = len(game['Members'])-1 + submitted = len(game['Submitted']) + if len(game['Members']) >= self.minMembers: + if submitted < totalUsers: + msg += '{}/{} cards submitted...'.format(submitted, totalUsers) + else: + msg += 'All cards have been submitted!' + await self.showOptions(ctx, user) + return + if not judge == '**YOU** are': + # Judge doesn't need to lay a card + if blackNum == 1: + # Singular + msg += '\n\nLay a card with `{}lay [card number]`'.format(ctx.prefix) + elif blackNum > 1: + # Plural + msg += '\n\nLay **{} cards** with `{}lay [card numbers separated by commas (1,2,3)]`'.format(blackNum, ctx.prefix) + + stat_embed.set_author(name='Current Play') + stat_embed.set_footer(text='Cards Against Humanity - id: {}'.format(game['ID'])) + await self.bot.send_message(user, embed=stat_embed) + await self.bot.send_message(user, msg) + + async def showHand(self, ctx, user): + # Shows the user's hand in an embed + stat_embed = discord.Embed(color=discord.Color.green()) + game = self.userGame(user) + if not game: + return + i = 0 + msg = '' + points = '? points' + for member in game['Members']: + if member['ID'] == user.id: + # Got our user + if member['Points']==1: + points = '1 point' + else: + points = '{} points'.format(member['Points']) + for card in member['Hand']: + i += 1 + msg += '{}. {}\n'.format(i, card['Text']) + + try: + blackCard = '**{}**'.format(game['BlackCard']['Text']) + except Exception: + blackCard = '**None.**' + stat_embed.set_author(name='Your Hand - {}'.format(points)) + stat_embed.set_footer(text='Cards Against Humanity - id: {}'.format(game['ID'])) + await self.bot.send_message(user, embed=stat_embed) + await self.bot.send_message(user, msg) + + async def showOptions(self, ctx, user): + # Shows the judgement options + stat_embed = discord.Embed(color=discord.Color.orange()) + game = self.userGame(user) + if not game: + return + # Add title + stat_embed.set_author(name='JUDGEMENT TIME!!') + stat_embed.set_footer(text='Cards Against Humanity - id: {}'.format(game['ID'])) + await self.bot.send_message(user, embed=stat_embed) + + if game['Members'][game['Judge']]['User'] == user: + judge = '**YOU** are' + else: + if game['Members'][game['Judge']]['IsBot']: + # Bot + judge = '*{} ({})* is'.format(self.botName, game['Members'][game['Judge']]['ID']) + else: + judge = '*{}* is'.format(self.displayname(game['Members'][game['Judge']]['User'])) + blackCard = game['BlackCard']['Text'] + + msg = '{} judging.\n\n'.format(judge) + msg += '__Black Card:__\n\n**{}**\n\n'.format(blackCard) + msg += '__Submitted White Cards:__\n\n' + + i = 0 + for sub in game['Submitted']: + i+=1 + msg += '{}. {}\n'.format(i, ' - '.join(sub['Cards'])) + if judge == '**YOU** are': + msg += '\nPick a winner with `{}pick [submission number]`.'.format(ctx.prefix) + await self.bot.send_message(user, msg) + + async def drawCard(self, game): + # Draws a random unused card and shuffles the deck if needed + totalDiscard = len(game['Discard']) + for member in game['Members']: + totalDiscard += len(member['Hand']) + if totalDiscard >= len(self.deck['whiteCards']): + # Tell everyone the cards were shuffled + for member in game['Members']: + if member['IsBot']: + continue + user = member['User'] + await self.bot.send_message(user, 'Shuffling white cards...') + # Shuffle the cards + self.shuffle(game) + while True: + # Random grab a unique card + index = random.randint(0, len(self.deck['whiteCards'])-1) + if not index in game['Discard']: + game['Discard'].append(index) + text = self.deck['whiteCards'][index] + text = self.cleanJson(text) + card = { 'Index': index, 'Text': text } + return card + + + def shuffle(self, game): + # Adds discards back into the deck + game['Discard'] = [] + for member in game['Members']: + for card in member['Hand']: + game['Discard'].append(card['Index']) + + + async def drawCards(self, user, cards = 10): + if not type(user) is str: + # Assume it's a discord.Member/User + user = user.id + # fills the user's hand up to number of cards + game = self.userGame(user) + for member in game['Members']: + if member['ID'] == user: + # Found our user - let's draw cards + i = len(member['Hand']) + while i < cards: + # Draw unique cards until we fill our hand + newCard = await self.drawCard(game) + member['Hand'].append(newCard) + i += 1 + + + async def drawBCard(self, game): + # Draws a random black card + totalDiscard = len(game['BDiscard']) + if totalDiscard >= len(self.deck['blackCards']): + # Tell everyone the cards were shuffled + for member in game['Members']: + if member['IsBot']: + continue + user = member['User'] + await self.bot.send_message(user, 'Shuffling black cards...') + # Shuffle the cards + game['BDiscard'] = [] + while True: + # Random grab a unique card + index = random.randint(0, len(self.deck['blackCards'])-1) + if not index in game['BDiscard']: + game['BDiscard'].append(index) + text = self.deck['blackCards'][index]['text'] + text = self.cleanJson(text) + game['BlackCard'] = { 'Text': text, 'Pick': self.deck['blackCards'][index]['pick'] } + return game['BlackCard'] + + + async def nextPlay(self, ctx, game): + # Advances the game + if len(game['Members']) < self.minMembers: + stat_embed = discord.Embed(color=discord.Color.red()) + stat_embed.set_author(name='Not enough players to continue! ({}/{})'.format(len(game['Members']), self.minMembers)) + stat_embed.set_footer(text='Have other users join with: {}joincah {}'.format(ctx.prefix, game['ID'])) + for member in game['Members']: + if member['IsBot']: + continue + await self.bot.send_message(member['User'], embed=stat_embed) + return + + # Find if we have a winner + winner = False + stat_embed = discord.Embed(color=discord.Color.lighter_grey()) + for member in game['Members']: + if member['IsBot']: + # Clear pending tasks and set to None + if not member['Task'] == None: + task = member['Task'] + if not task.done(): + # Task isn't finished - we're on a new hand, cancel it + task.cancel() + member['Task'] = None + if member['Points'] >= self.winAfter: + # We have a winner! + winner = True + if member['IsBot']: + stat_embed.set_author(name='{} ({}) is the WINNER!!'.format(self.botName, member['ID'])) + else: + stat_embed.set_author(name='{} is the WINNER!!'.format(self.displayname(member['User']))) + stat_embed.set_footer(text='Congratulations!'.format(game['ID'])) + break + if winner: + for member in game['Members']: + if not member['IsBot']: + await self.bot.send_message(member['User'], embed=stat_embed) + # Reset all users + member['Hand'] = [] + member['Points'] = 0 + member['Won'] = [] + member['Laid'] = False + member['Refreshed'] = False + await asyncio.sleep(self.loopsleep) + + game['Judging'] = False + # Clear submitted cards + game['Submitted'] = [] + # We have enough members + if game['Judge'] == -1: + # First game - randomize judge + game['Judge'] = random.randint(0, len(game['Members'])-1) + else: + game['Judge']+=1 + # Reset the judge if out of bounds + if game['Judge'] >= len(game['Members']): + game['Judge'] = 0 + + # Draw the next black card + bCard = await self.drawBCard(game) + + # Draw cards + for member in game['Members']: + member['Laid'] = False + await self.drawCards(member['ID']) + + # Show hands + for member in game['Members']: + if member['IsBot']: + continue + await self.showPlay(ctx, member['User']) + index = game['Members'].index(member) + if not index == game['Judge']: + await self.showHand(ctx, member['User']) + await asyncio.sleep(self.loopsleep) + + # Have the bots lay their cards + for member in game['Members']: + if not member['IsBot']: + continue + if member['ID'] == game['Members'][game['Judge']]['ID']: + continue + # Not a human player, and not the judge + # task = self.bot.loop.create_task(self.botPick(ctx, member, game))\ + task = asyncio.ensure_future(self.botPick(ctx, member, game)) + member['Task'] = task + # await self.botPick(ctx, member, game) + + + @commands.command(pass_context=True) + async def game(self, ctx, *, message = None): + """Displays the game's current status.""" + if not await self.checkPM(ctx.message): + return + userGame = self.userGame(ctx.message.author) + if not userGame: + msg = "You're not in a game - you can create one with `{}newcah` or join one with `{}joincah`.".format(ctx.prefix, ctx.prefix) + await self.bot.send_message(ctx.message.author, msg) + return + await self.showPlay(ctx, ctx.message.author) + + + @commands.command(pass_context=True) + async def say(self, ctx, *, message = None): + """Broadcasts a message to the other players in your game.""" + if not await self.checkPM(ctx.message): + return + userGame = self.userGame(ctx.message.author) + if not userGame: + msg = "You're not in a game - you can create one with `{}newcah` or join one with `{}joincah`.".format(ctx.prefix, ctx.prefix) + await self.bot.send_message(ctx.message.author, msg) + return + userGame['Time'] = int(time.time()) + if message == None: + msg = "Ooookay, you say *nothing...*" + await self.bot.send_message(ctx.message.author, msg) + return + msg = '*{}* says: {}'.format(ctx.message.author.name, message) + for member in userGame['Members']: + if member['IsBot']: + continue + # Tell them all!! + if not member['User'] == ctx.message.author: + # Don't tell yourself + await self.bot.send_message(member['User'], msg) + else: + # Update member's time + member['Time'] = int(time.time()) + await self.bot.send_message(ctx.message.author, 'Message sent!') + + + @commands.command(pass_context=True) + async def lay(self, ctx, *, card = None): + """Lays a card or cards from your hand. If multiple cards are needed, separate them by a comma (1,2,3).""" + if not await self.checkPM(ctx.message): + return + userGame = self.userGame(ctx.message.author) + if not userGame: + msg = "You're not in a game - you can create one with `{}newcah` or join one with `{}joincah`.".format(ctx.prefix, ctx.prefix) + await self.bot.send_message(ctx.message.author, msg) + return + userGame['Time'] = int(time.time()) + for member in userGame['Members']: + if member['User'] == ctx.message.author: + member['Time'] = int(time.time()) + user = member + index = userGame['Members'].index(member) + if index == userGame['Judge']: + await self.bot.send_message(ctx.message.author, "You're the judge. You don't get to lay cards this round.") + return + for submit in userGame['Submitted']: + if submit['By']['User'] == ctx.message.author: + await self.bot.send_message(ctx.message.author, "You already made your submission this round.") + return + if card == None: + await self.bot.send_message(ctx.message.author, 'You need you input *something.*') + return + card = card.strip() + card = card.replace(" ", "") + # Not the judge + if len(userGame['Members']) < self.minMembers: + stat_embed = discord.Embed(color=discord.Color.red()) + stat_embed.set_author(name='Not enough players to continue! ({}/{})'.format(len(userGame['Members']), self.minMembers)) + stat_embed.set_footer(text='Have other users join with: {}joincah {}'.format(ctx.prefix, userGame['ID'])) + await self.bot.send_message(ctx.message.author, embed=stat_embed) + return + + numberCards = userGame['BlackCard']['Pick'] + cards = [] + if numberCards > 1: + cardSpeak = "cards" + try: + card = card.split(',') + except Exception: + card = [] + if not len(card) == numberCards: + msg = 'You need to lay **{} cards** (no duplicates) with `{}lay [card numbers separated by commas (1,2,3)]`'.format(numberCards, ctx.prefix) + await self.bot.send_message(ctx.message.author, msg) + await self.showHand(ctx, ctx.message.author) + return + # Got something + # Check for duplicates + if not len(card) == len(set(card)): + msg = 'You need to lay **{} cards** (no duplicates) with `{}lay [card numbers separated by commas (1,2,3)]`'.format(numberCards, ctx.prefix) + await self.bot.send_message(ctx.message.author, msg) + await self.showHand(ctx, ctx.message.author) + return + # Works + for c in card: + try: + c = int(c) + except Exception: + msg = 'You need to lay **{} cards** (no duplicates) with `{}lay [card numbers separated by commas (1,2,3)]`'.format(numberCards, ctx.prefix) + await self.bot.send_message(ctx.message.author, msg) + await self.showHand(ctx, ctx.message.author) + return + + if c < 1 or c > len(user['Hand']): + msg = 'Card numbers must be between 1 and {}.'.format(len(user['Hand'])) + await self.bot.send_message(ctx.message.author, msg) + await self.showHand(ctx, ctx.message.author) + return + cards.append(user['Hand'][c-1]['Text']) + # Remove from user's hand + card = sorted(card, key=lambda card:int(card), reverse=True) + for c in card: + user['Hand'].pop(int(c)-1) + # Valid cards + + newSubmission = { 'By': user, 'Cards': cards } + else: + cardSpeak = "card" + try: + card = int(card) + except Exception: + msg = 'You need to lay a valid card with `{}lay [card number]`'.format(ctx.prefix) + await self.bot.send_message(ctx.message.author, msg) + await self.showHand(ctx, ctx.message.author) + return + if card < 1 or card > len(user['Hand']): + msg = 'Card numbers must be between 1 and {}.'.format(len(user['Hand'])) + await self.bot.send_message(ctx.message.author, msg) + await self.showHand(ctx, ctx.message.author) + return + # Valid card + newSubmission = { 'By': user, 'Cards': [ user['Hand'].pop(card-1)['Text'] ] } + userGame['Submitted'].append(newSubmission) + + # Shuffle cards + shuffle(userGame['Submitted']) + + user['Laid'] = True + await self.bot.send_message(ctx.message.author, 'You submitted your {}!'.format(cardSpeak)) + await self.checkSubmissions(ctx, userGame, user) + + + @commands.command(pass_context=True) + async def pick(self, ctx, *, card = None): + """As the judge - pick the winning card(s).""" + if not await self.checkPM(ctx.message): + return + # Check if the user is already in game + userGame = self.userGame(ctx.message.author) + if not userGame: + # Not in a game + msg = "You're not in a game - you can create one with `{}newcah` or join one with `{}joincah`.".format(ctx.prefix, ctx.prefix) + await self.bot.send_message(ctx.message.author, msg) + return + userGame['Time'] = int(time.time()) + isJudge = False + for member in userGame['Members']: + if member['User'] == ctx.message.author: + member['Time'] = int(time.time()) + user = member + index = userGame['Members'].index(member) + if index == userGame['Judge']: + isJudge = True + if not isJudge: + msg = "You're not the judge - I guess you'll have to wait your turn.".format(ctx.prefix, ctx.prefix) + await self.bot.send_message(ctx.message.author, msg) + return + # Am judge + totalUsers = len(userGame['Members'])-1 + submitted = len(userGame['Submitted']) + if submitted < totalUsers: + if totalUsers - submitted == 1: + msg = "Still waiting on 1 card..." + else: + msg = "Still waiting on {} cards...".format(totalUsers-submitted) + await self.bot.send_message(ctx.message.author, msg) + return + try: + card = int(card)-1 + except Exception: + card = -1 + if card < 0 or card >= totalUsers: + msg = "Your pick must be between 1 and {}.".format(totalUsers) + await self.bot.send_message(ctx.message.author, msg) + return + # Pick is good! + await self.winningCard(ctx, userGame, card) + + + @commands.command(pass_context=True) + async def hand(self, ctx): + """Shows your hand.""" + if not await self.checkPM(ctx.message): + return + # Check if the user is already in game + userGame = self.userGame(ctx.message.author) + if not userGame: + # Not in a game + msg = "You're not in a game - you can create one with `{}newcah` or join one with `{}joincah`.".format(ctx.prefix, ctx.prefix) + await self.bot.send_message(ctx.message.author, msg) + return + await self.showHand(ctx, ctx.message.author) + userGame['Time'] = currentTime = int(time.time()) + + + @commands.command(pass_context=True) + async def newcah(self, ctx): + """Starts a new Cards Against Humanity game.""" + #if not await self.checkPM(ctx.message): + #return + # Check if the user is already in game + userGame = self.userGame(ctx.message.author) + if userGame: + # Already in a game + msg = "You're already in a game (id: *{}*)\nType `{}leavecah` to leave that game.".format(userGame['ID'], ctx.prefix) + await self.bot.send_message(ctx.message.channel, msg) + return + + # Not in a game - create a new one + gameID = self.randomID() + currentTime = int(time.time()) + newGame = { 'ID': gameID, 'Members': [], 'Discard': [], 'BDiscard': [], 'Judge': -1, 'Time': currentTime, 'BlackCard': None, 'Submitted': [], 'NextHand': asyncio.Event(), 'Judging': False, 'Timeout': True } + member = { 'ID': ctx.message.author.id, 'User': ctx.message.author, 'Points': 0, 'Won': [], 'Hand': [], 'Laid': False, 'Refreshed': False, 'IsBot': False, 'Creator': True, 'Task': None, 'Time': currentTime } + newGame['Members'].append(member) + newGame['Running'] = True + task = self.bot.loop.create_task(self.gameCheckLoop(ctx, newGame)) + task = self.bot.loop.create_task(self.checkCards(ctx, newGame)) + self.games.append(newGame) + # Tell the user they created a new game and list its ID + await self.bot.send_message(ctx.message.channel, 'You created game id: *{}*'.format(gameID)) + await self.drawCards(ctx.message.author) + # await self.showHand(ctx, ctx.message.author) + # await self.nextPlay(ctx, newGame) + + + @commands.command(pass_context=True) + async def leavecah(self, ctx): + """Leaves the current game you're in.""" + removeCheck = await self.removeMember(ctx.message.author) + if not removeCheck: + msg = 'You are not in a game.' + await self.bot.send_message(ctx.message.channel, msg) + return + if self.checkGame(removeCheck): + # await self.nextPlay(ctx, removeCheck) + + """# Start the game loop + event = removeCheck['NextHand'] + self.bot.loop.call_soon_threadsafe(event.set)""" + # Player was removed - try to handle it calmly... + await self.checkSubmissions(ctx, removeCheck) + + + @commands.command(pass_context=True) + async def joincah(self, ctx, *, id = None): + """Join a Cards Against Humanity game. If no id or user is passed, joins a random game.""" + #if not await self.checkPM(ctx.message): + #return + # Check if the user is already in game + userGame = self.userGame(ctx.message.author) + isCreator = False + if userGame: + # Already in a game + msg = "You're already in a game (id: *{}*)\nType `{}leavecah` to leave that game.".format(userGame['ID'], ctx.prefix) + await self.bot.send_message(ctx.message.channel, msg) + return + if len(self.games): + if id: + game = self.gameForID(id) + if game == None: + # That id doesn't exist - or is possibly a user + # If user, has to be joined from server chat + if not ctx.message.server: + msg = "I couldn't find a game attached to that id. If you are trying to join a user - run the `{}joincah [user]` command in a channel on a server you share with that user.".format(ctx.prefix) + await self.bot.send_message(ctx.message.channel, msg) + return + else: + # We have a server - let's try for a user + member = self.memberforname(id, ctx.message.server) + if not member: + # Couldn't find user! + msg = "I couldn't find a game attached to that id. If you are trying to join a user - run the `{}joincah [user]` command in a channel on a server you share with that user.".format(ctx.prefix) + await self.bot.send_message(ctx.message.channel, msg) + return + # Have a user - check if they're in a game + game = self.userGame(member) + if not game: + # That user is NOT in a game! + msg = "That user doesn't appear to be playing." + await self.bot.send_message(ctx.message.channel, msg) + return + + else: + game = random.choice(self.games) + else: + # No games - create a new one + gameID = self.randomID() + currentTime = int(time.time()) + game = { 'ID': gameID, 'Members': [], 'Discard': [], 'BDiscard': [], 'Judge': -1, 'Time': currentTime, 'BlackCard': None, 'Submitted': [], 'NextHand': asyncio.Event(), 'Judging': False, 'Timeout': True } + game['Running'] = True + task = self.bot.loop.create_task(self.gameCheckLoop(ctx, game)) + task = self.bot.loop.create_task(self.checkCards(ctx, game)) + self.games.append(game) + # Tell the user they created a new game and list its ID + await self.bot.send_message(ctx.message.channel, '**You created game id:** ***{}***'.format(gameID)) + isCreator = True + + # Tell everyone else you joined + for member in game['Members']: + if member['IsBot']: + continue + await self.bot.send_message(member['User'], '***{}*** **joined the game!**'.format(self.displayname(ctx.message.author))) + + # We got a user! + currentTime = int(time.time()) + member = { 'ID': ctx.message.author.id, 'User': ctx.message.author, 'Points': 0, 'Won': [], 'Hand': [], 'Laid': False, 'Refreshed': False, 'IsBot': False, 'Creator': isCreator, 'Task': None, 'Time': currentTime } + game['Members'].append(member) + await self.drawCards(ctx.message.author) + if len(game['Members'])==1: + # Just created the game + await self.drawCards(ctx.message.author) + else: + msg = "**You've joined game id:** ***{}!***\n\nThere are *{} users* in this game.".format(game['ID'], len(game['Members'])) + await self.bot.send_message(ctx.message.channel, msg) + + # Check if adding put us at minimum members + if len(game['Members']) - 1 < self.minMembers: + # It was - *actually* start a game + event = game['NextHand'] + self.bot.loop.call_soon_threadsafe(event.set) + else: + # It was not - just incorporate new players + await self.checkSubmissions(ctx, game) + # Reset judging flag to retrigger actions + game['Judging'] = False + # Show the user the current card and their hand + await self.showPlay(ctx, member['User']) + await self.showHand(ctx, member['User']) + event = game['NextHand'] + + game['Time'] = int(time.time()) + + + @commands.command(pass_context=True) + async def addbot(self, ctx): + """Adds a bot to the game. Can only be done by the player who created the game.""" + if not await self.checkPM(ctx.message): + return + # Check if the user is already in game + userGame = self.userGame(ctx.message.author) + if not userGame: + # Not in a game + msg = "You're not in a game - you can create one with `{}newcah` or join one with `{}joincah`.".format(ctx.prefix, ctx.prefix) + await self.bot.send_message(ctx.message.author, msg) + return + botCount = 0 + for member in userGame['Members']: + if member['IsBot']: + botCount += 1 + continue + if member['User'] == ctx.message.author: + if not member['Creator']: + # You didn't make this game + msg = 'Only the player that created the game can add bots.' + await self.bot.send_message(ctx.message.author, msg) + return + member['Time'] = int(time.time()) + # We are the creator - let's check the number of bots + if botCount >= self.maxBots: + # Too many bots! + msg = 'You already have enough bots (max is {}).'.format(self.maxBots) + await self.bot.send_message(ctx.message.author, msg) + return + # We can get another bot! + botID = self.randomBotID(userGame) + lobot = { 'ID': botID, 'User': None, 'Points': 0, 'Won': [], 'Hand': [], 'Laid': False, 'Refreshed': False, 'IsBot': True, 'Creator': False, 'Task': None } + userGame['Members'].append(lobot) + await self.drawCards(lobot['ID']) + msg = '***{} ({})*** **joined the game!**'.format(self.botName, botID) + for member in userGame['Members']: + if member['IsBot']: + continue + await self.bot.send_message(member['User'], msg) + # await self.nextPlay(ctx, userGame) + + # Check if adding put us at minimum members + if len(userGame['Members']) - 1 < self.minMembers: + # It was - *actually* start a game + event = userGame['NextHand'] + self.bot.loop.call_soon_threadsafe(event.set) + else: + # It was not - just incorporate new players + await self.checkSubmissions(ctx, userGame) + # Reset judging flag to retrigger actions + userGame['Judging'] = False + # Schedule stuff + task = asyncio.ensure_future(self.botPick(ctx, lobot, userGame)) + lobot['Task'] = task + + + @commands.command(pass_context=True) + async def addbots(self, ctx, number = None): + """Adds bots to the game. Can only be done by the player who created the game.""" + if not await self.checkPM(ctx.message): + return + # Check if the user is already in game + userGame = self.userGame(ctx.message.author) + if not userGame: + # Not in a game + msg = "You're not in a game - you can create one with `{}newcah` or join one with `{}joincah`.".format(ctx.prefix, ctx.prefix) + await self.bot.send_message(ctx.message.author, msg) + return + botCount = 0 + for member in userGame['Members']: + if member['IsBot']: + botCount += 1 + continue + if member['User'] == ctx.message.author: + if not member['Creator']: + # You didn't make this game + msg = 'Only the player that created the game can add bots.' + await self.bot.send_message(ctx.message.author, msg) + return + member['Time'] = int(time.time()) + if number == None: + # No number specified - let's add the max number of bots + number = self.maxBots - botCount + + try: + number = int(number) + except Exception: + msg = 'Number of bots to add must be an integer.' + await self.bot.send_message(ctx.message.author, msg) + return + + # We are the creator - let's check the number of bots + if botCount >= self.maxBots: + # Too many bots! + msg = 'You already have enough bots (max is {}).'.format(self.maxBots) + await self.bot.send_message(ctx.message.author, msg) + return + + if number > (self.maxBots - botCount): + number = self.maxBots - botCount + + if number == 1: + msg = '**Adding {} bot:**\n\n'.format(number) + else: + msg = '**Adding {} bots:**\n\n'.format(number) + + newBots = [] + for i in range(0, number): + # We can get another bot! + botID = self.randomBotID(userGame) + lobot = { 'ID': botID, 'User': None, 'Points': 0, 'Won': [], 'Hand': [], 'Laid': False, 'Refreshed': False, 'IsBot': True, 'Creator': False, 'Task': None } + userGame['Members'].append(lobot) + newBots.append(lobot) + await self.drawCards(lobot['ID']) + msg += '***{} ({})*** **joined the game!**\n'.format(self.botName, botID) + # await self.nextPlay(ctx, userGame) + + for member in userGame['Members']: + if member['IsBot']: + continue + await self.bot.send_message(member['User'], msg) + + # Check if adding put us at minimum members + if len(userGame['Members']) - number < self.minMembers: + # It was - *actually* start a game + event = userGame['NextHand'] + self.bot.loop.call_soon_threadsafe(event.set) + else: + # It was not - just incorporate new players + await self.checkSubmissions(ctx, userGame) + # Reset judging flag to retrigger actions + game['Judging'] = False + for bot in newBots: + # Schedule stuff + task = asyncio.ensure_future(self.botPick(ctx, bot, userGame)) + bot['Task'] = task + + @commands.command(pass_context=True) + async def removebot(self, ctx, *, id = None): + """Removes a bot from the game. Can only be done by the player who created the game.""" + if not await self.checkPM(ctx.message): + return + # Check if the user is already in game + userGame = self.userGame(ctx.message.author) + if not userGame: + # Not in a game + msg = "You're not in a game - you can create one with `{}newcah` or join one with `{}joincah`.".format(ctx.prefix, ctx.prefix) + await self.bot.send_message(ctx.message.author, msg) + return + botCount = 0 + for member in userGame['Members']: + if member['IsBot']: + botCount += 1 + continue + if member['User'] == ctx.message.author: + if not member['Creator']: + # You didn't make this game + msg = 'Only the player that created the game can remove bots.' + await self.bot.send_message(ctx.message.author, msg) + return + member['Time'] = int(time.time()) + # We are the creator - let's check the number of bots + if id == None: + # Just remove the first bot we find + for member in userGame['Members']: + if member['IsBot']: + await self.removeMember(member['ID']) + """# Start the game loop + event = userGame['NextHand'] + self.bot.loop.call_soon_threadsafe(event.set)""" + # Bot was removed - try to handle it calmly... + await self.checkSubmissions(ctx, userGame) + return + msg = 'No bots to remove!' + await self.bot.send_message(ctx.message.author, msg) + return + else: + # Remove a bot by id + if not await self.removeMember(id): + # not found + msg = 'I couldn\'t locate that bot on this game. If you\'re trying to remove a player, try the `{}removeplayer [name]` command.'.format(ctx.prefix) + await self.bot.send_message(ctx.message.author, msg) + return + # await self.nextPlay(ctx, userGame) + + """# Start the game loop + event = userGame['NextHand'] + self.bot.loop.call_soon_threadsafe(event.set)""" + # Bot was removed - let's try to handle it calmly... + await self.checkSubmissions(ctx, userGame) + + + @commands.command(pass_context=True) + async def cahgames(self, ctx): + """Displays up to 10 CAH games in progress.""" + shuffledGames = list(self.games) + random.shuffle(shuffledGames) + if not len(shuffledGames): + await self.bot.send_message(ctx.message.channel, 'No games being played currently.') + return + + max = 10 + if len(shuffledGames) < 10: + max = len(shuffledGames) + msg = '__Current CAH Games__:\n\n' + + for i in range(0, max): + playerCount = 0 + botCount = 0 + gameID = shuffledGames[i]['ID'] + for j in shuffledGames[i]['Members']: + if j['IsBot']: + botCount += 1 + else: + playerCount += 1 + botText = '{} bot'.format(botCount) + if not botCount == 1: + botText += 's' + playerText = '{} player'.format(playerCount) + if not playerCount == 1: + playerText += 's' + + msg += '{}. {} - {} | {}\n'.format(i+1, gameID, playerText, botText) + + await self.bot.send_message(ctx.message.channel, msg) + + + + @commands.command(pass_context=True) + async def score(self, ctx): + """Display the score of the current game.""" + if not await self.checkPM(ctx.message): + return + # Check if the user is already in game + userGame = self.userGame(ctx.message.author) + if not userGame: + # Not in a game + msg = "You're not in a game - you can create one with `{}newcah` or join one with `{}joincah`.".format(ctx.prefix, ctx.prefix) + await self.bot.send_message(ctx.message.author, msg) + return + stat_embed = discord.Embed(color=discord.Color.purple()) + stat_embed.set_author(name='Current Score') + stat_embed.set_footer(text='Cards Against Humanity - id: {}'.format(userGame['ID'])) + await self.bot.send_message(ctx.message.author, embed=stat_embed) + users = sorted(userGame['Members'], key=lambda card:int(card['Points']), reverse=True) + msg = '' + i = 0 + if len(users) > 10: + msg += '__10 of {} Players:__\n\n'.format(len(users)) + else: + msg += '__Players:__\n\n' + for user in users: + i += 1 + if i > 10: + break + if user['Points'] == 1: + if user['User']: + # Person + msg += '{}. *{}* - 1 point\n'.format(i, self.displayname(user['User'])) + else: + # Bot + msg += '{}. *{} ({})* - 1 point\n'.format(i, self.botName, user['ID']) + else: + if user['User']: + # Person + msg += '{}. *{}* - {} points\n'.format(i, self.displayname(user['User']), user['Points']) + else: + # Bot + msg += '{}. *{} ({})* - {} points\n'.format(i, self.botName, user['ID'], user['Points']) + await self.bot.send_message(ctx.message.author, msg) + + @commands.command(pass_context=True) + async def laid(self, ctx): + """Shows who laid their cards and who hasn't.""" + if not await self.checkPM(ctx.message): + return + # Check if the user is already in game + userGame = self.userGame(ctx.message.author) + if not userGame: + # Not in a game + msg = "You're not in a game - you can create one with `{}newcah` or join one with `{}joincah`.".format(ctx.prefix, ctx.prefix) + await self.bot.send_message(ctx.message.author, msg) + return + stat_embed = discord.Embed(color=discord.Color.purple()) + stat_embed.set_author(name='Card Check') + stat_embed.set_footer(text='Cards Against Humanity - id: {}'.format(userGame['ID'])) + await self.bot.send_message(ctx.message.author, embed=stat_embed) + users = sorted(userGame['Members'], key=lambda card:int(card['Laid'])) + msg = '' + i = 0 + if len(users) > 10: + msg += '__10 of {} Players:__\n\n'.format(len(users)) + else: + msg += '__Players:__\n\n' + for user in users: + if len(userGame['Members']) >= self.minMembers: + if user == userGame['Members'][userGame['Judge']]: + continue + i += 1 + if i > 10: + break + + if user['Laid']: + if user['User']: + # Person + msg += '{}. *{}* - Cards are in.\n'.format(i, self.displayname(user['User'])) + else: + # Bot + msg += '{}. *{} ({})* - Cards are in.\n'.format(i, self.botName, user['ID']) + else: + if user['User']: + # Person + msg += '{}. *{}* - Waiting for cards...\n'.format(i, self.displayname(user['User'])) + else: + # Bot + msg += '{}. *{} ({})* - Waiting for cards...\n'.format(i, self.botName, user['ID']) + await self.bot.send_message(ctx.message.author, msg) + + @commands.command(pass_context=True) + async def removeplayer(self, ctx, *, name = None): + """Removes a player from the game. Can only be done by the player who created the game.""" + if not await self.checkPM(ctx.message): + return + # Check if the user is already in game + userGame = self.userGame(ctx.message.author) + if not userGame: + # Not in a game + msg = "You're not in a game - you can create one with `{}newcah` or join one with `{}joincah`.".format(ctx.prefix, ctx.prefix) + await self.bot.send_message(ctx.message.author, msg) + return + botCount = 0 + for member in userGame['Members']: + if member['IsBot']: + botCount += 1 + continue + if member['User'] == ctx.message.author: + if not member['Creator']: + # You didn't make this game + msg = 'Only the player that created the game can remove players.' + await self.bot.send_message(ctx.message.author, msg) + return + member['Time'] = int(time.time()) + # We are the creator - let's check the number of bots + if name == None: + # Nobody named! + msg = 'Okay, I removed... no one from the game...' + await self.bot.send_message(ctx.message.author, msg) + return + + # Let's get the person either by name, or by id + nameID = ''.join(list(filter(str.isdigit, name))) + for member in userGame['Members']: + toRemove = False + if member['IsBot']: + continue + if name.lower() == self.displayname(member['User']).lower(): + # Got em! + toRemove = True + elif nameID == member['ID']: + # Got em! + toRemove = True + if toRemove: + await self.removeMember(member['ID']) + break + # await self.nextPlay(ctx, userGame) + + if toRemove: + """# Start the game loop + event = userGame['NextHand'] + self.bot.loop.call_soon_threadsafe(event.set)""" + # Player was removed - try to handle it calmly... + await self.checkSubmissions(ctx, userGame) + else: + msg = 'I couldn\'t locate that player on this game. If you\'re trying to remove a bot, try the `{}removebot [id]` command.'.format(ctx.prefix) + await self.bot.send_message(ctx.message.author, msg) + return + + @commands.command(pass_context=True) + async def flushhand(self, ctx): + """Flushes the cards in your hand - can only be done once per game.""" + if not await self.checkPM(ctx.message): + return + # Check if the user is already in game + userGame = self.userGame(ctx.message.author) + if not userGame: + # Not in a game + msg = "You're not in a game - you can create one with `{}newcah` or join one with `{}joincah`.".format(ctx.prefix, ctx.prefix) + await self.bot.send_message(ctx.message.author, msg) + return + if userGame['Judge'] == -1: + msg = "The game hasn't started yet. Probably not worth it to flush your hand before you get it..." + await self.bot.send_message(ctx.message.author, msg) + return + for member in userGame['Members']: + if member['IsBot']: + continue + if member['User'] == ctx.message.author: + member['Time'] = int(time.time()) + # Found us! + if member['Refreshed']: + # Already flushed their hand + msg = 'You have already flushed your hand this game.' + await self.bot.send_message(ctx.message.author, msg) + return + else: + member['Hand'] = [] + await self.drawCards(member['ID']) + member['Refreshed'] = True + msg = 'Flushing your hand!' + await self.bot.send_message(ctx.message.author, msg) + await self.showHand(ctx, ctx.message.author) + return + + @commands.command(pass_context=True) + async def idlekick(self, ctx, *, setting = None): + """Sets whether or not to kick members if idle for 5 minutes or more. Can only be done by the player who created the game.""" + if not await self.checkPM(ctx.message): + return + # Check if the user is already in game + userGame = self.userGame(ctx.message.author) + if not userGame: + # Not in a game + msg = "You're not in a game - you can create one with `{}newcah` or join one with `{}joincah`.".format(ctx.prefix, ctx.prefix) + await self.bot.send_message(ctx.message.author, msg) + return + botCount = 0 + for member in userGame['Members']: + if member['IsBot']: + botCount += 1 + continue + if member['User'] == ctx.message.author: + if not member['Creator']: + # You didn't make this game + msg = 'Only the player that created the game can remove bots.' + await self.bot.send_message(ctx.message.author, msg) + return + # We are the creator - let's check the number of bots + if setting == None: + # Output idle kick status + if userGame['Timeout']: + await self.bot.send_message(ctx.message.channel, 'Idle kick is enabled.') + else: + await self.bot.send_message(ctx.message.channel, 'Idle kick is disabled.') + return + elif setting.lower() == "yes" or setting.lower() == "on" or setting.lower() == "true": + setting = True + elif setting.lower() == "no" or setting.lower() == "off" or setting.lower() == "false": + setting = False + else: + setting = None + + if setting == True: + if userGame['Timeout'] == True: + msg = 'Idle kick remains enabled.' + else: + msg = 'Idle kick now enabled.' + for member in userGame['Members']: + member['Time'] = int(time.time()) + else: + if userGame['Timeout'] == False: + msg = 'Idle kick remains disabled.' + else: + msg = 'Idle kick now disabled.' + userGame['Timeout'] = setting + + await self.bot.send_message(ctx.message.channel, msg) + + @commands.command() + async def cahcredits(self): + """Code credits.""" + message = await self._credit() + await self.bot.say(message) + + async def _credit(self): + message = "```This cog is made possible by CorpBot.\n" + message+= "Please visit https://github.com/corpnewt/CorpBot.py for more information.```" + return message + +def setup(bot): + bot.add_cog(CardsAgainstHumanity(bot)) diff --git a/cah/data/deck.json b/cah/data/deck.json new file mode 100644 index 0000000..47bd1e5 --- /dev/null +++ b/cah/data/deck.json @@ -0,0 +1 @@ +{"blackCards":[{"text":"Why can't I sleep at night?","pick":1},{"text":"I got 99 problems but _ ain't one.","pick":1},{"text":"What's a girl's best friend?","pick":1},{"text":"What's that smell?","pick":1},{"text":"This is the way the world ends \/ This is the way the world ends \/ Not with a bang but with _.","pick":1},{"text":"What is Batman's guilty pleasure?","pick":1},{"text":"TSA guidelines now prohibit _ on airplanes.","pick":1},{"text":"What ended my last relationship?","pick":1},{"text":"MTV's new reality show features eight washed-up celebrities living with _.","pick":1},{"text":"I drink to forget _.","pick":1},{"text":"I'm sorry, Professor, but I couldn't complete my homework because of _.","pick":1},{"text":"Alternative medicine is now embracing the curative powers of _.","pick":1},{"text":"What's that sound?","pick":1},{"text":"What's the next Happy Meal® toy?","pick":1},{"text":"It's a pity that kids these days are all getting involved with _.","pick":1},{"text":"In the new Disney Channel Original Movie, Hannah Montana struggles with _ for the first time.","pick":1},{"text":"_. That's how I want to die.","pick":1},{"text":"What does Dick Cheney prefer?","pick":1},{"text":"What's the most emo?","pick":1},{"text":"Instead of coal, Santa now gives the bad children _.","pick":1},{"text":"Next from J.K. Rowling: Harry Potter and the Chamber of _.","pick":1},{"text":"A romantic, candlelit dinner would be incomplete without _.","pick":1},{"text":"White people like _.","pick":1},{"text":"_. Betcha can't have just one!","pick":1},{"text":"War!

What is it good for?","pick":1},{"text":"BILLY MAYS HERE FOR _.","pick":1},{"text":"_. High five, bro.","pick":1},{"text":"During sex, I like to think about _.","pick":1},{"text":"What did I bring back from Mexico?","pick":1},{"text":"What are my parents hiding from me?","pick":1},{"text":"What will always get you laid?","pick":1},{"text":"What would grandma find disturbing, yet oddly charming?","pick":1},{"text":"What did the U.S. airdrop to the children of Afghanistan?","pick":1},{"text":"What helps Obama unwind?","pick":1},{"text":"What's there a ton of in heaven?","pick":1},{"text":"Major League Baseball has banned _ for giving players an unfair advantage.","pick":1},{"text":"When I am a billionaire, I shall erect a 50-foot statue to commemorate _.","pick":1},{"text":"What's the new fad diet?","pick":1},{"text":"When I am the President of the United States, I will create the Department of _.","pick":1},{"text":"_. It's a trap!","pick":1},{"text":"How am I maintaining my relationship status?","pick":1},{"text":"What will I bring back in time to convince people that I am a powerful wizard?","pick":1},{"text":"While the United States raced the Soviet Union to the moon, the Mexican government funneled millions of pesos into research on _.","pick":1},{"text":"Coming to Broadway this season, _: The Musical.","pick":1},{"text":"What's my secret power?","pick":1},{"text":"What gives me uncontrollable gas?","pick":1},{"text":"But before I kill you, Mr. Bond, I must show you _.","pick":1},{"text":"What never fails to liven up the party?","pick":1},{"text":"What am I giving up for Lent?","pick":1},{"text":"What do old people smell like? ","pick":1},{"text":"The class field trip was completely ruined by _.","pick":1},{"text":"When Pharaoh remained unmoved, Moses called down a plague of _.","pick":1},{"text":"I do not know with which weapons World War III will be fought, but World War IV will be fought with _.","pick":1},{"text":"What's Teach for America using to inspire inner city students to succeed?","pick":1},{"text":"In Michael Jackson's final moments, he thought about _.","pick":1},{"text":"Why do I hurt all over?","pick":1},{"text":"Studies show that lab rats navigate mazes 50% faster after being exposed to _.","pick":1},{"text":"Why am I sticky?","pick":1},{"text":"What's my anti-drug?","pick":1},{"text":"And the Academy Award for _ goes to _.","pick":2},{"text":"For my next trick, I will pull _ out of _.","pick":2},{"text":"_: Good to the last drop.","pick":1},{"text":"What did Vin Diesel eat for dinner?","pick":1},{"text":"_: kid-tested, mother-approved.","pick":1},{"text":"What gets better with age?","pick":1},{"text":"I never truly understood _ until I encountered _.","pick":2},{"text":"Rumor has it that Vladimir Putin's favorite delicacy is _ stuffed with _.","pick":2},{"text":"Lifetime® presents _, the story of _.","pick":2},{"text":"Make a haiku.","pick":3},{"text":"In M. Night Shyamalan's new movie, Bruce Willis discovers that _ had really been _ all along.","pick":2},{"text":"_ is a slippery slope that leads to _.","pick":2},{"text":"In a world ravaged by _, our only solace is _.","pick":2},{"text":"That's right, I killed _. How, you ask? _.","pick":2},{"text":"When I was tripping on acid, _ turned into _.","pick":2},{"text":"_ + _ = _.","pick":3},{"text":"What's the next superhero\/sidekick duo?","pick":2},{"text":"Dear Abby,

I'm having some trouble with _ and would like your advice.","pick":1},{"text":"After the earthquake, Sean Penn brought _ to the people of Haiti.","pick":1},{"text":"In L.A. County Jail, word is you can trade 200 cigarettes for _.","pick":1},{"text":"Maybe she's born with it. Maybe it's _.","pick":1},{"text":"Life for American Indians was forever changed when the White Man introduced them to _.","pick":1},{"text":"Next on ESPN2, the World Series of _.","pick":1},{"text":"Step 1: _. Step 2: _. Step 3: Profit.","pick":2},{"text":"Here is the church
Here is the steeple
Open the doors
And there is _.","pick":1},{"text":"How did I lose my virginity?","pick":1},{"text":"During his childhood, Salvador Dalí produced hundreds of paintings of _.","pick":1},{"text":"In 1,000 years, when paper money is a distant memory, how will we pay for goods and services?","pick":1},{"text":"What don't you want to find in your Kung Pao chicken?","pick":1},{"text":"The Smithsonian Museum of Natural History has just opened an exhibit on _.","pick":1},{"text":"Daddy, why is Mommy crying?","pick":1}],"whiteCards":["Coat hanger abortions.","Man meat.","Autocannibalism.","Vigorous jazz hands.","Flightless birds.","Pictures of boobs.","Doing the right thing.","The violation of our most basic human rights.","Viagra®.","Self-loathing.","Spectacular abs.","A balanced breakfast.","Roofies.","Concealing a boner.","Amputees.","The Big Bang.","Former President George W. Bush.","The Rev. Dr. Martin Luther King, Jr.","Smegma.","Being marginalized.","Cuddling.","Laying an egg.","The Pope.","Aaron Burr.","Genital piercings.","Fingering.","A bleached asshole.","Horse meat.","Fear itself.","Science.","Elderly Japanese men.","Stranger danger.","The terrorists.","Praying the gay away.","Same-sex ice dancing.","Ethnic cleansing.","Cheating in the Special Olympics.","German dungeon porn.","Bingeing and purging.","Making a pouty face.","William Shatner.","Heteronormativity.","Nickelback.","Tom Cruise.","The profoundly handicapped.","The placenta.","Chainsaws for hands.","Arnold Schwarzenegger.","An icepick lobotomy.","Goblins.","Object permanence.","Dying.","Foreskin.","A falcon with a cap on its head.","Hormone injections.","Dying of dysentery.","Sexy pillow fights.","The invisible hand.","A really cool hat.","Sean Penn.","Heartwarming orphans.","The clitoris.","The Three-Fifths compromise.","A sad handjob.","Men.","Historically black colleges.","A micropenis.","Raptor attacks.","Agriculture.","Vikings.","Pretending to care.","The Underground Railroad.","My humps.","Being a dick to children.","Geese.","Bling.","Sniffing glue.","The South.","An Oedipus complex.","Eating all of the cookies before the AIDS bake-sale.","Sexting.","YOU MUST CONSTRUCT ADDITIONAL PYLONS.","Mutually-assured destruction.","Sunshine and rainbows.","Count Chocula.","Sharing needles.","Being rich.","Skeletor.","A sausage festival.","Michael Jackson.","Emotions.","Farting and walking away.","The Chinese gymnastics team.","Necrophilia.","Spontaneous human combustion.","Yeast.","Leaving an awkward voicemail.","Dick Cheney.","White people.","Penis envy.","Teaching a robot to love.","Sperm whales.","Scrubbing under the folds.","Panda sex.","Whipping it out.","Catapults.","Masturbation.","Natural selection.","Opposable thumbs.","A sassy black woman.","AIDS.","The KKK.","Figgy pudding.","Seppuku.","Gandhi.","Preteens.","Toni Morrison's vagina.","Five-Dollar Footlongs™.","Land mines.","A sea of troubles.","A zesty breakfast burrito.","Christopher Walken.","Friction.","Balls.","Dental dams.","A can of whoop-ass.","A tiny horse.","Waiting 'til marriage.","Authentic Mexican cuisine.","Genghis Khan.","Old-people smell.","Feeding Rosie O'Donnell.","Pixelated bukkake.","Friends with benefits.","The token minority.","The Tempur-Pedic® Swedish Sleep System™.","A thermonuclear detonation.","Take-backsies.","The Rapture.","A cooler full of organs.","Sweet, sweet vengeance.","RoboCop.","Keanu Reeves.","Drinking alone.","Giving 110%.","Flesh-eating bacteria.","The American Dream.","Taking off your shirt.","Me time.","A murder most foul.","The inevitable heat death of the universe.","The folly of man.","That thing that electrocutes your abs.","Cards Against Humanity.","Fiery poops.","Poor people.","Edible underpants.","Britney Spears at 55.","All-you-can-eat shrimp for $4.99.","Pooping back and forth. Forever.","Fancy Feast®.","Jewish fraternities.","Being a motherfucking sorcerer.","Pulling out.","Picking up girls at the abortion clinic.","The homosexual agenda.","The Holy Bible.","Passive-agression.","Ronald Reagan.","Vehicular manslaughter.","Nipple blades.","Assless chaps.","Full frontal nudity.","Hulk Hogan.","Daddy issues.","The hardworking Mexican.","Natalie Portman.","Waking up half-naked in a Denny's parking lot.","God.","Sean Connery.","Saxophone solos.","Gloryholes.","The World of Warcraft.","Homeless people.","Scalping.","Darth Vader.","Eating the last known bison.","Guys who don't call.","Hot Pockets®.","A time travel paradox.","The milk man.","Testicular torsion.","Dropping a chandelier on your enemies and riding the rope up.","World peace.","A salty surprise.","Poorly-timed Holocaust jokes.","Smallpox blankets.","Licking things to claim them as your own.","The heart of a child.","Robert Downey, Jr.","Lockjaw.","Eugenics.","A good sniff.","Friendly fire.","The taint; the grundle; the fleshy fun-bridge.","Wearing underwear inside-out to avoid doing laundry.","Hurricane Katrina.","Free samples.","Jerking off into a pool of children's tears.","A foul mouth.","The glass ceiling.","Republicans.","Explosions.","Michelle Obama's arms.","Getting really high.","Attitude.","Sarah Palin.","The Übermensch.","Altar boys.","My soul.","My sex life.","Pedophiles.","72 virgins.","Pabst Blue Ribbon.","Domino's™ Oreo™ Dessert Pizza.","A snapping turtle biting the tip of your penis.","The Blood of Christ.","Half-assed foreplay.","My collection of high-tech sex toys.","A middle-aged man on roller skates.","Bitches.","Bill Nye the Science Guy.","Italians.","A windmill full of corpses.","Adderall™.","Crippling debt.","A stray pube.","Prancing.","Passing a kidney stone.","A brain tumor.","Leprosy.","Puppies!","Bees?","Frolicking.","Repression.","Road head.","A bag of magic beans.","An asymmetric boob job.","Dead parents.","Public ridicule.","A mating display.","A mime having a stroke.","Stephen Hawking talking dirty.","African children.","Mouth herpes.","Overcompensation.","Riding off into the sunset.","Being on fire.","Tangled Slinkys.","Civilian casualties.","Auschwitz.","My genitals.","Not reciprocating oral sex.","Lactation.","Being fabulous.","Shaquille O'Neal's acting career.","My relationship status.","Asians who aren't good at math.","Alcoholism.","Incest.","Grave robbing.","Hope.","8 oz. of sweet Mexican black-tar heroin.","Kids with ass cancer.","Winking at old people.","The Jews.","Justin Bieber.","Doin' it in the butt.","A lifetime of sadness.","The Hamburglar.","Swooping.","Classist undertones.","New Age music.","Not giving a shit about the Third World.","The Kool-Aid Man.","A hot mess.","Tentacle porn.","Lumberjack fantasies.","The gays.","Scientology.","Estrogen.","GoGurt®.","Judge Judy.","Dick fingers.","Racism.","Surprise sex!","Police brutality.","Passable transvestites.","The Virginia Tech Massacre.","When you fart and a little bit comes out.","Oompa-Loompas.","A fetus.","Obesity.","Tasteful sideboob.","Hot people.","BATMAN!!!","Black people.","A gassy antelope.","Sexual tension.","Third base.","Racially-biased SAT questions.","Porn stars.","A Super Soaker™ full of cat pee.","Muhammed (Praise Be Unto Him).","Puberty.","A disappointing birthday party.","An erection that lasts longer than four hours.","White privilege.","Getting so angry that you pop a boner.","Wifely duties.","Two midgets shitting into a bucket.","Queefing.","Wiping her butt.","Golden showers.","Barack Obama.","Nazis.","A robust mongoloid.","An M. Night Shyamalan plot twist.","Getting drunk on mouthwash.","Lunchables™.","Women in yogurt commercials.","John Wilkes Booth.","Powerful thighs.","Mr. Clean, right behind you.","Multiple stab wounds.","Cybernetic enhancements.","Serfdom.","Kanye West.","Women's suffrage.","Children on leashes.","Harry Potter erotica.","The Dance of the Sugar Plum Fairy.","Lance Armstrong's missing testicle.","Parting the Red Sea.","The Amish.","Dead babies.","Child beauty pageants.","AXE Body Spray.","Centaurs.","Copping a feel.","Grandma.","Famine.","The Trail of Tears.","The miracle of childbirth.","Finger painting.","A monkey smoking a cigar.","The Make-A-Wish® Foundation.","Anal beads.","The Force.","Kamikaze pilots.","Dry heaving.","Active listening.","Ghosts.","The Hustle.","Peeing a little bit.","Another goddamn vampire movie.","Shapeshifters.","The Care Bear Stare.","Hot cheese.","A mopey zoo lion.","A defective condom.","Teenage pregnancy.","A Bop It™.","Expecting a burp and vomiting on the floor.","Horrifying laser hair removal accidents.","Boogers.","Unfathomable stupidity.","Breaking out into song and dance.","Soup that is too hot.","Morgan Freeman's voice.","Getting naked and watching Nickelodeon.","MechaHitler.","Flying sex snakes.","The true meaning of Christmas.","My inner demons.","Pac-Man uncontrollably guzzling cum.","My vagina.","A homoerotic volleyball montage.","Actually taking candy from a baby.","Crystal meth.","Exactly what you'd expect.","Natural male enhancement.","Passive-aggressive Post-it notes.","Inappropriate yodeling.","Lady Gaga.","The Little Engine That Could.","Vigilante justice.","A death ray.","Poor life choices.","A gentle caress of the inner thigh.","Embryonic stem cells.","Nicolas Cage.","Firing a rifle into the air while balls deep in a squealing hog.","Switching to Geico®.","The chronic.","Erectile dysfunction.","Home video of Oprah sobbing into a Lean Cuisine®.","A bucket of fish heads.","50,000 volts straight to the nipples.","Being fat and stupid.","Hospice care.","A pyramid of severed heads.","Getting married, having a few kids, buying some stuff, retiring to Florida, and dying.","A subscription to Men's Fitness.","Crucifixion.","A micropig wearing a tiny raincoat and booties.","Some god-damn peace and quiet.","Used panties.","A tribe of warrior women.","The penny whistle solo from \"My Heart Will Go On.\"","An oversized lollipop.","Helplessly giggling at the mention of Hutus and Tutsis.","Not wearing pants.","Consensual sex.","Her Majesty, Queen Elizabeth II.","Funky fresh rhymes.","The art of seduction.","The Devil himself.","Advice from a wise, old black man.","Destroying the evidence.","The light of a billion suns.","Wet dreams.","Synergistic management solutions.","Growing a pair.","Silence.","An M16 assault rifle.","Poopy diapers.","A live studio audience.","The Great Depression.","A spastic nerd.","Rush Limbaugh's soft, shitty body.","Tickling Sean Hannity, even after he tells you to stop.","Stalin.","Brown people.","Rehab.","Capturing Newt Gingrich and forcing him to dance in a monkey suit.","Battlefield amputations.","An uppercut.","Shiny objects.","An ugly face.","Menstrual rage.","A bitch slap.","One trillion dollars.","Chunks of dead prostitute.","The entire Mormon Tabernacle Choir.","The female orgasm.","Extremely tight pants.","The Boy Scouts of America.","Stormtroopers.","Throwing a virgin into a volcano."],"Base":{"name":"Base Set","black":[0,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],"white":[0,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,422,423,424,425,426,427,428,429,430,431,432,433,434,435,436,437,438,439,440,441,442,443,444,445,446,447,448,449,450,451,452,453,454,455,456,457,458,459]},"order":["Base"]} \ No newline at end of file diff --git a/cah/info.json b/cah/info.json new file mode 100644 index 0000000..93113d4 --- /dev/null +++ b/cah/info.json @@ -0,0 +1,7 @@ +{ + "AUTHOR" : "aikaterna", + "INSTALL_MSG" : "Thanks for installing Cards Against Humanity. I highly advise only playing this game with 3-4 people on one server, on a private bot, because of the high amount of DM's this game generates." + "NAME" : "cah", + "SHORT" : "Cards Against Humanity.", + "DESCRIPTION" : "This cog was made possible by CorpBot: https://github.com/corpnewt/CorpBot.py" +} diff --git a/pug/info.json b/pug/info.json index d296ac6..007ec56 100644 --- a/pug/info.json +++ b/pug/info.json @@ -4,4 +4,5 @@ "NAME" : "Pug", "SHORT" : "Warcraft character lookup.", "DESCRIPTION" : "This tool lets you check potential group pugs for enchants, gems, and raid completion. It was made possible by PugBot: https://github.com/reznok/PugBot" + "TAGS" : ["warcraft", "raiding"] }