class WikiaLookup(commands.Cog): def __init__(self, bot): self.bot = bot self.wikia_list = JsonFileObject('wikia.json') @commands.command() async def wikia(self, ctx, *, term=''): '''Searches for and grabs a link to the page of the given title from the set wiki sites.''' if not self.wikia_list.get(str(ctx.guild.id)): await ctx.send(f'No wikias have been set for this guild. Use the `{self.bot.command_prefix}wikialist` command to add some to the list') return if term: async with ctx.channel.typing(): w = 0 log.info(f'retrieve "{term}" from wikia...') for sub in self.wikia_list.get(str(ctx.guild.id)): try: w = wikia_module.page(sub.strip(' []'), term) log.info('page found on {0}...'.format(sub)) break # page found, exit the for loop except: w = 0 log.info(f'page not found in {sub}') if w is not 0: await ctx.send(w.url.replace(' ', '_')) else: await ctx.send(f':sweat: Sorry, I couldnt find a page titled "{term}"...') else: log.info('no search term...') await ctx.send('Use **!wiki <Page Title>** to search and grab a link to the page of that title on the following wiki sites: {}'.format(self.wikia_list[ctx.guild.id])) @commands.command() @commands.has_permissions(administrator=True) async def wikialist(self, ctx, *, wikias=''): '''Set the list of wikias that will be searched using the !wikia command. List wikias in a space separated list''' if wikias: self.wikia_list.update({str(ctx.guild.id): wikias.split(' ')}) self.wikia_list.save() await ctx.send(f'Wikia list updated! :thumbsup: ```{self.wikia_list[str(ctx.guild.id)]}```') else: await ctx.send(f'The following wikias are searched in order until a match is found: ```{self.wikia_list[str(ctx.guild.id)]}```')
class Economy(commands.Cog): def __init__(self, bot): self.bot = bot self.global_economy = JsonFileObject('economy.json') def get_local_tokens(self, ctx): tokens = self.global_economy.get(ctx.server.id) if not tokens: self.global_economy.update(ctx.server.id, {}) tokens = self.global_economy[ctx.server.id] self.global_economy.save() return tokens def get_token(self, ctx, name): tokens = self.get_local_tokens(ctx) token = tokens.get(name) if not token: tokens.update(name, {}) token = tokens[name] token.update('ledger', {}) self.global_economy.save() return token @commands.Cog.listener() async def on_message(self, message): if message.author == self.bot.user \ or message.content.startswith(self.bot.command_prefix): return #detect edge rolls and deduct edge automatically term = re.search(r'[.+!]') @commands.command() @commands.has_permissions(administrator = True) async def economy(self, ctx, name = None, param = None, value = None): '''Manage server currencies, use empty command to list available tokens and properties. Name only to create a new token or list all player balances.''' if not name: msg = '```' for token_name, token in self.get_local_tokens(ctx): msg += token_name + '\n' for key, item in token: if not key == 'ledger': msg += '\t{0}: \t{1}'.format(key, item) msg += '\n\n' msg += '```' elif name and not param: msg = '```' msg += name.capitalize() + '\n\n' for player, history in self.get_local_tokens(ctx)[name].get("ledger"): try: player = except: pass msg += f'' msg += '\n' msg += '```' else: token = self.get_token(ctx, name) if param and value: token.update(param, value) self.global_economy.save() @commands.command() @commands.has_role('gm') async def setmax(self, ctx, user, unit, max_value): '''Set the maximum amount of a token for a single user. setmax <user> <token_name> <max_value>''' tokens = self.get_local_tokens(ctx) token = tokens.get(unit) if not token: return "Currency not found" token_user = token.get(user.strip('<@!>')) token_user.update('max', max_value) @commands.command() @commands.has_role('gm') async def take(self, ctx, amount, unit, user, max_value): '''GM Only. Remove an amount from a player\'s inventory''' @commands.command() async def give(self, ctx, user, amount, unit): '''WIP. Transfer an amount to another player. Currency must be tradable.''' pass @commands.command() async def burn(self, ctx, user, amount, unit): '''WIP. Spends tokens and lowers maximum by the same amount''' pass @commands.command() async def balance(self, ctx, unit, player = None): '''WIP. Shows account balance for self or another player''' pass @commands.command() async def history(self, ctx, unit_name, lines = 5, user = None): '''Work in Progress''' pass
class Tracker(commands.Cog): def __init__(self, bot): self.bot = bot self.initiative = JsonFileObject('initiative.json') def _is_waiting_msg(self, m): return m.content.startswith(mstr.PBP_WAITING) @commands.Cog.listener() async def on_message(self, message): #ignore self and command messages if message.author == self.bot.user: return elif message.content.startswith(self.bot.command_prefix): await self.update_tracking_message(message) return ini = self.initiative.get(str(message.channel.id)) if not ini: return next = await self.get_next_turn(message) author = message.author.mention.replace("!", "") if not next: pass elif author in next: ini['entries'][author]['turns taken'] += 1 self.initiative.save() elif author not in next: await message.delete() await message.channel.send( f'Sorry {message.author.mention}, you can only send a message when it is your turn! :slight_smile:', delete_after=15) await self.update_tracking_message(message) async def update_tracking_message(self, ctx): ini = self.initiative[str(ctx.channel.id)] mode = ini['mode'] await ctx.channel.purge(check=self._is_waiting_msg) if mode == 'off': return _round = str(ini['round'] + 1) _pass = str(ini['pass'] + 1) if mode == 'sr5' else 0 turn = await self.get_next_turn(ctx) log.debug(f'Turn = {turn}') if isinstance(turn, list): turn = ' '.join(turn) msg = mstr.PBP_WAITING msg += '\n' + f'`Round: {_round}' if _pass: msg += f' | Pass: {_pass}' msg += f' | Mode: {mode}`' msg += '\n \n' + turn await ctx.channel.send(msg) async def get_next_turn(self, ctx: discord.ext.commands.Context): try: ini = self.initiative[str(ctx.channel.id)] except KeyError: return None mode = ini['mode'] entries = ini['entries'] if mode == 'off': next = '' elif mode == 'sr5': highest = ('', 0) for i, e in entries.items(): mod_ini = e['roll'] - e['spent'] - (10 * e['turns taken']) if mod_ini > 0 and mod_ini > highest[1] and e[ 'turns taken'] <= ini['pass']: if not e.get('name'): member = ctx.guild.get_member(i.strip("<@!>")) name = (getattr(member, 'nick', None) or getattr(member, 'name', None)) or i e.update({'name': name}) highest = (i, mod_ini) if highest == ('', 0): #increment pass ini['pass'] += 1 for i, e in entries.items(): mod_ini = e['roll'] - e['spent'] - (10 * e['turns taken']) if mod_ini > 0 and mod_ini > highest[1] and e[ 'turns taken'] <= ini['pass']: highest = (i, mod_ini) self.initiative.save() if highest == ('', 0): #increment round await self._roll(ctx) ini['round'] += 1 ini['pass'] = 0 for i, e in entries.items(): e['turns taken'] = 0 e['spent'] = 0 if e['roll'] > highest[1]: highest = (i, e['roll']) self.initiative.save() if highest == ('', 0): #wtf? next = r'¯\_(ツ)_/¯' else: next = f'{highest[0]} ({str(highest[1])})' elif mode == 'roundrobin': next = [] for i, e in entries.items(): if not e.get('name'): member = ctx.guild.get_member(i.strip("<@!>")) name = (getattr(member, 'nick', None) or getattr(member, 'name', None)) or i e.update({'name': name}) if e['turns taken'] <= ini['round']: next.append(i) if not next: ini['round'] += 1 if e['turns taken'] <= ini['round']: next.append(i) self.initiative.save() if not next: next = [r'¯\_(ツ)_/¯'] else: raise commands.CommandInvokeError( 'Invalid Initiative mode, use "off", "sr5", or "roundrobin"') return next @commands.command() @commands.has_role('gm') async def init(self, ctx, mode, *initiative_list): '''Add user to initiative order with the given initiative result Initiative list should be formatted in a comma separated list: (e.g @user#1234 10, @member#4321 15, Big Monster 12, ...) Available modes are: off - Deactivate tracking on this channel roundrobin - Everyone gets exactly one turn sr5 - One turn for every 10 points of initiative rolled''' #sanity check valid_modes = ['sr5', 'roundrobin', 'off'] if mode not in valid_modes: await ctx.send( f'Invalid mode used, please use one of the following: {valid_modes}. (eg. `{self.bot.command_prefix}init roundrobin @user#1234...`)', delete_after=10) return log.debug(f'{len(initiative_list)} Entries passed: {initiative_list}') entries = {} if mode == 'sr5': simple_list = [ x.strip() for x in ' '.join(initiative_list).replace('!', '').split(',') ] for e in simple_list: t = e.split(' ') entries.update({ ' '.join(t[0:-1]): { 'formula': t[-1], 'roll': 0, 'spent': 0, 'turns taken': 0 } }) log.debug(simple_list) elif mode == 'roundrobin': for e in initiative_list: entries.update({e: {'turns taken': 0}}) log.debug(f'--> {entries}') self.initiative.update({ str(ctx.channel.id): { 'mode': mode, 'round': 0, 'pass': 0, 'entries': entries } }) log.debug(f'----> {self.initiative[str(ctx.channel.id)]}') await self._roll(ctx) await self.update_tracking_message(ctx) @commands.command() @commands.has_role('gm') async def setinit(self, ctx, user, formula=None): '''Set formula for or add a user to turn tracking''' player = user.replace('!', '') ini = self.initiative.get(str(ctx.channel.id)) #sanity check if not ini or ini['mode'] == 'off': await ctx.send( f'Unable to update user initiative because turn tracking is not active on this channel. Use `{self.bot.command_prefix}init` to activate turn tracking.', delete_after=15) return entries = ini['entries'] if ini['mode'] == 'sr5': if formula == None: await ctx.send( 'Unable to update user initiative. A formula is required for sr5 initiative in the format `x+yd6`', delete_after=15) else: if entries.get(player): entries[player]['formula'] = formula await ctx.send(f'{player} initiative formula updated!') else: entries.update({ player: { 'formula': formula, 'roll': 0, 'spent': 0, 'turns taken': 0 } }) await ctx.send(f'{player} added to initiative!') if ini['mode'] == 'roundrobin': entries.update({ player: { 'formula': formula, 'roll': 0, 'spent': 0, 'turns taken': ini['round'] - 1 } }) await ctx.send(f'{player} added to initiative!') await self._roll(ctx, player) await self.update_tracking_message(ctx) @commands.command() @commands.has_role('gm') async def reroll(self, ctx, *users): '''[sr5 only] Re-roll initiative for any selected user without disturbing other entries in the order. Name no players to reroll everyone.''' players = [x.replace('!', '') for x in users] await self._roll(ctx, *players) async def _roll(self, ctx, *players): ini = self.initiative[str(ctx.channel.id)] done = [] failed = [] try: log.debug(f'_roll called by {ctx.command}') except Exception as e: log.debug(f'_roll called by a process: {e}') #only need to re-roll when mode is Shadowrun 5th edition if ini['mode'] != 'sr5': return #if no entries are listed, reroll all of them if not players: entries = ini['entries'] else: entries = {p: ini['entries'].get(p) for p in players} for k, e in entries.items(): try: log.debug(f'Rolling for {k} ---> {e}') formula = e['formula'] log.debug(f'Formula: {formula}') mod, pattern = formula.split('+') count, die = pattern.split('d') rolls = random.choices([x + 1 for x in range(int(die))], k=int(count)) log.debug(f'Dice Rolls: {rolls}') roll_sum = sum(rolls) grand_total = int(mod) + roll_sum log.debug(f'Final roll: {mod} + {roll_sum} = {grand_total}') e['roll'] = grand_total done.append(f"{k}\t{e['roll']}") except Exception as f: failed.append(f"{k}, {f}") await ctx.channel.send('```Initiative has been rolled:```{0}'.format( '\n'.join(done))) if failed: await ctx.channel.send( '```The following entries had errors and could not be rolled:``` {0}' .format('\n'.join(failed))) #TODO: Send confirmation message with list of initiative rolls self.initiative.save() await self.update_tracking_message(ctx) @commands.command() async def skip(self, ctx, *users): '''Skips the named user(s) in initiative. If no users named, skips self''' author_roles = [r.name for r in ctx.author.roles] if users and 'gm' not in author_roles: await ctx.send( f'Only a GM can name another person to skip, players should only use `{self.bot.command_prefix}skip` on it\'s own to skip themselves :smile:', delete_after=15) return elif not users: users = [ctx.author.mention] ch_id = str(ctx.channel.id) ini = self.initiative[ch_id] c_ini = await self.get_next_turn(ctx) user_list = [u.replace('!', '') for u in users] for user in user_list: if user in c_ini: ini['entries'][user]['turns taken'] += 1 self.initiative.save() await ctx.send(f'`Skipped` {user}') await ctx.message.delete() await self.update_tracking_message(ctx) @commands.command() async def spendinit(self, ctx, amount: int, *, action_taken): '''Spend initiative points to inturrupt or take a special action''' author = ctx.author.mention.replace('!', '') ini = self.initiative[str(ctx.channel.id)] entries = ini['entries'] entry = entries[author] cpass = ini['pass'] roll = entry['roll'] spent = entry['spent'] net_init = roll - spent - (10 * cpass) if net_init >= amount: entry['spent'] += amount new_init = net_init - amount self.initiative.save() await ctx.send( f'```{ctx.author.name} spent [{amount}] initiative to take the following action. Their initiative is now [{new_init}]``` {action_taken}' ) else: await ctx.send( f'Sorry, {author}, you do not have the enough initiative to spend. Your current score is **{net_init}**.', delete_after=15) await ctx.message.delete() await self.update_tracking_message(ctx) @commands.command() @commands.has_role('gm') async def takeinit(self, ctx, user, amount: int, *, action_taken): '''Same as spendinit but used on a user or NPC other than self''' author = user.replace('!', '') ini = self.initiative[str(ctx.channel.id)] entries = ini['entries'] entry = entries[author] cpass = ini['pass'] roll = entry['roll'] spent = entry['spent'] net_init = roll - spent - (10 * cpass) if net_init >= amount: entry['spent'] += amount new_init = net_init - amount self.initiative.save() await ctx.send( f'```{ctx.author.name} spent {amount} initiative to take the following action. Their initiative is now [{new_init}]``` {action_taken}' ) else: await ctx.send( f'Sorry, {author} does not have the enough initiative to spend. Their current score is **{net_init}**.', delete_after=15) await ctx.message.delete() await self.update_tracking_message(ctx) @commands.command() async def taketurn(self, ctx, user, *, action_taken): '''Allows a person to take a turn for an NPC or player. User argument only required for roundrobin''' ini = self.initiative.get(str(ctx.channel.id)) cini = await self.get_next_turn(ctx) #if mode is not roundrobin, attach the user argument to the beginning of action string since it should be ignored if ini['mode'] != 'roundrobin': action_taken = f'{user} {action_taken}' user = cini.split( '(')[0].strip() #current initiative minus the initiative score player = user.replace('!', '') entries = ini['entries'] entry = entries[player] if player in cini: entry['turns taken'] += 1 self.initiative.save() embeddable = discord.Embed( description=f"{user}'s turn was taken by {ctx.author.name}") await ctx.send(embed=embeddable) else: await ctx.send( f'Sorry, {player} does not have a turn to take right now', delete_after=15) await self.update_tracking_message(ctx) @commands.command() @commands.has_role('gm') async def setroll(self, ctx, user, roll: int = 0): '''Set an individuals initiative roll. If roundrobin, this will cause the player to join the next round''' player = user.replace('!', '') ini = self.initiative[str(ctx.channel.id)] entries = ini['entries'] if ini['mode'] == 'off': await ctx.send( f'{player} could not be added because turn tracking is turned off. Use `{self.bot.command_prefix}init` to start turn tracking. Type `{self.bot.command_prefix}help init` for more detailed information', delete_after=15) return if ini['mode'] == 'sr5': entries.update( {player: { 'roll': roll, 'spent': roll, 'turns taken': 0 }}) elif ini['mode'] == 'roundrobin': entries.update({player: {'turns taken': ini['round']}}) await ctx.send( f'{player} has been added to initiative. They will join in starting next round.', delete_after=15) self.initiative.save() await ctx.message.delete() await self.update_tracking_message(ctx)
class GameCommands(commands.Cog): def __init__(self, bot): self.bot = bot self.npcs = JsonFileObject('npcs.json') @commands.command() @commands.has_role('gm') async def open(self, ctx): '''Post open message and add player role to allow posting in channel''' await ctx.message.delete() player_roles = [ x for x in ctx.guild.roles if x.name.lower().startswith('player') ] for role in player_roles: await ctx.channel.set_permissions(role, send_messages=True, read_messages=True) await ctx.send('```This log is now open for player entries```') @commands.command() @commands.has_role('gm') async def close(self, ctx): '''Post close message and sync channel to category permissions''' await ctx.message.delete() await ctx.send( '```This log is closed, The next chapter will open soon```') await ctx.channel.edit(sync_permissions=True) @commands.command() @commands.has_role('gm') async def mimic(self, ctx, user, *, message): '''The bot will post a message imitating another user or an NPC''' await ctx.message.delete() person = {} if user.startswith('<'): m = ctx.guild.get_member(int(user.strip('<!@>'))) avatar = m.avatar_url if not m.avatar_url == '' else m.default_avatar_url person.update({'name': m.name, 'avatar_url': avatar}) log.debug(f'{ctx.author.name} is impersonating {user}, {m.name}') else: npc_list = self.npcs.get(str(ctx.guild.id)) if user in npc_list: npc = npc_list.get(user) person.update({ 'name': npc['display_name'], 'avatar_url': npc['avatar_url'] }) log.debug( f'{ctx.author.name} is impersonating NPC, {npc["display_name"]}' ) if person: embedable = discord.Embed( description=message) #, timestamp = ctx.timestamp) #embedable.set_footer(text = 'sent by GM') embedable.set_author( name=person['name']) #, icon_url= person['avatar_url']) embedable.set_thumbnail(url=person['avatar_url']) await ctx.send(embed=embedable) @commands.command() @commands.has_role('gm') async def addnpc(self, ctx, name, display_name, avatar_url): '''Adds an NPC for use with the mimic command''' await ctx.message.delete() npc_list = self.npcs.get(str(ctx.guild.id)) if npc_list: npc_list.update({ name: { 'display_name': display_name, 'avatar_url': avatar_url } }) self.npcs.save() else: self.npcs.update({ str(ctx.guild.id): { name: { 'display_name': display_name, 'avatar_url': avatar_url } } }) self.npcs.save() npc = self.npcs[str(ctx.guild.id)][name] log.debug(npc) embedable = discord.Embed( description= f'NPC profile has been saved to {ctx.guild.name}. Make sure everything looks as you expect it to here :smile:' ) #, timestamp = ctx.message.timestamp) embedable.set_footer(text=f'!mimic "{name}"') embedable.set_author( name=npc.get('display_name')) #, icon_url= npc.get('avatar_url')) embedable.set_thumbnail(url=npc.get('avatar_url')) await ctx.author.send(embed=embedable) @commands.command() @commands.has_role('gm') async def deletenpc(self, ctx, name): '''Remove NPC from server list''' await ctx.message.delete() npc_list = self.npcs.get(str(ctx.guild.id)) if npc_list: npc_list.remove(name) self.npcs.save() await ctx.author.send( f'{name} has been removed from the list of NPCs in {ctx.guild.name}' ) else: await ctx.author.send( f'{name} does not exist in the list of NPCs in {ctx.guild.name}' ) @commands.command() @commands.has_role('gm') async def listnpcs(self, ctx): '''list of available NPCs in fancy boxes''' await ctx.message.delete() npc_list = self.npcs.get(str(ctx.guild.id)) for name, npc in npc_list.items(): embedable = discord.Embed(description='Lorem impsum sic amor emet' ) #, timestamp = ctx.message.timestamp) embedable.set_footer(text=f'!mimic "{name}"') embedable.set_author(name=npc.get('display_name'), icon_url=npc.get('avatar_url')) #embedable.set_thumbnail(url = npc.get('avatar_url')) await ctx.send(embed=embedable)