thewindmage420

DND Combat Bot

Jan 23rd, 2025
70
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 37.37 KB | None | 0 0
  1. import discord
  2. import typing
  3. import json
  4. import random
  5. import os.path
  6. import dice
  7. import re
  8. import json
  9. import gzip
  10. import subprocess
  11. import tempfile
  12. from discord.ui import Item
  13. from math import sqrt
  14. from typing import Optional
  15. from typing import Union
  16.  
  17. from discord.ext import commands
  18.  
  19. from dotenvy import load_env, read_file
  20. from os import environ
  21.  
  22. load_env(read_file('combatbot.env'))
  23. TOKEN = environ.get('DISCORD_TOKEN')
  24.  
  25. p = '='  # blocks for health in hpbar
  26. d = '_'  # blocks for damage in hpbar
  27. chunk_total = 12  # total number of blocks in hpbar
  28.  
  29. init_order = []  # list of names and initiative rolls, sorted highest>lowest
  30. init_names = []  # names in init order w/out rolls
  31. init_names_e = []  # enemy names in initiative
  32. init_names_p = []  # player names in initiative
  33.  
  34. current_turn = 0  # the current player turn number
  35. turn_num = 1  # current actual turn number
  36. current_round = 1  # the current round number
  37. total_turns = 1  # total number of turns
  38. ko = 0
  39. player_turn = ''  # the player(or enemy) who's turn it is
  40.  
  41. init_round = False  # determines whether or not the initiative round is active
  42. in_combat = False  # determines whether or not combat is active
  43.  
  44. hpbar = ''  # variable for drawing hpbar
  45. statlist = ''  # variable for drawing stat list
  46. stats_name = ''  # variable for drawing name
  47. entity = ''
  48. embed = ''  # variable for defining embeds
  49. hp_out = ''
  50.  
  51. all_hp = {}  # list of everyone's hp
  52.  
  53. available_dice_assets = ['d4', 'd6', 'd8', 'd10', 'd12', 'd20']
  54.  
  55.  
  56. # cdn_base_url = 'https://cdn.discordapp.com/attachments/1000024222514561027/1000'
  57.  
  58.  
  59. def dump_enemylist():
  60.     with open('enemy_list', 'w') as f:
  61.         json.dump(enemies, f)  # check if 'enemy_list' file exists & if not then create it
  62.  
  63.  
  64. def dump_playerlist():
  65.     with open('player_list', 'w') as f:
  66.         json.dump(players, f)  # check if 'enemy_list' file exists & if not then create it
  67.  
  68.  
  69. def dump_enemyhp():
  70.     with open('enemy_hp', 'w') as f:
  71.         json.dump(enemy_hp, f)
  72.  
  73.  
  74. def dump_playerhp():
  75.     with open('player_hp', 'w') as f:
  76.         json.dump(player_hp, f)
  77.  
  78.  
  79. def roll_dice(
  80.         die: str,
  81. ) -> tuple[int, str, list[int], int, Optional[list[str]]]:
  82.     """Roll a dice with dice notation and return
  83.  
  84.    :param die: dice notation
  85.    :return: (number_of_dice, dice_sides, result, total, Optional[dice_images], bonus)
  86.    """
  87.     dice_sides: str = ""
  88.     if mo := re.search(r"(?P<dice_sides>d\d+)", die):
  89.         (dice_sides,) = mo.groups()
  90.  
  91.     bonus = 0
  92.     if not '+' in die and not '-' in die:
  93.         result = dice.roll(die)
  94.         total = int(result) + int(bonus)
  95.     if '+' in die:
  96.         die, bonus = die.split('+')
  97.         result = dice.roll(die)
  98.         total = int(result) + int(bonus)
  99.     if '-' in die:
  100.         die, bonus = die.split('-')
  101.         result = dice.roll(die)
  102.         total = int(result) - int(bonus)
  103.         bonus = int(bonus)
  104.         bonus = -abs(bonus)
  105.     # result = dice.roll(die)
  106.  
  107.     number_of_dice = len(result)
  108.     dice_sides = str(dice_sides)
  109.     ret_value = (number_of_dice, dice_sides, result, total, int(bonus))
  110.     list_images = []
  111.     for number in result:
  112.         dice_face_file_name = f"assets/{dice_sides}-{number}.png"
  113.         if (
  114.                 dice_sides in available_dice_assets
  115.                 and os.access(dice_face_file_name, os.R_OK)
  116.         ):
  117.             list_images.append(dice_face_file_name)
  118.     if dice_sides in available_dice_assets and number_of_dice <= 25:
  119.         tmp = tempfile.NamedTemporaryFile(suffix='.png')
  120.         tmp.close()
  121.         process = subprocess.Popen([
  122.             'montage',
  123.             '-alpha', 'Set',
  124.             '-background', 'None',
  125.             '-resize', '200x200>',
  126.             '-gravity', 'Center',
  127.             '-extent', '220x220',
  128.             '-tile', f'{round(sqrt(number_of_dice))}x',
  129.             '+repage', *list_images, tmp.name
  130.         ], shell=False, stdout=subprocess.PIPE)
  131.         process.wait()
  132.     if dice_sides in available_dice_assets and number_of_dice <= 25:
  133.         return number_of_dice, dice_sides, result, total, [tmp.name], int(bonus)
  134.     return number_of_dice, dice_sides, result, total, '', int(bonus)
  135.  
  136.  
  137. class Reroll(discord.ui.View):
  138.     def __init__(self, die: str, *items: Item):
  139.         self.die = die
  140.         super().__init__(*items)
  141.  
  142.     @discord.ui.button(label="Reroll!", style=discord.ButtonStyle.primary, emoji="🎲")
  143.     async def button_callback(self, button, interaction):
  144.         (embed, file) = create_roll_embed(self.die, interaction.user)
  145.         if file:
  146.             return await interaction.response.send_message(file=file, embed=embed, view=Reroll(self.die))
  147.         await interaction.response.send_message(embed=embed, view=Reroll(self.die))
  148.  
  149.  
  150. def create_roll_embed(die: str, user: discord.User):
  151.     (number_of_dice, dice_sides, result, total, dice_images, bonus) = roll_dice(die)
  152.     embed = discord.Embed(
  153.         title="Rolled a dice",
  154.         description=f"",
  155.         color=discord.Color.blue(),
  156.     )
  157.     embed.set_author(name=user.display_name, icon_url=user.avatar.url)
  158.     embed.set_footer(
  159.         text="Information requested by: {}".format(user.display_name)
  160.     )
  161.     embed.add_field(name='dice', value=die, inline=True)
  162.     if bonus > 0:
  163.         embed.add_field(name='result', value=f'{result}+{bonus}', inline=True)
  164.     if bonus < 0:
  165.         embed.add_field(name='result', value=f'{result}{bonus}', inline=True)
  166.     if bonus == 0:
  167.         embed.add_field(name='result', value=f'{result}', inline=True)
  168.     embed.add_field(name='total', value=f'{total}', inline=True)
  169.     if len(dice_images) > 0:
  170.         file = discord.File(dice_images[0])
  171.         embed.set_image(url=f'attachment://{os.path.basename(dice_images[0])}')
  172.         return embed, file
  173.     return embed, None
  174.  
  175.  
  176. def drawhpbar():
  177.     global chunks, hpbar
  178.     if chunks > 0:
  179.         if chunks < 1:
  180.             chunks = 1
  181.         chunks = round(chunks)
  182.     chunk_count = 0  # define starting point for hp blocks
  183.     hpbar = ''  # define empty hpbar
  184.     dmg_total = chunk_total - chunks  # calculate total damage taken
  185.     dmg_count = 0  # define starting point for hp blocks
  186.  
  187.     if chunk_count < chunk_total:
  188.         while chunk_count < chunks:
  189.             hpbar = hpbar + p
  190.             chunk_count += 1  # fill hbar with current health
  191.     if dmg_count < dmg_total:
  192.         while dmg_count < dmg_total:
  193.             hpbar = hpbar + d
  194.             dmg_count += 1  # fill remaining space with total damage taken
  195.  
  196.  
  197. def drawstatlist():
  198.     global embed, statlist, stats_name
  199.     slstatlist = statlist.splitlines()
  200.     statlist1 = slstatlist[:len(slstatlist) // 2]
  201.     statlist2 = slstatlist[len(slstatlist) // 2:]
  202.     sl1 = ''
  203.     sl2 = ''
  204.     for x in statlist1:
  205.         sl1 = sl1 + x + '\n'
  206.     for x in statlist2:
  207.         sl2 = sl2 + x + '\n'
  208.     if stats_name in enemies.keys():
  209.         embed = discord.Embed(title=stats_name + ": " + hp_out + str(enemies[stats_name]['Percent']) + '%',
  210.                               color=0x00ff00)
  211.         embed.add_field(name="Stats:", value=sl1, inline=True)
  212.         embed.add_field(name="Abilities:", value=sl2, inline=True)
  213.         statlist = ''
  214.     if stats_name in players.keys():
  215.         embed = discord.Embed(title=stats_name + ": " + hp_out + str(players[stats_name]['Percent']) + '%',
  216.                               color=0x00ff00)
  217.         embed.add_field(name="Stats:", value=sl1, inline=True)
  218.         embed.add_field(name="Abilities:", value=sl2, inline=True)
  219.         statlist = ''
  220.  
  221.  
  222. def drawhplist():
  223.     global embed, statlist, entity
  224.  
  225.     embed = discord.Embed(title='', color=0x00ff00)
  226.     embed.add_field(name=entity + " Healthbars:", value=statlist, inline=True)
  227.     statlist = ''
  228.     entity = ''
  229.  
  230.  
  231. def drawcombatinfo():
  232.     global embed
  233.     line1 = "Round: " + str(current_round) + '\n'
  234.     line2 = "Turn: " + str(turn_num) + ' (' + str(total_turns) + ')\n'
  235.     line3 = "It is now " + player_turn + "'s turn."
  236.     lines = line1 + line2 + line3
  237.     embed = discord.Embed(title='', description="", color=0x00ff00)
  238.     embed.add_field(name="Combat info:", value=lines, inline=True)
  239.  
  240.  
  241. def turncleanup():
  242.     global total_turns, current_turn, current_round, embed, turn_num, player_turn, ko
  243.     turn_num += 1
  244.     total_turns += 1
  245.     current_turn -= ko
  246.     if current_turn >= len(init_order):
  247.         current_turn = 0
  248.         turn_num = 1
  249.         current_round += 1
  250.     player_turn = str(init_names[current_turn])
  251.     ko = 0
  252.  
  253.     drawcombatinfo()
  254.  
  255.  
  256. def combatcleanup():
  257.     global chunk_total, init_order, init_names, init_names_e, init_names_p, current_turn, current_round, total_turns, \
  258.         player_turn, init_round, in_combat, turn_num
  259.     chunk_total = 12
  260.     init_order = []
  261.     init_names = []
  262.     init_names_e = []
  263.     init_names_p = []
  264.     current_turn = 0
  265.     turn_num = 1
  266.     current_round = 1
  267.     total_turns = 1
  268.     player_turn = ''
  269.     init_round = False
  270.     in_combat = False
  271.  
  272.  
  273. if not os.path.isfile('player_list'):
  274.     players = {}
  275.     dump_playerlist()
  276.  
  277. if not os.path.isfile('enemy_list'):
  278.     enemies = {}
  279.     dump_enemylist()
  280.  
  281. if not os.path.isfile('player_hp'):
  282.     player_hp = {}
  283.     dump_playerhp()
  284.  
  285. if not os.path.isfile('enemy_hp'):
  286.     enemy_hp = {}
  287.     dump_enemyhp()
  288.  
  289. with open('player_list') as f:
  290.     players = json.load(f)  # load the player_list into the players dict
  291.  
  292. with open('enemy_list') as f:
  293.     enemies = json.load(f)  # load the enemy_list into the enemies dict
  294.  
  295. with open('player_hp') as f:
  296.     player_hp = json.load(f)
  297.  
  298. with open('enemy_hp') as f:
  299.     enemy_hp = json.load(f)
  300.  
  301. all_hp.update(player_hp)
  302. all_hp.update(enemy_hp)
  303.  
  304. attack_types = ['Phys', 'Ranged', 'Spell', 'Spell Save']
  305. stats = ['Name', 'HP', 'MaxHP', 'Percent', 'Healthbar', 'AC', 'Init Roll', 'Prof Bonus', 'Init Bonus', 'STR', 'DEX',
  306.          # 0      #1     #2       #3         #4          #5     #6            #7           #8            #9    #10
  307.          'CON', 'INT', 'WIS', 'CHA', 'Spell DC', 'Spell Atk']  # , 'To Hit', 'Atk Die']
  308. # 11    #12    #13    #14    #15         #16          #17       #18
  309. mods = ['STR Mod', 'DEX Mod', 'CON Mod', 'INT Mod', 'WIS Mod', "CHA Mod"]
  310. # ^^assign various stats to the stats[] list
  311.  
  312.  
  313. description = '''A bot designed to handle D&D combat, keep track of player and enemy statistics,
  314. and provide other quality of life utilities.'''
  315. bot = commands.Bot(command_prefix='.', description=description)
  316.  
  317.  
  318. @bot.event
  319. async def on_ready():
  320.     print('Logged in as')
  321.     print(bot.user.name)
  322.     print(bot.user.id)
  323.     print('------')
  324.     for guild in bot.guilds:
  325.         print(guild.name)  # prints all server's names
  326.  
  327.  
  328. @bot.slash_command()
  329. async def roll(ctx, die: str = None):
  330.     """Roll dice in 'NdN' or 'NdN .+ N' format
  331.  
  332.    :param die: dice in 'NdN' format
  333.    """
  334.     await ctx.defer()
  335.     (embed, file) = create_roll_embed(die, ctx.author)
  336.     if file:
  337.         return await ctx.followup.send(file=file, embed=embed, view=Reroll(die))
  338.     await ctx.followup.send(embed=embed, view=Reroll(die))
  339.  
  340.  
  341. @bot.slash_command(description='Make a choice between any number of defined choices.')
  342. async def choose(ctx, *choices: str):
  343.     """Chooses between multiple choices."""
  344.     await ctx.respond(random.choice(choices))
  345.  
  346.  
  347. @bot.slash_command()
  348. async def addenemy(ctx, name: str, hp: int, maxhp: int, ac: int, prof_bonus: int, initbonus: int):
  349.     """Define a new enemy in the enemy list."""
  350.     global chunk_total, chunks, enemy_hp, all_hp
  351.  
  352.     if hp > 2000:
  353.         hp = 2000
  354.     if maxhp > 2000:
  355.         maxhp = 2000
  356.     if ac > 2000:
  357.         ac = 2000
  358.     if prof_bonus > 2000:
  359.         prof_bonus = 2000
  360.     if initbonus > 2000:
  361.         initbonus = 2000
  362.  
  363.     percent = hp / maxhp * 100  # <calculate health percentage
  364.     enemies[name] = {stats[1]: hp, stats[2]: maxhp, stats[3]: round(percent)}
  365.     # ^^write name to the enemies dictionary and assign name, stats, and hp%
  366.  
  367.     chunks = (enemies[name]['HP'] / enemies[name]['MaxHP']) * chunk_total  # calculate total # of blocks for hpbar
  368.     drawhpbar()
  369.     enemy_hp.update({name: '`[' + hpbar + ']`'})
  370.     all_hp[name] = {enemy_hp[name]}
  371.     enemies[name].update({stats[5]: ac, stats[6]: 0, stats[7]: prof_bonus, stats[8]: initbonus})
  372.  
  373.     percent = enemies[name]['HP'] / enemies[name]['MaxHP'] * 100  # calc hp percentage
  374.     enemies[name]['Percent'] = round(percent)  # assign new hp percentage
  375.  
  376.     sl = ''
  377.     for x, y in zip(enemies[name].keys(), enemies[name].values()):
  378.         sl = sl + str(x) + ':' + str(y) + '\n'
  379.     embed = discord.Embed(title=name, color=0x00ff00)
  380.     embed.add_field(name="Stats:", value=sl, inline=True)
  381.  
  382.     dump_enemyhp()
  383.     dump_enemylist()
  384.     await ctx.respond(embed=embed)
  385.     await ctx.respond("Stats added! Add ability scores with </addabts> for help type </help> <addabts>.")
  386.  
  387.  
  388. @bot.slash_command()
  389. async def addplayer(ctx, name: str, hp: int, maxhp: int, ac: int, prof_bonus: int, initbonus: int):
  390.     """Define a new player in the player list."""
  391.     global chunk_total, chunks, player_hp, all_hp
  392.  
  393.     if hp > 2000:
  394.         hp = 2000
  395.     if maxhp > 2000:
  396.         maxhp = 2000
  397.     if ac > 2000:
  398.         ac = 2000
  399.     if prof_bonus > 2000:
  400.         prof_bonus = 2000
  401.     if initbonus > 2000:
  402.         initbonus = 2000
  403.  
  404.     percent = hp / maxhp * 100  # <calculate health percentage
  405.     players[name] = {stats[1]: hp, stats[2]: maxhp, stats[3]: round(percent)}
  406.     # ^^write name to the players dictionary and assign name, stats, and hp%
  407.  
  408.     chunks = (players[name]['HP'] / players[name]['MaxHP']) * chunk_total  # calculate total # of blocks for hpbar
  409.     drawhpbar()
  410.     player_hp.update({name: '`[' + hpbar + ']`'})
  411.     all_hp[name] = {player_hp[name]}
  412.     players[name].update(
  413.         {stats[5]: ac, stats[6]: 0, stats[7]: prof_bonus, stats[8]: initbonus})  # assign remaining stats
  414.  
  415.     percent = players[name]['HP'] / players[name]['MaxHP'] * 100  # calc hp percentage
  416.     players[name]['Percent'] = round(percent)  # assign new hp percentage
  417.  
  418.     sl = ''
  419.     for x, y in zip(players[name].keys(), players[name].values()):
  420.         sl = sl + str(x) + ':' + str(y) + '\n'
  421.     embed = discord.Embed(title=name, color=0x00ff00)
  422.     embed.add_field(name="Stats:", value=sl, inline=True)
  423.  
  424.     dump_playerhp()
  425.     dump_playerlist()
  426.     await ctx.respond(embed=embed)
  427.     await ctx.respond("Stats added! Add ability scores with </addabts> for help type </help> <addabts>.")
  428.  
  429.  
  430. @bot.slash_command()
  431. async def addabts(ctx, name: str, strength: int, dexterity: int, constitution: int, intelligence: int, wisdom: int,
  432.                   charisma: int, spellsave: typing.Optional[int], *, spellatk: typing.Optional[int]):
  433.     """Define enemy/player abilities."""
  434.  
  435.     if strg > 40:
  436.         strg = 40
  437.     if dex > 40:
  438.         dex = 40
  439.     if con > 40:
  440.         con = 40
  441.     if intel > 40:
  442.         intel = 40
  443.     if wis > 40:
  444.         wis = 40
  445.     if cha > 40:
  446.         cha = 40
  447.  
  448.     if name in enemies.keys():
  449.         dexmod = int((dex - 10) / 2)
  450.         enemies[name].update({stats[9]: strg, stats[10]: dex, stats[11]: con, stats[12]: intel,
  451.                               stats[13]: wis, stats[14]: cha})
  452.         if spellsave == None and spellatk == None:
  453.             enemies[name].update({stats[15]: 0, stats[16]: 0})
  454.         elif spellsave and spellatk:
  455.             enemies[name].update({stats[15]: spellsave, stats[16]: spellatk})
  456.             if enemies[name][15] > 40:
  457.                 enemies[name][15] = 40
  458.             if enemies[name][16] > 40:
  459.                 enemies[name][16] = 40
  460.         dump_enemylist()
  461.         dump_enemyhp()
  462.  
  463.     if name in players.keys():
  464.         dexmod = int((dex - 10) / 2)
  465.         players[name].update(
  466.             {stats[9]: strg, stats[10]: dex, stats[11]: con, stats[12]: intel, stats[13]: wis,
  467.              stats[14]: cha})
  468.         if spellsave == None and spellatk == None:
  469.             players[name].update({stats[15]: 0, stats[16]: 0})
  470.         elif spellsave and spellatk:
  471.             players[name].update({stats[15]: spellsave, stats[16]: spellatk})
  472.             if players[name][15] > 40:
  473.                 players[name][15] = 40
  474.             if players[name][16] > 40:
  475.                 players[name][16] = 40
  476.         dump_playerlist()
  477.         dump_playerhp()
  478.  
  479.     global statlist, stats_name, hp_out
  480.     if name in enemies.keys():
  481.         for x, y in zip(enemies[name].keys(), enemies[name].values()):
  482.             statlist = statlist + x + ': ' + str(y) + '\n'
  483.         stats_name = name
  484.         hp_out = str(all_hp[name]).replace("{'", "").replace("'}", "")
  485.         drawstatlist()
  486.  
  487.     if name in players.keys():
  488.         for x, y in zip(players[name].keys(), players[name].values()):
  489.             statlist = statlist + x + ': ' + str(y) + '\n'
  490.         stats_name = name
  491.         hp_out = str(all_hp[name]).replace("{'", "").replace("'}", "")
  492.         drawstatlist()
  493.  
  494.     await ctx.respond(embed=embed)
  495.     await ctx.respond("Abilites added! Check stats with <.sts> <name>.")
  496.  
  497.  
  498. # @bot.slash_command()
  499. # async def elist(ctx):
  500. #    """Lists every enemy name in the enemy list."""
  501. #    enemy_names = []
  502. #    enames = ''
  503. #    embed = ''
  504. #    for x in enemies.keys():
  505. #        enemy_names.append(x)  # write all enemy names from the enemy dict to the enemy_names list
  506. #    for y in enemy_names:
  507. #        enames = enames + y + '\n'
  508. #    embed = discord.Embed(color=0x00ff00)
  509. #    embed.add_field(name="Enemies:", value=enames, inline=True)
  510. #    await ctx.respond(embed=embed)
  511.  
  512.  
  513. @bot.slash_command()
  514. async def elist(ctx):
  515.     """Lists every enemy's current healthbar."""
  516.     global statlist, entity, hp_out, enemy_hp, all_hp
  517.     for x, y in zip(enemy_hp.keys(), enemy_hp.values()):
  518.         statlist = statlist + str(x) + ': ' + str(y) + '\n'
  519.  
  520.     entity = 'Enemy'
  521.     drawhplist()
  522.     await ctx.respond(embed=embed)
  523.  
  524.  
  525. # @bot.slash_command()
  526. # async def plist(ctx):
  527. #    """Lists every player name in the player list."""
  528. #    player_names = []
  529. #    pnames = ''
  530. #    embed = ''
  531. #    for x in players.keys():
  532. #        player_names.append(x)  # write all player names from the player dict to the player_names list
  533. #    for y in player_names:
  534. #        pnames = pnames + y + '\n'
  535. #    embed = discord.Embed(color=0x00ff00)
  536. #    embed.add_field(name="Players:", value=pnames, inline=True)
  537. #    await ctx.respond(embed=embed)
  538.  
  539.  
  540. @bot.slash_command()
  541. async def plist(ctx):
  542.     """Lists every player's current healthbar."""
  543.     global statlist, entity, hp_out, player_hp, all_hp
  544.     for x, y in zip(player_hp.keys(), player_hp.values()):
  545.         name = str(x)
  546.         statlist = statlist + str(x) + ': ' + str(y) + str(players[name]['HP']) + '/' + str(
  547.             players[name]['MaxHP']) + ' - ' + str(players[name]['Percent']) + '%' + '\n'
  548.  
  549.     entity = 'Player'
  550.     drawhplist()
  551.     await ctx.respond(embed=embed)
  552.  
  553.  
  554. @bot.slash_command()
  555. async def hp(ctx, name: str):
  556.     """Displays the specified player/enemy's Healthbar."""
  557.     global hp_out
  558.     hp_out = str(all_hp[name]).replace("{'", "").replace("'}", "")
  559.     percentage = ''
  560.     if name in players.keys():
  561.         percentage = str(players[name]['HP']) + '/' + str(players[name]['MaxHP']) + ' - ' + str(
  562.             players[name]['Percent']) + '%'
  563.     await ctx.respond(name + ': ' + hp_out + percentage)  # send the specified player/enemy name and their current hpbar
  564.  
  565.  
  566. @bot.slash_command()
  567. async def sts(ctx, name: str):
  568.     """Shows the full stat list of the specified player/enemy."""
  569.     global statlist, stats_name, hp_out
  570.     if name in enemies.keys():
  571.         for x, y in zip(enemies[name].keys(), enemies[name].values()):
  572.             statlist = statlist + x + ': ' + str(y) + '\n'
  573.         stats_name = name
  574.         hp_out = str(all_hp[name]).replace("{'", "").replace("'}", "")
  575.         drawstatlist()
  576.  
  577.     if name in players.keys():
  578.         for x, y in zip(players[name].keys(), players[name].values()):
  579.             statlist = statlist + x + ': ' + str(y) + '\n'
  580.         stats_name = name
  581.         hp_out = str(all_hp[name]).replace("{'", "").replace("'}", "")
  582.         drawstatlist()
  583.  
  584.     await ctx.respond(embed=embed)  # send every enemy stat and its' current value as a message
  585.  
  586.  
  587. @bot.slash_command()
  588. async def dmg(ctx, name: str, dmg: int):
  589.     """Deals damage to the specified player/enemy by the specified amount."""
  590.     global chunk_total, init_order, init_names, player_turn, current_turn, current_round, total_turns, chunks, hp_out, \
  591.         enemy_hp, player_hp, all_hp, ko
  592.     if dmg > 2000:
  593.         dmg = 2000
  594.  
  595.     if name in enemies.keys():  # check if specified entity is in enemy list
  596.         enemies[name]['HP'] -= dmg  # deal specified damage to enemy
  597.         percent = enemies[name]['HP'] / enemies[name]['MaxHP'] * 100  # calc hp percentage
  598.         enemies[name]['Percent'] = round(percent)  # assign new hp percentage to specified enemy
  599.  
  600.         chunks = (enemies[name]['HP'] / enemies[name]['MaxHP']) * chunk_total  # calculate total # of blocks for hpbar
  601.         drawhpbar()
  602.         enemy_hp.update({name: '`[' + hpbar + ']`'})
  603.         all_hp.update(enemy_hp)
  604.  
  605.         dump_enemyhp()
  606.         dump_enemylist()
  607.         hp_out = str(all_hp[name]).replace("{'", "").replace("'}", "")
  608.         await ctx.respond(name + ": " + hp_out)
  609.  
  610.         if enemies[name]['HP'] <= 0:
  611.             del_list = [name, enemies[name][stats[6]]]
  612.             init_order = [i for i in init_order if i != del_list]
  613.             init_names = [item[0] for item in init_order]
  614.             ko = 1
  615.             if name in init_names_e:
  616.                 init_names_e.remove(name)
  617.             if init_names_e != []:
  618.                 if name == player_turn:
  619.                     current_turn += 1
  620.                     turncleanup()
  621.                     await ctx.respond(embed=embed)
  622.                     hp_out = str(all_hp[player_turn]).replace("{'", "").replace("'}", "")
  623.                     await ctx.respond(player_turn + ': ' + hp_out)
  624.             if init_names_e == [] and in_combat:
  625.                 combatcleanup()
  626.                 await ctx.respond('All enemies defeated!\nCombat is now over.')
  627.             current_turn += 1
  628.  
  629.     if name in players.keys():  # check if specified entity is in player list
  630.         players[name]['HP'] -= dmg  # deal specified damage to player
  631.         percent = players[name]['HP'] / players[name]['MaxHP'] * 100  # calc hp percentage
  632.         players[name]['Percent'] = round(percent)  # assign new hp percentage to specified player
  633.  
  634.         chunks = (players[name]['HP'] / players[name]['MaxHP']) * chunk_total  # calculate total # of blocks for hpbar
  635.         drawhpbar()
  636.         player_hp.update({name: '`[' + hpbar + ']`'})
  637.         all_hp.update(player_hp)
  638.  
  639.         dump_playerhp()
  640.         dump_playerlist()
  641.         hp_out = str(all_hp[name]).replace("{'", "").replace("'}", "")
  642.         await ctx.respond(
  643.             name + ": " + hp_out + str(players[name]['HP']) + '/' + str(players[name]['MaxHP']) + ' - ' + str(
  644.                 players[name]['Percent']) + '%')
  645.  
  646.         if players[name]['HP'] <= 0:
  647.             del_list = [name, players[name][stats[6]]]
  648.             init_order = [i for i in init_order if i != del_list]
  649.             init_names = [item[0] for item in init_order]
  650.             ko = 1
  651.             if name in init_names_p:
  652.                 init_names_p.remove(name)
  653.             if init_names_p != []:
  654.                 if name == player_turn:
  655.                     current_turn += 1
  656.                     turncleanup()
  657.                     await ctx.respond(embed=embed)
  658.                     hp_out = str(all_hp[player_turn]).replace("{'", "").replace("'}", "")
  659.                     await ctx.respond(player_turn + ': ' + hp_out)
  660.             if init_names_p == [] and in_combat:
  661.                 combatcleanup()
  662.                 await ctx.respond('All players defeated!\nCombat is now over.')
  663.             current_turn += 1
  664.  
  665.  
  666. @bot.slash_command()
  667. async def heal(ctx, name: str, dmg: int):
  668.     """Deals damage to the specified player/enemy by the specified amount."""
  669.     global chunk_total, init_order, init_names, player_turn, current_turn, current_round, total_turns, chunks, hp_out, \
  670.         enemy_hp, player_hp, all_hp, ko
  671.     if dmg > 2000:
  672.         dmg = 2000
  673.     if name in enemies.keys():  # check if specified entity is in enemy list
  674.         enemies[name]['HP'] += dmg  # deal specified damage to enemy
  675.         percent = enemies[name]['HP'] / enemies[name]['MaxHP'] * 100  # calc hp percentage
  676.         enemies[name]['Percent'] = round(percent)  # assign new hp percentage to specified enemy
  677.  
  678.         chunks = (enemies[name]['HP'] / enemies[name]['MaxHP']) * chunk_total  # calculate total # of blocks for hpbar
  679.         drawhpbar()
  680.         enemy_hp.update({name: '`[' + hpbar + ']`'})
  681.         all_hp.update(enemy_hp)
  682.  
  683.         dump_enemyhp()
  684.         dump_enemylist()
  685.         hp_out = str(all_hp[name]).replace("{'", "").replace("'}", "")
  686.         await ctx.respond(name + ": " + hp_out)
  687.  
  688.         if enemies[name]['HP'] <= 0:
  689.             del_list = [name, enemies[name][stats[6]]]
  690.             init_order = [i for i in init_order if i != del_list]
  691.             init_names = [item[0] for item in init_order]
  692.             ko = 1
  693.             if name in init_names_e:
  694.                 init_names_e.remove(name)
  695.             if init_names_e != []:
  696.                 if name == player_turn:
  697.                     current_turn += 1
  698.                     turncleanup()
  699.                     await ctx.respond(embed=embed)
  700.                     hp_out = str(all_hp[player_turn]).replace("{'", "").replace("'}", "")
  701.                     await ctx.respond(player_turn + ': ' + hp_out)
  702.             if init_names_e == [] and in_combat:
  703.                 combatcleanup()
  704.                 await ctx.respond('All enemies defeated!')
  705.                 await ctx.respond("Combat is now over.")
  706.  
  707.     if name in players.keys():  # check if specified entity is in player list
  708.         players[name]['HP'] += dmg  # deal specified damage to player
  709.         percent = players[name]['HP'] / players[name]['MaxHP'] * 100  # calc hp percentage
  710.         players[name]['Percent'] = round(percent)  # assign new hp percentage to specified player
  711.  
  712.         chunks = (players[name]['HP'] / players[name]['MaxHP']) * chunk_total  # calculate total # of blocks for hpbar
  713.         drawhpbar()
  714.         player_hp.update({name: '`[' + hpbar + ']`'})
  715.         all_hp.update(player_hp)
  716.  
  717.         dump_playerhp()
  718.         dump_playerlist()
  719.         hp_out = str(all_hp[name]).replace("{'", "").replace("'}", "")
  720.         await ctx.respond(
  721.             name + ": " + hp_out + str(players[name]['HP']) + '/' + str(players[name]['MaxHP']) + ' - ' + str(
  722.                 players[name]['Percent']) + '%')
  723.  
  724.         if players[name]['HP'] <= 0:
  725.             del_list = [name, players[name][stats[6]]]
  726.             init_order = [i for i in init_order if i != del_list]
  727.             init_names = [item[0] for item in init_order]
  728.             ko = 1
  729.             if name in init_names_p:
  730.                 init_names_p.remove(name)
  731.             if init_names_p != []:
  732.                 if name == player_turn:
  733.                     current_turn += 1
  734.                     turncleanup()
  735.                     await ctx.respond(embed=embed)
  736.                     hp_out = str(all_hp[player_turn]).replace("{'", "").replace("'}", "")
  737.                     await ctx.respond(player_turn + ': ' + hp_out)
  738.             if init_names_p == [] and in_combat:
  739.                 combatcleanup()
  740.                 await ctx.respond('All players defeated!\nCombat is now over.')
  741.  
  742.  
  743. @bot.slash_command()
  744. async def combatinfo(ctx):
  745.     """Displays the current turn/round description."""
  746.     if in_combat:
  747.         drawcombatinfo()
  748.         await ctx.respond(embed=embed)
  749.  
  750.  
  751. @bot.slash_command()
  752. async def startinit(ctx):
  753.     """Start the initiatve round to make initiative rolls."""
  754.     global init_round, in_combat
  755.     if init_round and not in_combat:
  756.         print(init_round)
  757.         await ctx.respond(
  758.             "It is already the initiative round.\n </startcombat> to end the initiative round or </clrinit> to start over")
  759.     if not init_round:
  760.         init_round = True
  761.         await ctx.respond("Initiative round started.")
  762.  
  763.  
  764. @bot.slash_command()
  765. async def clrinit(ctx):
  766.     """Clears current initiative order."""
  767.     if not in_combat:
  768.         combatcleanup()
  769.         await ctx.respond("Initiative cleared. Start the initiative round again with </startinit>.")
  770.     if in_combat:
  771.         await ctx.respond("Cannot clear initiative while in combat.")
  772.  
  773.  
  774. @bot.slash_command()
  775. async def initr(ctx, name: str):
  776.     """Roll initiative for specified enemy/player."""
  777.     global init_order, init_round
  778.     if name not in players and name not in enemies:
  779.         await ctx.respond('Name not found.')
  780.     if init_round and not in_combat:
  781.         if any(name in sl for sl in init_order):
  782.             if name in enemies:
  783.                 del_list = [name, enemies[name][stats[6]]]
  784.                 init_order = [i for i in init_order if i != del_list]
  785.             if name in players:
  786.                 del_list = [name, players[name][stats[6]]]
  787.                 init_order = [i for i in init_order if i != del_list]
  788.         if name in enemies.keys():
  789.             roll = dice.roll('1d20+' + str(enemies[name][stats[8]]))
  790.             enemies[name][stats[6]] = roll
  791.             if not name in init_names_e:
  792.                 init_names_e.append(name)
  793.         if name in players.keys():
  794.             roll = dice.roll('1d20+' + str(players[name][stats[8]]))
  795.             players[name][stats[6]] = roll
  796.             if not name in init_names_p:
  797.                 init_names_p.append(name)
  798.         if not any(name in sl for sl in init_order):
  799.             init_order.append([name, roll])
  800.  
  801.         def Sort(init_order):
  802.             init_order.sort(key=lambda x: x[1], reverse=True)
  803.             return init_order
  804.  
  805.         Sort(init_order)
  806.  
  807.         def conversion(tup, dict):
  808.             for x, y in init_order:
  809.                 init_order_dict.setdefault(x, str(y))
  810.             return init_order_dict
  811.  
  812.         init_order_dict = {}
  813.         init_list = ''
  814.         conversion(init_order, init_order_dict)
  815.  
  816.         for x, y in zip(init_order_dict.keys(), init_order_dict.values()):
  817.             init_list = init_list + x + ': ' + str(y) + '\n'
  818.         embed = discord.Embed(title='', color=0x00ff00)
  819.         embed.add_field(name='Initiative Order: ', value=init_list, inline=True)
  820.  
  821.         await ctx.respond(embed=embed)
  822.     if in_combat:
  823.         await ctx.respond("Cannot reroll initiative while in combat. Use </addinit> to add/change one.")
  824.     if not init_round and not in_combat:
  825.         await ctx.respond("Begin initiative with </startinit> before making initiative rolls!")
  826.  
  827.  
  828. @bot.slash_command()
  829. async def initorder(ctx):
  830.     """Show the current initiative order."""
  831.     if not init_round and not in_combat:
  832.         await ctx.respond('Not currently in combat.')
  833.     if init_round or in_combat:
  834.         def Sort(init_order):
  835.             init_order.sort(key=lambda x: x[1], reverse=True)
  836.             return init_order
  837.  
  838.         Sort(init_order)
  839.  
  840.         def conversion(tup, dict):
  841.             for x, y in init_order:
  842.                 init_order_dict.setdefault(x, str(y))
  843.             return init_order_dict
  844.  
  845.         init_order_dict = {}
  846.         init_list = ''
  847.         conversion(init_order, init_order_dict)
  848.  
  849.         for x, y in zip(init_order_dict.keys(), init_order_dict.values()):
  850.             init_list = init_list + x + ': ' + str(y) + '\n'
  851.         embed = discord.Embed(title='', color=0x00ff00)
  852.         embed.add_field(name='Initiative Order: ', value=init_list, inline=True)
  853.  
  854.         await ctx.respond(embed=embed)
  855.  
  856.  
  857. @bot.slash_command()
  858. async def addinit(ctx, name: str, amt: int):
  859.     """Add initiative rolls to initiative order."""
  860.     global init_names, init_order, current_turn, current_round, total_turns, player_turn, init_round, in_combat, players, enemies
  861.     if name in players or name in enemies:
  862.         if init_round or in_combat:
  863.             turn_add = init_order[current_turn]
  864.             turn_add = turn_add[1]
  865.             if amt > turn_add:
  866.                 current_turn += 1
  867.             if any(name in sl for sl in init_order):
  868.                 if name in enemies:
  869.                     del_list = [name, enemies[name][stats[6]]]
  870.                     init_order = [i for i in init_order if i != del_list]
  871.                 if name in players:
  872.                     del_list = [name, players[name][stats[6]]]
  873.                     init_order = [i for i in init_order if i != del_list]
  874.             if name in enemies.keys():
  875.                 enemies[name][stats[6]] = amt
  876.                 if not name in init_names_e:
  877.                     init_names_e.append(name)
  878.             if name in players.keys():
  879.                 players[name][stats[6]] = amt
  880.                 if not name in init_names_p:
  881.                     init_names_p.append(name)
  882.             if not any(name in sl for sl in init_order):
  883.                 init_order.append([name, amt])
  884.  
  885.             def Sort(init_order):
  886.                 init_order.sort(key=lambda x: x[1], reverse=True)
  887.                 return init_order
  888.  
  889.             Sort(init_order)
  890.  
  891.             def conversion(tup, dict):
  892.                 for x, y in init_order:
  893.                     init_order_dict.setdefault(x, str(y))
  894.                 return init_order_dict
  895.  
  896.             init_order_dict = {}
  897.             init_list = ''
  898.             conversion(init_order, init_order_dict)
  899.  
  900.             init_names = [item[0] for item in init_order]
  901.             player_turn = str(init_names[current_turn])
  902.             drawcombatinfo()
  903.             hp_out = str({all_hp[player_turn]}).replace("{'", "").replace("'}", "")
  904.  
  905.             for x, y in zip(init_order_dict.keys(), init_order_dict.values()):
  906.                 init_list = init_list + x + ': ' + str(y) + '\n'
  907.             embed = discord.Embed(title='', color=0x00ff00)
  908.             embed.add_field(name='Initiative Order: ', value=init_list, inline=True)
  909.  
  910.             await ctx.respond(name + ' added to initiative order.')
  911.             await ctx.respond(embed=embed)
  912.  
  913.     if name not in players and name not in enemies:
  914.         await ctx.respond('Name not found.')
  915.     if not init_round and not in_combat:
  916.         await ctx.respond("Begin initiative with </startinit> before making initiative rolls!")
  917.  
  918.  
  919. @bot.slash_command()
  920. async def startcombat(ctx):
  921.     """Start combat after initiative rolls."""
  922.     global init_names, init_order, current_turn, current_round, total_turns, player_turn, init_round, in_combat
  923.     if not init_round and not in_combat:
  924.         await ctx.respond("Roll for initiative before starting combat. Start the initiative round with </startinit>.")
  925.     if in_combat:
  926.         await ctx.respond("End the current combat before starting a new one. Defeat all enemies or use </endcombat>.")
  927.     if init_round:
  928.         if len(init_names_e) < 1 or len(init_names_p) < 1:
  929.             await ctx.respond(
  930.                 "Make at least two initiative rolls before starting combat. (one enemy and one player minimum)")
  931.     if init_round == True and len(init_order) > 1 and len(init_names_e) > 0 and len(init_names_p) > 0 and not in_combat:
  932.         in_combat = True
  933.         init_round = False
  934.         init_names = [item[0] for item in init_order]
  935.         await ctx.respond("Combat has now begun.")
  936.  
  937.         player_turn = str(init_names[0])
  938.         drawcombatinfo()
  939.         hp_out = str(all_hp[player_turn]).replace("{'", "").replace("'}", "")
  940.  
  941.         await ctx.respond(embed=embed)
  942.         if player_turn in enemies.keys():
  943.             await ctx.respond(player_turn + ': ' + hp_out)
  944.         if player_turn in players.keys():
  945.             percentage = str(players[player_turn]['HP']) + '/' + str(players[player_turn]['MaxHP']) + ' - ' + str(
  946.                 players[player_turn]['Percent']) + '%'
  947.             await ctx.respond(player_turn + ': ' + hp_out + percentage)
  948.  
  949.  
  950. @bot.slash_command()
  951. async def endturn(ctx):
  952.     """End current player's turn."""
  953.     global current_turn, player_turn, current_round, total_turns, hp_out, init_names
  954.     if in_combat:
  955.         current_turn += 1
  956.         turncleanup()
  957.         await ctx.respond(embed=embed)
  958.         hp_out = str(all_hp[player_turn]).replace("{'", "").replace("'}", "")
  959.         if player_turn in enemies.keys():
  960.             await ctx.respond(player_turn + ': ' + hp_out)
  961.         if player_turn in players.keys():
  962.             percentage = str(players[player_turn]['HP']) + '/' + str(players[player_turn]['MaxHP']) + ' - ' + str(
  963.                 players[player_turn]['Percent']) + '%'
  964.             await ctx.respond(player_turn + ': ' + hp_out + percentage)
  965.     if not in_combat:
  966.         await ctx.respond("Must be in combat to end a turn.")
  967.  
  968.  
  969. @bot.slash_command()
  970. async def endcombat(ctx):
  971.     """End the current combat."""
  972.     if not in_combat:
  973.         await ctx.respond(
  974.             "Cannot end combat while not in combat. Start initiative with </startinit> or combat with </startcombat>.")
  975.     if in_combat:
  976.         combatcleanup()
  977.         await ctx.respond("Combat is now over.")
  978.  
  979.  
  980. @bot.slash_command()
  981. async def delete(ctx, name: str):
  982.     """Delete the specified enemy/player from the respective list"""
  983.     if name in enemies.keys():
  984.         del enemies[name]
  985.     if name in enemy_hp.keys():
  986.         del enemy_hp[name]
  987.     if name in players.keys():
  988.         del players[name]
  989.     if name in player_hp.keys():
  990.         del player_hp[name]
  991.     dump_enemyhp()
  992.     dump_enemylist()
  993.     dump_playerhp()
  994.     dump_playerlist()
  995.     await ctx.respond(name + ' deleted from appropriate list.')
  996.  
  997.  
  998. bot.run(TOKEN)
  999.  
Add Comment
Please, Sign In to add comment