2021-07-19 02:26:35 +01:00
import os # OS Locations
import yaml # YAML parser for Bot config files
import asyncio # Discord Py Dependency
import discord # Main Lib
from discord . ext import commands # Commands module
from discord_slash import SlashCommand , SlashContext , cog_ext , utils # Slash Command Library
from discord_slash . utils . manage_commands import create_choice , create_option , create_permission # Slash Command features
from discord_slash . model import SlashCommandPermissionType
from discord_slash . client import SlashCommand
import re
2021-07-19 15:30:04 +01:00
from bot import configFile , yaml_load , yaml_dump , reloadCog , cogsDir , unloadCog , dataFile , lookupFile , gmFile
2021-07-19 02:26:35 +01:00
2021-07-19 15:30:04 +01:00
class GameManagement ( commands . Cog , name = ' Game Management ' ) :
2021-07-19 02:26:35 +01:00
def __init__ ( self , client ) :
self . client = client
2021-07-19 15:30:04 +01:00
conf = yaml_load ( configFile )
lookup = yaml_load ( lookupFile )
data = yaml_load ( dataFile )
guild_ids = [ int ( x ) for x in list ( lookup ) ]
### Move delete, Modify, and Reset commands to a separate secondary cog to enable when games exist?
2021-07-19 02:26:35 +01:00
@cog_ext.cog_subcommand (
base = ' game ' ,
# subcommand_group='',
name = ' delete ' ,
description = ' Deletes a game role and accompanying category, text, voice channels, and data. ' ,
# 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 representing the game you want to interact with. ' ,
option_type = 8 ,
required = True
)
]
)
async def _game_delete ( self , ctx : SlashContext , game_role : discord . Role ) :
await ctx . channel . trigger_typing ( )
conf = yaml_load ( configFile )
data = yaml_load ( dataFile )
2021-07-19 15:30:04 +01:00
gms = yaml_load ( gmFile )
2021-07-19 02:26:35 +01:00
lookup = yaml_load ( lookupFile )
guildStr = str ( ctx . guild . id )
if str ( game_role . id ) not in lookup [ guildStr ] :
2021-07-19 15:30:04 +01:00
await ctx . send ( f ' ```This is not a valid game role. Please mention a role that is associated with a game.``` ' )
2021-07-19 02:26:35 +01:00
return
2021-07-19 15:30:04 +01:00
game_title = lookup [ guildStr ] [ str ( game_role . id ) ] [ ' game_title ' ]
2021-07-19 02:26:35 +01:00
time = lookup [ guildStr ] [ str ( game_role . id ) ] [ ' time ' ]
c = ctx . guild . get_channel ( lookup [ guildStr ] [ str ( game_role . id ) ] [ ' category ' ] )
2021-07-19 15:30:04 +01:00
gm = lookup [ guildStr ] [ str ( game_role . id ) ] [ ' gm ' ]
channelsFound = False
2021-07-20 00:44:03 +01:00
del data [ guildStr ] [ time ] [ str ( game_role . id ) ]
2021-07-19 15:30:04 +01:00
if c is not None :
channelsFound = True
for t in ctx . guild . text_channels :
if t . category == c :
await t . delete ( reason = f ' /game delete command issued by ` { ctx . author . display_name } ` ' )
for v in ctx . guild . voice_channels :
if v . category == c :
await v . delete ( reason = f ' /game delete command issued by ` { ctx . author . display_name } ` ' )
await c . delete ( reason = f ' /game delete command issued by ` { ctx . author . display_name } ` ' )
2021-07-19 02:26:35 +01:00
lookup [ guildStr ] . pop ( str ( game_role . id ) , None )
2021-07-19 15:30:04 +01:00
gm_m = await ctx . guild . fetch_member ( gm )
output = f ' The game ` { game_title } ` for timeslot ` { conf [ guildStr ] [ " timeslots " ] [ time ] } ` and with GM ` { gm_m . display_name } ` has been deleted. '
if channelsFound :
output = ' ' . join ( [ output , ' All associated text, voice, and category channels have been deleted. ' ] )
else :
output = ' ' . join ( [ output , ' No associated text, voice, or category channels were found. Please delete them manually if they still persist. ' ] )
await ctx . send ( f ' ``` { output } ``` ' )
2021-07-19 02:26:35 +01:00
await game_role . delete ( reason = f ' /game delete command issued by ` { ctx . author . display_name } ` ' )
2021-07-19 15:30:04 +01:00
gms [ guildStr ] [ str ( gm ) ] . remove ( game_role . id )
if not gms [ guildStr ] [ str ( gm ) ] :
gms [ guildStr ] . pop ( str ( gm ) )
2021-07-19 02:26:35 +01:00
yaml_dump ( lookup , lookupFile )
yaml_dump ( data , dataFile )
2021-07-19 15:30:04 +01:00
yaml_dump ( gms , gmFile )
if not any ( [ x for x in yaml_load ( lookupFile ) . values ( ) ] ) :
unloadCog ( f ' ./ { cogsDir } /slashcommands/secondary/game_management.py ' )
await self . client . slash . sync_all_commands ( )
2021-07-19 21:22:25 +01:00
@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 (
2021-07-20 00:44:03 +01:00
name = ' current_players ' ,
description = ' The number players currently in the game. ' ,
2021-07-19 21:22:25 +01:00
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 ,
2021-07-20 00:44:03 +01:00
current_players : int = None ,
2021-07-19 21:22:25 +01:00
system : str = None ,
platform : str = None
) :
await ctx . channel . trigger_typing ( )
2021-07-20 00:44:03 +01:00
if all ( x is None for x in [ timeslot , gm , max_players , game_title , min_players , current_players , system , platform ] ) :
2021-07-19 21:22:25 +01:00
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 )
2021-07-20 00:44:03 +01:00
r = game_role
rStr = str ( r . id )
old_time = lookup [ guildStr ] [ rStr ] [ ' time ' ]
time = re . sub ( r " \ W+ " , ' ' , timeslot [ : 9 ] . lower ( ) ) if timeslot else old_time
2021-07-19 21:22:25 +01:00
if guildStr not in lookup :
lookup [ guildStr ] = { }
if guildStr not in data :
data [ guildStr ] = { }
2021-07-20 00:44:03 +01:00
if time not in data [ guildStr ] :
data [ guildStr ] [ time ] = { }
# Command Validation Checks
if rStr 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
2021-07-19 21:22:25 +01:00
if timeslot is not None :
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.``` ' )
2021-07-20 00:44:03 +01:00
return
if any ( x is not None and x < 0 for x in [ min_players , max_players , current_players ] ) :
await ctx . send ( f ' ```You cannot enter negative integers for the number of players.``` ' )
2021-07-19 21:22:25 +01:00
return
2021-07-20 00:44:03 +01:00
if min_players and max_players and min_players > max_players :
await ctx . send ( f ' ```The minimum number of players cannot exceed the maximum number of players.``` ' )
2021-07-19 21:22:25 +01:00
return
2021-07-20 00:44:03 +01:00
if current_players and max_players and current_players > max_players :
await ctx . send ( f ' ```The number of reserved spaces cannot exceed the maximum number of players.``` ' )
2021-07-19 21:22:25 +01:00
return
2021-07-20 00:44:03 +01:00
# Infer Old Data
old_data = data [ guildStr ] [ old_time ] [ rStr ] . copy ( )
result = f ' The game { old_data [ " game_title " ] } has been updated. \n '
## Change to the Title and/or Time Slot:
if time != old_time :
result = ' ' . join ( [ result , f " The game ' s time slot has changed from { conf [ guildStr ] [ ' timeslots ' ] [ time ] } to { conf [ guildStr ] [ ' timeslots ' ] [ time ] } . \n " ] )
del data [ guildStr ] [ old_time ] [ rStr ]
if game_title and game_title != old_data [ ' game_title ' ] :
2021-07-19 21:22:25 +01:00
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
2021-07-20 00:44:03 +01:00
result = ' ' . join ( [ result , f " The game ' s title has been updated to { game_title } \n " ] )
game_title = old_data [ ' game_title ' ] if not game_title else game_title
if time != old_time or game_title != old_data [ ' game_title ' ] :
result = ' ' . join ( [ result , f " The names of the game role and channel categories have been changed to match the updated { ' timeslot ' if time != old_time and game_title == old_data [ ' game_title ' ] else ' game title ' if game_title != old_data [ ' game_title ' ] and time == old_time else ' time slot and game title ' } . \n " ] )
# Update Role
await r . edit (
mentionable = True ,
permissions = discord . Permissions . none ( ) ,
reason = f ' /game modify command issued by ` { ctx . author . display_name } ` ' ,
colour = discord . Colour . green ( )
)
# Update GM
if gm and gm . id != old_data [ ' gm ' ] :
result = ' ' . join ( [ result , f " The GM has been updated to { gm . display_name } . \n " ] )
gms [ guildStr ] [ str ( old_data [ ' gm ' ] ) ] . remove ( r . id )
gm = await ctx . guild . fetch_member ( old_data [ ' gm ' ] ) if not gm else gm
if r not in gm . roles :
await gm . add_roles ( r )
# Update Category
cExists = False
2021-07-19 21:22:25 +01:00
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
)
}
2021-07-20 00:44:03 +01:00
c_id = lookup [ guildStr ] [ rStr ] [ ' category ' ]
c = discord . utils . get ( ctx . guild . categories , id = c_id )
t_id = lookup [ guildStr ] [ rStr ] [ ' text_channel ' ]
t = discord . utils . get ( ctx . guild . text_channels , id = t_id )
if not c :
2021-07-19 21:22:25 +01:00
c = await ctx . guild . create_category (
2021-07-20 00:44:03 +01:00
name = f ' { time . upper ( ) } : { game_title } ' ,
2021-07-19 21:22:25 +01:00
overwrites = permissions ,
reason = f ' /game modify command issued by ` { ctx . author . display_name } ` '
)
await c . create_voice_channel (
2021-07-20 00:44:03 +01:00
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 modify command issued by ` { ctx . author . display_name } ` '
2021-07-19 21:22:25 +01:00
)
t = await c . create_text_channel (
2021-07-20 00:44:03 +01:00
name = f ' text: { game_title } ' ,
topic = f ' Default text channel for the game ` { game_title } `` taking place at ` { conf [ guildStr ] [ " timeslots " ] [ time ] } `, with GM ` { gm . display_name } `. ' ,
reason = f ' /game modify command issued by ` { ctx . author . display_name } ` '
2021-07-19 21:22:25 +01:00
)
else :
2021-07-20 00:44:03 +01:00
cExists = True
2021-07-19 21:22:25 +01:00
await c . edit (
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
2021-07-20 00:44:03 +01:00
if not t :
2021-07-19 21:22:25 +01:00
t = await c . create_text_channel (
2021-07-20 00:44:03 +01:00
name = f ' text: { game_title } ' ,
topic = f ' Default text channel for the game ` { game_title } `` taking place at ` { conf [ guildStr ] [ " timeslots " ] [ time ] } `, with GM ` { gm . display_name } `. ' ,
2021-07-19 21:22:25 +01:00
reason = f ' /game modify command issued by ` { ctx . author . display_name } ` '
)
else :
2021-07-20 00:44:03 +01:00
pins = await t . pins ( )
if pins :
hm = discord . utils . find ( lambda x : x . content . startswith ( ' ```Hello ' ) and ' Your game channels for ' in x . content and x . author . id == self . client . user . id , pins )
if hm :
await hm . delete ( )
2021-07-19 21:22:25 +01:00
if not v :
await c . create_voice_channel (
2021-07-20 00:44:03 +01:00
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 } `. ' ,
2021-07-19 21:22:25 +01:00
reason = f ' /game modify command issued by ` { ctx . author . display_name } ` '
)
2021-07-20 00:44:03 +01:00
# Determine remaining variables
max_players = old_data [ ' max_players ' ] if not max_players else max_players
min_players = old_data [ ' min_players ' ] if not min_players else min_players
current_players = old_data [ ' current_players ' ] if not current_players else current_players
platform = old_data [ ' platform ' ] if not platform else platform
system = old_data [ ' system ' ] if not system else system
result = ' ' . join ( [ ' ``` ' , result , f " The game has been configured with space for { max_players } players (with { current_players if current_players else ' 0 ' } currently occupied). " , ' ``` ' ] )
output = f ' ```Hello { gm . display_name } ! Your game channels for ` { game_title } ` has been updated. \n You 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: { game_title } \n ' ] )
output = ' ' . join ( [ output , f ' GM: { gm . display_name } \n ' ] )
2021-07-19 21:22:25 +01:00
output = ' ' . join ( [ output , f ' Time: { conf [ guildStr ] [ " timeslots " ] [ time ] } \n ' ] )
2021-07-20 00:44:03 +01:00
output = ' ' . join ( [ output , f ' System: { system } \n ' if system is not None else ' ' ] )
output = ' ' . join ( [ output , f ' Max Players: { max_players } \n ' ] )
output = ' ' . join ( [ output , f ' Min Players: { min_players } \n ' if min_players is not None else ' ' ] )
output = ' ' . join ( [ output , f ' Current Players: { current_players if current_players else " 0 " } \n ' ] )
output = ' ' . join ( [ output , f ' Platform: { platform } ``` ' if platform is not None else ' ``` ' ] )
2021-07-19 21:22:25 +01:00
await ctx . send ( result )
o = await t . send ( output )
2021-07-20 00:44:03 +01:00
await o . pin ( reason = f ' /game create command issued by ` { ctx . author . display_name } ` ' )
data [ guildStr ] [ time ] [ rStr ] = {
' game_title ' : game_title ,
' gm ' : gm . id ,
' max_players ' : max_players ,
' min_players ' : min_players ,
' current_players ' : current_players ,
' system ' : system ,
' platform ' : platform ,
2021-07-19 21:22:25 +01:00
' role ' : r . id ,
' category ' : c . id ,
' text_channel ' : t . id ,
' header_message ' : o . id
}
2021-07-20 00:44:03 +01:00
lookup [ guildStr ] [ rStr ] = {
2021-07-19 21:22:25 +01:00
' category ' : c . id ,
2021-07-20 00:44:03 +01:00
' gm ' : gm . id ,
2021-07-19 21:22:25 +01:00
' time ' : time ,
2021-07-20 00:44:03 +01:00
' game_title ' : game_title ,
' text_channel ' : t . id
2021-07-19 21:22:25 +01:00
}
2021-07-20 00:44:03 +01:00
if guildStr not in gms :
gms [ guildStr ] = { }
if str ( gm . id ) not in gms [ guildStr ] :
gms [ guildStr ] [ str ( gm . id ) ] = [ ]
gms [ guildStr ] [ str ( gm . id ) ] . append ( r . id )
2021-07-19 21:22:25 +01:00
yaml_dump ( data , dataFile )
yaml_dump ( lookup , lookupFile )
yaml_dump ( gms , gmFile )
2021-07-19 02:26:35 +01:00
def setup ( client ) :
2021-07-19 15:30:04 +01:00
client . add_cog ( GameManagement ( client ) )