import os import math import discord from discord.ext import commands from redbot.core.utils.chat_formatting import pagify from recbot.core.utils.chat_formatting import box from .utils.dataIO import dataIO #TODO from redbot.core import checks from random import randint # 0 - Robin, 1 - Single, 2 - Double, 3 - Triple, 4 - Guarentee, 5 - Compass T_TYPES = {0: "Round Robin", 1: "Single Elimination", 2: "Double Elimination", 3: "Triple Elimination", 4: "3 Game Guarentee", 5: "Compass Draw"} class Fight: """Cog for organizing fights""" def __init__(self, bot): self.bot = bot self.path = "data/Fox-Cogs/fight/" self.file_path = "data/Fox-Cogs/fight/fight.json" self.the_data = dataIO.load_json(self.file_path) def save_data(self): """Saves the json""" dataIO.save_json(self.file_path, self.the_data) # ************************v3 Shit************************ # def check(m): #Check Message from author # return m.author == ctx.author and m.channel == ctx.channel # ************************Fight command group start************************ @commands.group(no_pm=True) async def fight(self, ctx): """Participate in active fights!""" guild = ctx.message.guild if not self._activefight(guild.id): await self.bot.say("No tournament currently running!") else: await self.bot.say("Current tournament ID: " + self._activefight(guild.id)) if ctx.invoked_subcommand is None: await self.bot.send_cmd_help(ctx) # await self.bot.say("I can do stuff!") @fight.command(name="join") async def fight_join(self, ctx, user: discord.Member=None): """Join the active fight""" guild = ctx.message.guild if not user: user = ctx.message.author currFight = self._getcurrentfight(guild.id) tID = self._activefight(guild.id) if not currFight: await self.bot.say("No tournament currently running!") return if not currFight["OPEN"]: await self.bot.say("Tournament currently not accepting new players") return if self._infight(guild.id, tID, user.id): await self.bot.say("You are already in this tournament!") return currFight["PLAYERS"].append(user.id) self.save_data() await self.bot.say("User has been added to tournament") @fight.command(name="score") async def fight_score(self, ctx, tID=None, score1=None, score2=None): """Enters score for current match, or for passed tournament ID""" # guild = ctx.message.guild # user = ctx.message.author currFight = self._getcurrentfight(guild.id) if not currFight: await self.bot.say("No tournament currently running!") return if not tID: tID = self._activefight(guild.id) if not self._infight(guild.id, tID, author.id): await self.bot.say("You are not in a current tournament") return mID = self._parseuser(guild.id, tID, author.id) if not mID: await self.bot.say("You have no match to update!") return if currFight["RULES"]["TYPE"] == 0: # Round-Robin await self._rr_score(guild.id, tID, mID, author, score1, score2) @fight.command(name="leave") async def fight_leave(self, ctx, tID=None, user: discord.Member=None): """Forfeit your match and all future matches""" # guild = ctx.message.guild if not user: user = author if not tID: tID = self._activefight(guild.id) await self.bot.say("Todo Leave") # @fight.command(name="leaderboard", pass_context=True) # async def fight_leaderboard(self, ctx, ctag, ckind="Unranked", irank=0): # await self.bot.say("Todo Leaderboard") # """Adds clan to grab-list""" @fight.group(name="bracket") async def fight_bracket(self, ctx, tID): """Shows your current match your next opponent, run [p]fight bracket full to see all matches""" await self.bot.say("Todo Bracket") @fight_bracket.command(name="full") async def fight_bracket_full(self, ctx, tID): """Shows the full bracket""" await self.bot.say("Todo Bracket Full") # **********************Fightset command group start********************* # def fightsetdec(func): # async def decorated(self, ctx, *args, **kwargs): # guild = ctx.message.guild # await func(self, ctx, guild, *args, **kwargs) # return decorated @commands.group(no_pm=True, aliases=['setfight']) @checks.mod_or_permissions(administrator=True) async def fightset(self, ctx): """Admin command for starting or managing tournaments""" # guild = ctx.message.guild if guild.id not in self.the_data: self.the_data[guild.id] = { "CURRENT": None, "TOURNEYS": {}, "SETTINGS": { "SELFREPORT": True, "REPORTCHNNL": None, "ANNOUNCECHNNL": None, "ADMIN": None }, "SRTRACKER": { "ROUND": None, "CHNNLS": None, }, "EMOJI": { "NUMS": [], "UNDO": None, "APPR": None } } self.save_data() if ctx.invoked_subcommand is None: await self.bot.send_cmd_help(ctx) # await self.bot.say("I can do stuff!") @fightset.command(name="bestof") async def fightset_bestof(self, ctx, incount, tID=None): """Adjust # of games played per match. Must be an odd number""" # guild = ctx.message.guild if not tID and not self._activefight(guild.id): await self.bot.say("No active fight to adjust") return if not tID: tID = self._activefight(guild.id) try: num = int(incount) except: await self.bot.say("That is not a number") return if num % 2 != 1: await self.bot.say("Must be an odd number") return if num < 1: await self.bot.say("Must be greater than 0, idiot") return if num > 17: await self.bot.say("I can't go that high! Max 17") return self._getfight(guild.id, tID)["RULES"]["BESTOF"] = num self.save_data() await self.bot.say("Tourney ID "+tID+" is now Best of "+str(num)) @fightset.command(name="bestoffinal") async def fightset_bestoffinal(self, ctx, incount, tID=None): """Adjust # of games played in finals. Must be an odd number (Does not apply to tournament types without finals, such as Round Robin)""" #guild = ctx.message.guild if not tID and not self._activefight(guild.id): await self.bot.say("No active fight to adjust") return if not tID: tID = self._activefight(guild.id) try: num = int(incount) except: await self.bot.say("That is not a number") return if num % 2 != 1: await self.bot.say("Must be an odd number") return if num < 1: await self.bot.say("Must be greater than 0, idiot") return self._getfight(guild.id, tID)["RULES"]["BESTOFFINAL"] = num self.save_data() await self.bot.say("Tourney ID "+tID+" is now Best of "+str(num)) @fightset.command(name="current") async def fightset_current(self, ctx, tID): """Sets the current tournament to passed ID""" #guild = ctx.message.guild aFight = self._getfight(guild.id, tID) if not aFight: await self.bot.say("No tourney found with that ID") return self.the_data[guild.id]["CURRENT"] = tID self.save_data() await self.bot.say("Current tournament set to "+tID) @fightset.command(name="list") async def fightset_list(self, ctx): """Lists all current and past fights""" #guild = ctx.message.guild for page in pagify(str(self.the_data[guild.id]["TOURNEYS"])): await self.bot.say(box(page)) await self.bot.say("Done") @fightset.command(name="open") async def fightset_open(self, ctx): """Toggles the open status of current tournament""" #guild = ctx.message.guild if not self._activefight(guild.id): await self.bot.say("No active fight to adjust") return currFight = self._getcurrentfight(guild.id) currFight["OPEN"] = not currFight["OPEN"] self.save_data() await self.bot.say("Tournament Open status is now set to: " + str(currFight["OPEN"])) @fightset.command(name="name") async def fightset_name(self, ctx, inname, tID=None): """Renames the tournament""" #guild = ctx.message.guild if not tID and not self._activefight(guild.id): await self.bot.say("No active fight to adjust") return if not tID: tID = self._activefight(guild.id) self._getfight(guild.id, tID)["NAME"] = inname self.save_data() await self.bot.say("Tourney ID "+tID+" is now called "+self._getfight(guild.id, tID)["NAME"]) @fightset.command(name="start") async def fightset_start(self, ctx): """Starts the current tournament, must run setup first""" def check(m): #Check Message from author return m.author == author and m.channel == channel #guild = ctx.message.guild #author = ctx.message.author currFight = self._getcurrentfight(guild.id) tID = self._activefight(guild.id) if not tID: await self.bot.say("No current fight to start") return if currFight["TYPEDATA"]: # Empty dicionary {} resolves to False await self.bot.say("Looks like this tournament has already started.\nDo you want to delete all match data and restart? (yes/no)") # answer = await self.bot.wait_for_message(timeout=120, author=author) try: answer = await self.bot.wait_for('message', check=check, timeout=120) except asyncio.TimeoutError: await ctx.send("Cancelled due to timeout") if not answer.content and answer.content.upper() in ["YES", "Y"]: await self.bot.say("Cancelled") return currFight["OPEN"] = False # first close the tournament self.save_data() if currFight["RULES"]["TYPE"] == 0: # Round-Robin await self._rr_start(guild.id, tID) @fightset.command(name="setup") async def fightset_setup(self, ctx): """Setup a new tournament! Default settings are as follows Name: Tourney # (counts from 0) Best of: 1 Best of (final): 1 Self Report: True Type: 0 (Round Robin)""" #guild = ctx.message.guild currServ = self.the_data[guild.id] tID = str(len(currServ["TOURNEYS"])) # Can just be len without +1, tourney 0 makes len 1, tourney 1 makes len 2, etc currServ["CURRENT"] = tID currServ["TOURNEYS"][tID] = { "PLAYERS": [], "NAME": "Tourney "+str(tID), "RULES": {"BESTOF": 1, "BESTOFFINAL": 1, "TYPE": 0}, "TYPEDATA": {}, "OPEN": False, "WINNER": None } self.save_data() await self.bot.say("Tournament has been created!\n\n" + str(currServ["TOURNEYS"][tID])) await self.bot.say("Adjust settings as necessary, then open the tournament with [p]fightset toggleopen") @fightset.command(name="stop") async def fightset_stop(self, ctx): """Stops current tournament""" # guild = ctx.message.guild if not self._activefight(guild.id): await self.bot.say("No active fight to adjust") return # author = ctx.message.author currServ = self.the_data[guild.id] await self.bot.say("Current fight ID is "+str(currServ["CURRENT"])+"\nOkay to stop? (yes/no)") answer = await self.bot.wait_for_message(timeout=120, author=author) if not answer.content.upper() in ["YES", "Y"]: await self.bot.say("Cancelled") return currServ["CURRENT"] = None self.save_data() await self.bot.say("Fight has been stopped") # ***************************Fightset_guild command group start************************** @fightset.group(name="guild") async def fightset_guild(self, ctx): """Adjust guild wide settings""" if ctx.invoked_subcommand is None: await self.bot.send_cmd_help(ctx) @fightset_guild.command(name="selfreport") async def fightset_guild_selfreport(self, ctx): """Toggles the ability to self-report scores for all tournaments""" #guild = ctx.message.guild settings = self._getsettings(guild.id) settings["SELFREPORT"] = not settings["SELFREPORT"] self.save_data() await self.bot.say("Self-Reporting ability is now set to: " + str(settings["SELFREPORT"])) @fightset_guild.command(name="reportchnnl") async def fightset_guild_reportchnnl(self, ctx, channel: discord.Channel=None): """Set the channel for self-reporting""" #guild = ctx.message.guild settings = self._getsettings(guild.id) settings["REPORTCHNNL"] = channel.id self.save_data() await self.bot.say("Self-Reporting Channel is now set to: " + channel.mention) @fightset_guild.command(name="announcechnnl") async def fightset_guild_announcechnnl(self, ctx, channel: discord.Channel=None): """Set the channel for tournament announcements""" #guild = ctx.message.guild settings = self._getsettings(guild.id) settings["ANNOUNCECHNNL"] = channel.id self.save_data() await self.bot.say("Announcement Channel is now set to: " + channel.mention) @fightset_guild.command(name="setadmin") async def fightset_guild_setadmin(self, ctx, role: discord.Role=None): """Chooses the tournament-admin role. CAREFUL: This grants the ability to override self-reported scores!""" #guild = ctx.message.guild settings = self._getsettings(guild.id) settings["ADMIN"] = role.id self.save_data() await self.bot.say("Tournament Admin role is now set to: " + role.mention) # **********************Private command group start********************* def _guildsettings(self, guildid): """Returns the dictionary of guild settings""" return self.the_data[guildid]["SETTINGS"] def _messagetracker(self, guildid): """Returns the dictionary of message tracking""" return self.the_data[guildid]["SRTRACKER"] def _activefight(self, guildid): """Returns id for active fight, or None if no active fight""" return self.the_data[guildid]["CURRENT"] def _infight(self, guildid, tID, userid): """Checks if passed member is already in the tournament""" return userid in self.the_data[guildid]["TOURNEYS"][tID]["PLAYERS"] async def _embed_tourney(self, guildid, tID): """Prints a pretty embed of the tournament""" await self.bot.say("_placeholder Todo") async def _comparescores(self): """Checks user submitted scores for inconsistancies""" await self.bot.say("_comparescores Todo") def _parseuser(self, guildid, tID, userid): """Finds user in the tournament""" if self._getfight(guildid, tID)["RULES"]["TYPE"] == 0: # RR return self._rr_parseuser(guildid, tID, userid) return False def _get_team(self, guildid, teaminfo): """Team info is a list of userid's. Returns a list of user objects""" outlist = [] for player in teaminfo: outlist.append(self._get_user_from_id(guildid, player)) return outlist def _getsettings(self, guildid): return self.the_data[guildid]["SETTINGS"] async def _get_message_from_id(self, channelid, messageid): return await self.bot.get_message(self._get_channel_from_id(channelid), messageid) def _get_message_from_id_recent(self, messageid): return discord.utils.get(self.bot.messages, id=messageid) def _get_channel_from_id(self, guildid, channelid): guild = self._get_guild_from_id(guildid) return discord.utils.get(guild.channels, id=channelid) def _get_user_from_id(self, guildid, userid): guild = self._get_guild_from_id(guildid) return discord.utils.get(guild.members, id=userid) def _get_guild_from_id(self, guildid): return discord.utils.get(self.bot.guilds, id=guildid) def _getfight(self, guildid, tID): return self.the_data[guildid]["TOURNEYS"][tID] def _getcurrentfight(self, guildid): if not self._activefight(guildid): return None return self._getfight(guildid, self._activefight(guildid)) # *********** References to "TYPEDATA" must be done per tournament mode (Below this line) ******* # **********************Single Elimination*************************** async def _elim_setup(self, tID): await self.bot.say("Elim setup todo") async def _elim_start(self, tID): await self.bot.say("Elim start todo") async def _elim_update(self, matchID, ): await self.bot.say("Elim update todo") # **********************Round-Robin********************************** def _rr_parseuser(self, guildid, tID, userid): theT = self._getfight(guildid, tID) matches = theT["TYPEDATA"]["MATCHES"] schedule = theT["TYPEDATA"]["SCHEDULE"] for round in schedule: for mID in round: teamnum = self._rr_matchperms(guildid, tID, userid, mID) if teamnum and not self._rr_matchover(guildid, tID, mID): # User is in this match, check if it's done yet return mID return False # All matches done or not in tourney def _rr_matchover(self, guildid, tID, mID): theT = self._getfight(guildid, tID) match = theT["TYPEDATA"]["MATCHES"][mID] if (match["SCORE1"] == math.ceil(theT["RULES"]["BESTOF"]/2) or match["SCORE1"] == math.ceil(theT["RULES"]["BESTOF"]/2)): return True return False def _rr_roundover(self, guildid, tID): currFight = self._getfight(guildid, tID) currRound = currFight["TYPEDATA"]["SCHEDULE"][currFight["TYPEDATA"]["ROUND"]] for mID in currRound: if not self._rr_matchover(guildid, tID, mID): return False return True def _rr_matchperms(self, guildid, tID, userid, mID): # if self._get_user_from_id(guildid, userid) # Do an if-admin at start theT = self._getfight(guildid, tID) if userid in theT["TYPEDATA"]["MATCHES"][mID]["TEAM1"]: return 1 if userid in theT["TYPEDATA"]["MATCHES"][mID]["TEAM2"]: return 2 return False def _rr_setup(self, guildid, tID): theT = self._getfight(guildid, tID) theD = theT["TYPEDATA"] get_schedule = self._rr_schedule(theT["PLAYERS"]) theD["SCHEDULE"] = get_schedule[0] theD["MATCHES"] = get_schedule[1] theD["ROUND"] = 0 self.save_data() async def _rr_printround(self, guildid, tID, rID): theT = self._getfight(guildid, tID) theD = theT["TYPEDATA"] # rID starts at 0, so print +1. Never used for computation, so doesn't matter if self._guildsettings(guildid)["ANNOUNCECHNNL"]: await self.bot.send_message( self._get_channel_from_id(guildid, self._guildsettings(guildid)["ANNOUNCECHNNL"]), "Round "+str(rID+1) ) else: await self.bot.say("Round "+str(rID+1)) for mID in theD["SCHEDULE"][rID]: team1 = self._get_team(guildid, theD["MATCHES"][mID]["TEAM1"]) team2 = self._get_team(guildid, theD["MATCHES"][mID]["TEAM2"]) for i in range(len(team1)): if team1[i]: team1[i] = team1[i].mention else: team1[i] = "BYE" for i in range(len(team2)): if team2[i]: team2[i] = team2[i].mention else: team2[i] = "BYE" mention1 = ", ".join(team1) mention2 = ", ".join(team2) outembed=discord.Embed(title="Match ID: " + mID, color=0x0000bf) outembed.add_field(name="Team 1", value=mention1, inline=True) outembed.add_field(name="Team 2", value=mention2, inline=True) outembed.set_footer(text="React your team's score, then your opponents score!") if self._guildsettings(guildid)["REPORTCHNNL"]: message = await self.bot.send_message( self._get_channel_from_id(guildid, self._guildsettings(guildid)["REPORTCHNNL"]), embed=outembed ) else: message = await self.bot.say(embed=outembed) self._messagetracker(guildid)[message.id] = {"TID": tID, "MID": mID, "RID": rID} self.save_data() # await self.bot.say(team1 + " vs " + team2 + " || Match ID: " + match) async def _rr_start(self, guildid, tID): self._rr_setup(guildid, tID) if self._guildsettings(guildid)["ANNOUNCECHNNL"]: await self.bot.send_message( self._get_channel_from_id(guildid, self._guildsettings(guildid)["ANNOUNCECHNNL"]), "**Tournament is Starting**" ) else: await self.bot.say("**Tournament is Starting**") await self._rr_printround(guildid, tID, 0) async def _rr_score(self, guildid, tID, mID, author, t1points, t2points): theT = self._getfight(guildid, tID) theD = theT["TYPEDATA"] # if t1points and t2points: # theD["MATCHES"][mID]["SCORE1"] = t1points # theD["MATCHES"][mID]["SCORE2"] = t2points # self.save_data() # return if not t1points: await self.bot.say("Entering scores for match ID: " + mID + "\n\n") await self.bot.say("How many points did TEAM1 get?") if self._rr_matchperms(guildid, tID, author.id, mID) == 1: await self.bot.say("*HINT: You are on TEAM1*") answer = await self.bot.wait_for_message(timeout=120, author=author) try: t1points = int(answer.content) except: await self.bot.say("That's not a number!") return if not t2points: await self.bot.say("How many points did TEAM2 get?") if self._rr_matchperms(guildid, tID, author.id, mID) == 2: await self.bot.say("*HINT: You are on TEAM2*") answer = await self.bot.wait_for_message(timeout=120, author=author) try: t2points = int(answer.content) except: await self.bot.say("That's not a number!") return if (t1points == math.ceil(theT["RULES"]["BESTOF"]/2) or t2points == math.ceil(theT["RULES"]["BESTOF"]/2)): theD["MATCHES"][mID]["SCORE1"] = t1points theD["MATCHES"][mID]["SCORE2"] = t2points self.save_data() else: await self.bot.say("Invalid scores, nothing will be updated") return await self.bot.say("Scores have been saved successfully!") # if self._rr_checkround(guildid, tID) def _rr_schedule(self, inlist): """ Create a schedule for the teams in the list and return it""" s = [] # Schedule list outID = {} # Matches firstID = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"] # God dammit this could've been a string if len(inlist) % 2 == 1: inlist = inlist + ["BYE"] for i in range(len(inlist)): mid = int(len(inlist) / 2) l1 = inlist[:mid] l2 = inlist[mid:] l2.reverse() matchLetter = "" j = i while j+1 > 26: matchLetter += firstID[int(j + 1) % 26 - 1] j = (j + 1) / 26 - 1 matchLetter += firstID[int(j+1) % 26-1] matchLetter = matchLetter[::-1] matchID = [] for ix in range(len(l1)): matchID += [matchLetter+str(ix)] rPlayers = list(zip(l1, l2)) TeamCnt = 0 for ID in matchID: outID[ID] = { "TEAM1": [rPlayers[TeamCnt][0]], "TEAM2": [rPlayers[TeamCnt][1]], "SCORE1": 0, "SCORE2": 0, "USERSCORE1": {"SCORE1": 0, "SCORE2": 0}, "USERSCORE2": {"SCORE1": 0, "SCORE2": 0} } TeamCnt += 1 # List of match ID's is now done s += [matchID] # Schedule of matches inlist.insert(1, inlist.pop()) outlist = [[], {}] outlist[0] = s outlist[1] = outID # outlist[0] is list schedule of matches # outlist[1] is dict data of matches return outlist #**************** Socket attempt ******************** async def _on_react(self, reaction, user): """do nothing""" # if not self.the_data["trackmessage"]: # return # # if user == self.bot.user: # return # Don't remove bot's own reactions # message = reaction.message # emoji = reaction.emoji # # if not message.id == self.the_data["trackmessage"]: # return # # if str(emoji) in self.letters: # letter = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"[self.letters.index(str(emoji))] # await self._guessletter(letter, message.channel) # # # if str(emoji) in self.navigate: # if str(emoji) == self.navigate[0]: # await self._reactmessage_am(message) # # if str(emoji) == self.navigate[-1]: # await self._reactmessage_nz(message) async def on_socket_response(self, obj): if obj["t"] != "MESSAGE_REACTION_ADD": return if "emoji" not in obj["d"]: # This reaction is in the messages deque, use other listener return #if message_id not in guildid for #for guildid in self.the_data: # if not self._messagetracker(guildid) message_id = obj["d"]["message_id"] emoji = obj["d"]["emoji"]["name"] user_id = obj["d"]["user_id"] def check_folders(): if not os.path.exists("data/Fox-Cogs"): print("Creating data/Fox-Cogs folder...") os.makedirs("data/Fox-Cogs") if not os.path.exists("data/Fox-Cogs/fight"): print("Creating data/Fox-Cogs/fight folder...") os.makedirs("data/Fox-Cogs/fight") def check_files(): if not dataIO.is_valid_json("data/Fox-Cogs/fight/fight.json"): dataIO.save_json("data/Fox-Cogs/fight/fight.json", {}) def setup(bot): check_folders() check_files() n = Fight(bot) bot.add_cog(n) bot.add_listener(n._on_react, "on_reaction_add")