diff --git a/app/cogs/slashcommands/secondary/game_management.py b/app/cogs/slashcommands/secondary/game_management.py index e47be39..c3bb4d4 100644 --- a/app/cogs/slashcommands/secondary/game_management.py +++ b/app/cogs/slashcommands/secondary/game_management.py @@ -88,7 +88,305 @@ class GameManagement(commands.Cog, name='Game Management'): unloadCog(f'./{cogsDir}/slashcommands/secondary/game_management.py') await self.client.slash.sync_all_commands() - + @cog_ext.cog_subcommand( + base='game', + # subcommand_group='', + name='modify', + description='Edit the information of an existing game channel.', + # base_description='Commands for setting up and removing games on the Guild.', + # base_default_permission=False, + # base_permissions=permissions, + # subcommand_group_description='Adds a time slot available to the channel for games.', + guild_ids=guild_ids, + options=[ + create_option( + name='game_role', + description='The role of the game you are trying to edit.', + option_type=8, + required=True, + ), + create_option( + name='timeslot', + description='The new timeslot, if you are changing timeslots.', + option_type=3, + required=False + ), + create_option( + name='gm', + description='The new GM, if the GM is changing.', + option_type=6, + required=False + ), + create_option( + name='max_players', + description='The maximum number of players the game can take.', + option_type=4, + required=False + ), + create_option( + name='game_title', + description='The new title if the title is changing.', + option_type=3, + required=False + ), + create_option( + name='min_players', + description='The minimum number of players the gane can take.', + option_type=4, + required=False + ), + create_option( + name='reserved_spaces', + description='The number of spaces the GM is reserving in the game.', + option_type=4, + required=False + ), + create_option( + name='system', + description='What system the game is using.', + option_type=3, + required=False + ), + create_option( + name='platform', + description='What platform the game will be running on.', + option_type=3, + required=False + ) + ] + ) + async def _game_modify( + self, + ctx:SlashContext, + game_role:discord.Role, + timeslot:str=None, + gm:discord.User=None, + max_players:int=None, + game_title:str=None, + min_players:int=None, + reserved_spaces:int=None, + system:str=None, + platform:str=None + ): + await ctx.channel.trigger_typing() + if all(x is None for x in [timeslot, gm, max_players, game_title, min_players, reserved_spaces, system, platform]): + await ctx.send(f'```No parameters have been entered to modify the game.```') + return + conf = yaml_load(configFile) + data = yaml_load(dataFile) + gms = yaml_load(gmFile) + lookup = yaml_load(lookupFile) + guildStr = str(ctx.guild.id) + if guildStr not in lookup: + lookup[guildStr] = {} + if guildStr not in data: + data[guildStr] = {} + r = game_role + if timeslot is not None: + time = re.sub(r"\W+",'', timeslot[:9].lower()) + if time not in conf[guildStr]['timeslots']: + await ctx.send(f'```Time code `{timeslot}` is not recognised. Please enter a valid time code to register the game. use `/config timeslots list` to get a list of valid time codes.```') + return + if time not in data[guildStr]: + data[guildStr][time] = [] + else: + time = lookup[guildStr][str(r.id)]['time'] + g_title = game_title if game_title is not None else lookup[guildStr][str(r.id)]['game_title'] + if str(r.id) not in lookup[guildStr]: + await ctx.send(f'```This is not a valid game role. Please mention a role that is associated with a game.```') + return + if 'roles' not in conf[guildStr]: + conf[guildStr]['roles'] = {} + if 'bot' not in conf[guildStr]['roles']: + await ctx.send(f'```\`Bot` role for guild `{ctx.guild.name}` has not been defined. Cannot configure game.```') + return + if 'timeslots' not in conf[guildStr]: + conf[guildStr]['timeslots'] = {} + if any(x is not None and x < 0 for x in [min_players, max_players, reserved_spaces]): + await ctx.send(f'```You cannot enter negative integers for the number of players.```') + return + if min_players is not None and max_players is not None: + if min_players > max_players: + await ctx.send(f'```The minimum number of players cannot exceed the maximum number of players.```') + return + if reserved_spaces is not None and max_players is not None: + if reserved_spaces > max_players: + await ctx.send(f'```The number of reserved spaces cannot exceed the maximum number of players.```') + return + if game_title is not None: + if game_title in [x['game_title'] for x in lookup[guildStr].values()] and time in [x['time'] for x in lookup[guildStr].values()]: + await ctx.send(f'```The target game `{game_title}` has already been created for the time slot `{conf[guildStr]["timeslots"][time]}`. Please avoud duplicates, or use the `modify` sub-command to edit the existing game.```') + return + mc = gm if gm is not None else ctx.guild.get_member(lookup[guildStr][str(r.id)]['gm']) + #### Retrieve Old Values + oldTime = lookup[guildStr][str(r.id)]['time'] + for g in data[guildStr][oldTime]: + if game_role.id in g.values(): + oldDict = g.copy() + break + gDict = { + 'game_title': game_title, + 'gm': mc.id, + 'max_players': max_players, + 'min_players': min_players, + 'current_players': reserved_spaces, + 'system': system, + 'platform': platform, + # 'role': r.id, + # 'category': c.id, + # 'text_channel': t.id, + # 'header_message': o.id + } + d = [k for k in gDict if gDict[k] != oldDict[k] and gDict[k] != None] + result = f'The game {g_title} has been updated.\n' + if time != oldTime: + for g in list(data[guildStr][oldTime]): + if game_role.id in g.values(): + data[guildStr][oldTime].remove(g) + break + data[guildStr][oldTime].pop(str(r.id), None) + result = ''.join([result,f'The time slot for the game has been updated to {conf[guildStr]["timeslots"][time]}.\n']) + if 'gm' in d: + if r not in mc.roles: + await mc.add_roles(r) + if guildStr not in gms: + gms[guildStr] = {} + if str(mc.id) not in gms[guildStr]: + gms[guildStr][str(gm.id)] = [] + gms[guildStr][str(mc.id)].append(r.id) + gms[guildStr][str(lookup[guildStr][str(r.id)]['gm'])].remove(r.id) + if not gms[guildStr][str(lookup[guildStr][str(r.id)]['gm'])]: + gms[guildStr].pop(str(lookup[guildStr][str(r.id)]['gm']), None) + result = ''.join([result,f'The GM has been updated to {mc.display_name}.\n']) + c = await self.client.fetch_channel(lookup[guildStr][str(r.id)]['category']) + permissions = { + ctx.guild.default_role: discord.PermissionOverwrite(read_messages=False), + r: discord.PermissionOverwrite(read_messages=True), + ctx.guild.get_role(conf[guildStr]['roles']['bot']): discord.PermissionOverwrite(read_messages=True), + gm: discord.PermissionOverwrite( + read_messages=True, + manage_messages=True, + manage_channels=True, + manage_permissions=True, + priority_speaker=True, + move_members=True, + mute_members=True, + deafen_members=True + ) + } + print('So far so good.') + if time != oldTime or 'game_title' in d: + await r.edit( + name=f'{time.upper()}: {g_title}', + reason=f'/game modify command issued by `{ctx.author.display_name}`', + mentionable=True, + permissions=discord.Permissions.none(), + ) + result = ''.join([result,f'The names of the role and channel category have been updated to match the new {"game title" if "game_title" in d and time == oldTime else "time slot" if "game_title" not in d and time != oldTime else "game title and time slot"}.\n']) + if c is None: + c = await ctx.guild.create_category( + name=f'{time.upper()}: {g_title}', + overwrites=permissions, + reason=f'/game modify command issued by `{ctx.author.display_name}`' + ) + await c.create_voice_channel( + name=f'voice: {g_title}', + topic=f'Default voice channel for the game `{g_title}`` taking place at `{conf[guildStr]["timeslots"][time]}`, with GM `{mc.display_name}`.', + reason=f'/game create command issued by `{ctx.author.display_name}`' + ) + t = await c.create_text_channel( + name=f'text: {g_title}', + topic=f'Default text channel for the game `{g_title}`` taking place at `{conf[guildStr]["timeslots"][time]}`, with GM `{mc.display_name}`.', + reason=f'/game create command issued by `{ctx.author.display_name}`' + ) + else: + print(type(c)) + await c.edit( + name=f'{time.upper()}: {g_title}', + overwrites=permissions, + reason=f'/game modify command issued by `{ctx.author.display_name}`', + ) + tPos = len(ctx.guild.channels) + t = None + v = False + for tc in c.text_channels: + if tc.position <= tPos: + t, tPos = tc, tc.position + await t.edit( + sync_permissions=True, + reason=f'/game modify command issued by `{ctx.author.display_name}`' + ) + for vc in c.voice_channels: + await vc.edit( + sync_permissions=True, + reason=f'/game modify command issued by `{ctx.author.display_name}`' + ) + v = True + if t is None: + t = await c.create_text_channel( + name=f'text: {g_title}', + topic=f'Default text channel for the game `{g_title}`` taking place at `{conf[guildStr]["timeslots"][time]}`, with GM `{mc.display_name}`.', + reason=f'/game modify command issued by `{ctx.author.display_name}`' + ) + else: + hm = await t.fetch_message(oldDict['header_message']) + if hm is not None: + await hm.delete() + else: + pins = await t.pins + if pins: + hm = discord.utils.find(lambda x: x.text.startswith('```Hello ' and 'Your game channels for ' in x.text and x.author.id == self.client.user.id), pins) + if hm is not None: + await hm.delete() + if not v: + await c.create_voice_channel( + name=f'voice: {g_title}', + topic=f'Default voice channel for the game `{g_title}`` taking place at `{conf[guildStr]["timeslots"][time]}`, with GM `{mc.display_name}`.', + reason=f'/game modify command issued by `{ctx.author.display_name}`' + ) + result = ''.join([result,f'The game has space for {max_players} players (with {reserved_spaces if reserved_spaces is not None else str(0)} currently occupied).\n']) + sys = system if system is not None else oldDict['system'] + max_p = max_players if max_players is not None else oldDict['max_players'] + min_p = min_players if min_players is not None else oldDict['min_players'] + cur_p = reserved_spaces if reserved_spaces is not None else oldDict['current_players'] + plat = platform if platform is not None else oldDict['platform'] + output = f'```Hello {mc.display_name}! Your game channels for `{g_title}` have been created.\nYou can ping your players or edit the game settings by interacting with the game role through the Bot commands. Have fun!```\n' + output = ''.join([output,f'```Game Title: {g_title}\n']) + output = ''.join([output,f'GM: {mc.display_name}\n']) + output = ''.join([output,f'Time: {conf[guildStr]["timeslots"][time]}\n']) + output = ''.join([output,f'System: {sys}\n'if sys is not None else '']) + output = ''.join([output,f'Max Players: {max_p}\n']) + output = ''.join([output,f'Min Players: {min_p}\n'if min_p is not None else '']) + output = ''.join([output,f'Current Players: {cur_p}\n' if cur_p is not None else '0\n']) + output = ''.join([output,f'Platform: {plat}```' if plat is not None else '```']) + result = ''.join(['```',result,f'```\n{mc.mention} {r.mention} {t.mention}']) + await ctx.send(result) + o = await t.send(output) + await o.pin(reason=f'/game modift command issued by `{ctx.author.display_name}`') + gDict = { + 'game_title': g_title, + 'gm': mc.id, + 'max_players': max_p, + 'min_players': min_p, + 'current_players': cur_p, + 'system': sys, + 'platform': plat, + 'role': r.id, + 'category': c.id, + 'text_channel': t.id, + 'header_message': o.id + } + if time != oldTime: + data[guildStr][time].append(gDict) + lookup[guildStr][str(r.id)] = { + 'category': c.id, + 'gm': mc.id, + 'time': time, + 'game_title': g_title + } + yaml_dump(data,dataFile) + yaml_dump(lookup,lookupFile) + yaml_dump(gms,gmFile) def setup(client): client.add_cog(GameManagement(client)) \ No newline at end of file diff --git a/app/cogs/slashcommands/secondary/game_setup.py b/app/cogs/slashcommands/secondary/game_setup.py index 25efb35..0a2c974 100644 --- a/app/cogs/slashcommands/secondary/game_setup.py +++ b/app/cogs/slashcommands/secondary/game_setup.py @@ -56,7 +56,7 @@ class GameSetup(commands.Cog, name='Game Setup'): ), create_option( name='max_players', - description='The maximum number of players the GM can take.', + description='The maximum number of players the game can take.', option_type=4, required=True ), @@ -68,7 +68,7 @@ class GameSetup(commands.Cog, name='Game Setup'): ), create_option( name='min_players', - description='The minimum number of players the GM can take.', + description='The minimum number of players the game can take.', option_type=4, required=False ), @@ -121,11 +121,16 @@ class GameSetup(commands.Cog, name='Game Setup'): if time not in conf[guildStr]['timeslots']: await ctx.send(f'```Time code `{timeslot}` is not recognised. Please enter a valid time code to register the game. use `/config timeslots list` to get a list of valid time codes.```') return - if min_players > max_players: - await ctx.send(f'```The minimum number of players cannot exceed the maximum number of players.```') - return - if reserved_spaces > max_players: - await ctx.send(f'```The number of reserved spaces cannot exceed the maximum number of players.```') + if min_players is not None: + if min_players > max_players: + await ctx.send(f'```The minimum number of players cannot exceed the maximum number of players.```') + return + if reserved_spaces is not None: + if reserved_spaces > max_players: + await ctx.send(f'```The number of reserved spaces cannot exceed the maximum number of players.```') + return + if any(x is not None and x < 0 for x in [min_players, max_players, reserved_spaces]): + await ctx.send(f'```You cannot enter negative integers for the number of players.```') return if guildStr not in lookup: lookup[guildStr] = {} @@ -144,6 +149,7 @@ class GameSetup(commands.Cog, name='Game Setup'): reason=f'/game create command issued by `{ctx.author.display_name}`', mentionable=True, permissions=discord.Permissions.none(), + colour=discord.Colour.green() ) else: rExists = True @@ -151,8 +157,10 @@ class GameSetup(commands.Cog, name='Game Setup'): mentionable=True, permissions=discord.Permissions.none(), reason=f'/game create command issued by `{ctx.author.display_name}`', + colour=discord.Colour.green() ) - await gm.add_roles(r) + if r not in gm.roles: + await gm.add_roles(r) permissions = { ctx.guild.default_role: discord.PermissionOverwrite(read_messages=False), r: discord.PermissionOverwrite(read_messages=True), @@ -217,20 +225,21 @@ class GameSetup(commands.Cog, name='Game Setup'): pins = await t.pins if pins: hm = discord.utils.find(lambda x: x.text.startswith('```Hello ' and 'Your game channels for ' in x.text and x.author.id == self.client.user.id), pins) - await hm.delete() + if hm is not None: + await hm.delete() if not v: await c.create_voice_channel( name=f'voice: {game_title}', topic=f'Default voice channel for the game `{game_title}`` taking place at `{conf[guildStr]["timeslots"][time]}`, with GM `{gm.display_name}`.', reason=f'/game create command issued by `{ctx.author.display_name}`' ) - result = f'Game `{game_title}` has been created for timeslot `{conf[guildStr]["timeslots"][time]}`` with GM `{gm.display_name}` and space for {max_players} players (with spaces {reserved_spaces} currently occupied).\n' + result = f'Game `{game_title}` has been created for timeslot `{conf[guildStr]["timeslots"][time]}`` with GM `{gm.display_name}` and space for {max_players} players (with {reserved_spaces if reserved_spaces is not None else str(0)} currently occupied).\n' if not rExists: result = ''.join([result,f'There was already a role that matched the game, so that role has been reconfigured.\n\nNote: Editing this role will synchronise changes with the game channels, and deleting the role will delete the game and all its data.\n\n']) else: result = ''.join([result,f'A role for the game has been created.\n']) if not cExists: - result = ''.join([result,f'There was already a channel category that matched the game, so it has been reconfigured with the appropriate permissions and text and voice channels.\n\nNote: Editing this role will synchronise changes with the game channels, and deleting the role will delete the game and all its data.\n\n']) + result = ''.join([result,f'There was already a channel category that matched the game, so it has been reconfigured with the appropriate permissions and text and voice channels.\n']) else: result = ''.join([result,f'A channel category with the appropriate text and voice channels has been created.\n']) result = ''.join(['```',result,f'```\n{gm.mention} {r.mention} {t.mention}']) @@ -246,24 +255,26 @@ class GameSetup(commands.Cog, name='Game Setup'): await ctx.send(result) o = await t.send(output) await o.pin(reason=f'/game create command issued by `{ctx.author.display_name}`') - gDict = {} - gDict['game_title'] = game_title - gDict['gm'] = gm.id - gDict['max_players'] = max_players - gDict['min_players'] = min_players - gDict['current_players'] = reserved_spaces - gDict['system'] = system - gDict['platform'] = platform - gDict['role'] = r.id - gDict['category'] = c.id - gDict['text_channel'] = t.id - gDict['header_message'] = o.id + gDict = { + 'game_title': game_title, + 'gm': gm.id, + 'max_players': max_players, + 'min_players': min_players, + 'current_players': reserved_spaces, + 'system': system, + 'platform': platform, + 'role': r.id, + 'category': c.id, + 'text_channel': t.id, + 'header_message': o.id + } data[guildStr][time].append(gDict) - lookup[guildStr][str(r.id)] = {} - lookup[guildStr][str(r.id)]['category'] = c.id - lookup[guildStr][str(r.id)]['gm'] = gm.id - lookup[guildStr][str(r.id)]['time'] = time - lookup[guildStr][str(r.id)]['game_title'] = game_title + lookup[guildStr][str(r.id)] = { + 'category': c.id, + 'gm': gm.id, + 'time': time, + 'game_title': game_title + } if guildStr not in gms: gms[guildStr] = {} if str(gm.id) not in gms[guildStr]: diff --git a/app/data/config.yml b/app/data/config.yml index 76111cc..29f0ee8 100644 --- a/app/data/config.yml +++ b/app/data/config.yml @@ -21,3 +21,4 @@ student: 866645394699714570 timeslots: sunaft: Sunday Afternoon 1 pm + suneve: Sunday Evening