├── Attributes ├── levels.txt ├── school.txt ├── damage_types.txt ├── range.txt ├── duration.txt └── area_types.txt ├── .gitignore ├── SECURITY.md ├── make_spell_dir.py ├── LICENSE ├── README.md ├── bases.py ├── bard_spells.py ├── writer_live.py ├── line_shapes.py ├── writer_live.html ├── writer.py └── spells.json /Attributes/levels.txt: -------------------------------------------------------------------------------- 1 | Blank 2 | 0 3 | 1 4 | 2 5 | 3 6 | 4 7 | 5 8 | 6 9 | 7 10 | 8 11 | 9 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Uniques/ 2 | *.png 3 | __pycache__/ 4 | *.pyc 5 | *.wav 6 | dictionary_maker.py 7 | swg_dictionary.txt 8 | *.eps 9 | Dict_Key/ -------------------------------------------------------------------------------- /Attributes/school.txt: -------------------------------------------------------------------------------- 1 | Blank 2 | Abjuration 3 | Conjuration 4 | Divination 5 | Enchantment 6 | Evocation 7 | Illusion 8 | Necromancy 9 | Transmutation 10 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | 4 | ## Reporting a Vulnerability 5 | 6 | Please report any vulnerabilities by emailing me at gorillaofdestinytiktok@gmail.com 7 | -------------------------------------------------------------------------------- /Attributes/damage_types.txt: -------------------------------------------------------------------------------- 1 | None 2 | Acid 3 | Bludgeoning 4 | Cold 5 | Fire 6 | Force 7 | Lightning 8 | Necrotic 9 | Piercing 10 | Poison 11 | Psychic 12 | Radiant 13 | Slashing 14 | Thunder -------------------------------------------------------------------------------- /Attributes/range.txt: -------------------------------------------------------------------------------- 1 | Blank 2 | 1 mile 3 | 10 feet 4 | 100 feet 5 | 120 feet 6 | 150 feet 7 | 30 feet 8 | 300 feet 9 | 5 feet 10 | 500 feet 11 | 500 miles 12 | 60 feet 13 | 90 feet 14 | Self 15 | Sight 16 | Special 17 | Touch 18 | Unlimited -------------------------------------------------------------------------------- /Attributes/duration.txt: -------------------------------------------------------------------------------- 1 | Instantaneous 2 | 1 hour 3 | 1 minute 4 | 1 round 5 | 10 days 6 | 10 minutes 7 | 24 hours 8 | 30 days 9 | 7 days 10 | 8 hours 11 | Special 12 | Until dispelled 13 | Up to 1 hour 14 | Up to 1 minute 15 | Up to 1 round 16 | Up to 10 minutes 17 | Up to 2 hours 18 | Up to 24 hours 19 | Up to 8 hours 20 | -------------------------------------------------------------------------------- /Attributes/area_types.txt: -------------------------------------------------------------------------------- 1 | None 2 | cone (15) 3 | cone (30) 4 | cone (40) 5 | cone (60) 6 | cube (10) 7 | cube (100) 8 | cube (15) 9 | cube (150) 10 | cube (20) 11 | cube (200) 12 | cube (2500) 13 | cube (30) 14 | cube (40) 15 | cube (40000) 16 | cube (5) 17 | cube (5280) 18 | cylinder (10) 19 | cylinder (20) 20 | cylinder (40) 21 | cylinder (5) 22 | cylinder (50) 23 | cylinder (60) 24 | line (100) 25 | line (50) 26 | line (60) 27 | line (90) 28 | sphere (10) 29 | sphere (100) 30 | sphere (15) 31 | sphere (20) 32 | sphere (30) 33 | sphere (360) 34 | sphere (40) 35 | sphere (5) 36 | sphere (60) -------------------------------------------------------------------------------- /make_spell_dir.py: -------------------------------------------------------------------------------- 1 | import json 2 | from tqdm.auto import tqdm 3 | import os 4 | 5 | 6 | # Opening JSON file 7 | f = open('spells.json') 8 | 9 | if not os.path.isdir("spells/"): 10 | os.mkdir("spells/") 11 | 12 | # returns JSON object as 13 | # a dictionary 14 | data = json.load(f) 15 | 16 | # Iterating through the json 17 | # list 18 | print(data['results']) 19 | # Closing file 20 | f.close() 21 | 22 | for d in tqdm(data['results']): 23 | spell_name = d['index'] 24 | url = d['index'] 25 | os.system(f'curl -X GET "https://www.dnd5eapi.co/api/spells/{url}" -H "Accept: application/json" --output spells/{spell_name}.json') 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 GorillaOfDestiny 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SpellWritingGuide 2 | 3 | This is the tidier version of the code used in the [Spell Writing Guide](https://www.drivethrurpg.com/product/429711/The-Spell-Writing-Guide?manufacturers_id=22808) which aims to provide a simple method by which we can draw spells in D&D 5e. The system is general to any system and easy to modify, I will explain this later. 4 | 5 | ## Setup 6 | 7 | You can clone the repo for use simply by typing: 8 | 9 | ```git clone https://github.com/GorillaOfDestiny/SpellWritingGuide``` 10 | 11 | When initially running the code a folder called "Uniques" with files such as "11.npy" being created within. These contain the rotationally unique binary numbers the method relies on. They will only be created when such a file does not already exist in a directory called "Uniques". 12 | 13 | ### Dependencies 14 | 15 | Python vesion used in development: Python 3.10.4 16 | 17 | The required python modules are: 18 | - numpy 19 | - matplotlib 20 | - argparse 21 | - math 22 | - os 23 | - tqdm 24 | 25 | ## Running the file 26 | 27 | to run you type the command: ```py writer.py``` 28 | 29 | for information about optional commands type: ```py writer.py --help``` 30 | 31 | standard input for a spell is ```py writer.py -level -range -area -dtype -school ``` with replaced by relevant lowercase strings. Defaults to make "Fireball". 32 | 33 | To see the available inputs (and their format) type: ```py writer.py --arg_help -...``` 34 | 35 | input options are defined by the .txt files in "Attributes/" so ```--arg_help``` simply prints the values in this file. 36 | 37 | ## Modifying 38 | 39 | You can add your own options to the inputs by adding them in a new line in the relevant .txt files in "Attributes/" 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /bases.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import math 3 | 4 | #---------File for defining spell bases----------# 5 | # every base must haave an input of n and return (x,y) 6 | 7 | def polygon(n,radius = 1,start_angle = None): 8 | #Creates x,y data for an n-sided polygon 9 | if start_angle == None: 10 | start_angle = np.pi/n 11 | small_angle = [start_angle + i * 2*np.pi/n for i in np.arange(1,n+1)] 12 | x,y = (radius * np.sin(small_angle), radius * np.cos(small_angle)) 13 | return(x,y) 14 | 15 | def line(n): 16 | #makes a horizontal line of n-points 17 | x = np.arange(0,n) 18 | y = np.zeros((1,n)) 19 | return(x,y[0]) 20 | 21 | def quadratic(n,a = 1,b=0,c=0): 22 | #Creates x,y data for a quadratic equation beginning at 0 and bouncing between positive and negative values 23 | x= [0] 24 | while len(x) < n: 25 | if -x[-1] in x: 26 | x.append(-x[-1] +1) 27 | else: 28 | x.append(-x[-1]) 29 | x = np.array(x) 30 | y = a*x**2 +b*x+c 31 | return(x,y) 32 | 33 | def circle(n,radius = 1,theta0 = 0,theta1 = -np.pi/2): 34 | #creates a circular base between theta0 and theta1 35 | #quarter_circle is 0 -> -np.pi/2 36 | #semi_circle is 0 -> -np.pi 37 | theta = np.linspace(theta0,theta1,n) 38 | x = radius*np.cos(theta) 39 | y = radius*np.sin(theta) 40 | return(x,y) 41 | 42 | def cubic(n,a = 0.1,b=0,c = -0.75,d=0): 43 | #Creates a base accourding to the cubic function 44 | x = np.arange(-math.floor(n/2),math.ceil(n/2)) 45 | y = a*x**3+b**2+c*x+d 46 | return(x,y) 47 | 48 | def golden(n,lim = 3*np.pi): 49 | #Creates a base accourding to the golden ratio spiral 50 | t = np.linspace(0,lim,n) 51 | g = (1 + 5 ** 0.5) / 2 #golden ratio 52 | f = g**(t*g/(2*np.pi)) #factor 53 | x = np.cos(t)*f 54 | y = np.sin(t)*f 55 | return(x,y) 56 | 57 | 58 | if __name__ == "__main__": 59 | print("Hello Nerd!") -------------------------------------------------------------------------------- /bard_spells.py: -------------------------------------------------------------------------------- 1 | from writer import load_attribute 2 | import librosa 3 | 4 | 5 | class scale(): 6 | def __init__(self,steps,max_L,f0 = 440): 7 | n = [0] 8 | while len(n) < max_L: 9 | for s in list(steps): 10 | n.append(n[-1] + int(s)) 11 | 12 | 13 | self.n = n 14 | 15 | #default values 16 | self.f0 = f0 17 | def get_note(self,i): 18 | N = self.n[i] #+(i//len(self.n)) 19 | a = (2)**(1/12) 20 | f = self.f0*a**N 21 | return(f) 22 | 23 | 24 | def chords_maker(rang,level,area,dtype,school,scale_steps = "2212221",f0 = 440): 25 | ranges = load_attribute("attributes/range.txt") 26 | levels = load_attribute("attributes/levels.txt") 27 | area_types = load_attribute("attributes/area_types.txt") 28 | dtypes = load_attribute("attributes/damage_types.txt") 29 | schools = load_attribute("attributes/school.txt") 30 | 31 | lens = [len(ranges),len(levels),len(area_types),len(dtypes),len(schools)] 32 | reference_scale = scale(scale_steps,max_L = max(lens),f0 = f0) 33 | i_range = ranges.index(rang) 34 | i_levels = levels.index(level) 35 | i_area = area_types.index(area) 36 | i_dtype = dtypes.index(dtype) 37 | i_school = schools.index(school) 38 | attr = [i_range,i_levels,i_area,i_dtype,i_school] 39 | print(attr) 40 | while len(set(attr)) != len(attr): 41 | seen = [] 42 | for i,a in enumerate(attr): 43 | if a in seen: 44 | attr[i] += lens[i] 45 | else: 46 | seen.append(a) 47 | 48 | f = [] 49 | for i in attr: 50 | f.append(reference_scale.get_note(i)) 51 | return(f) 52 | 53 | if __name__ == "__main__": 54 | major = "2212221" 55 | minor = "2122122" 56 | blues = "321132" 57 | A4 = 440 58 | Middle_C = 264 59 | f = chords_maker('point (150 feet)','3',"sphere","fire","evocation",scale_steps = blues,f0 = Middle_C) 60 | for f_ in f: 61 | print(librosa.hz_to_note(f_)) 62 | 63 | -------------------------------------------------------------------------------- /writer_live.py: -------------------------------------------------------------------------------- 1 | from writer import * 2 | from bokeh.layouts import column,row 3 | from bokeh.models import Button,CustomJS, Dropdown 4 | from bokeh.palettes import RdYlBu3 5 | from bokeh.plotting import figure, curdoc,show 6 | 7 | def create_dropdown(att,label = "Dropdown"): 8 | menu = [(l.capitalize(),l.lower()) for l in att] 9 | dropdown = Dropdown(label=label, menu=menu) 10 | dropdown.js_on_event("menu_item_click", CustomJS(code="console.log('dropdown: ' + this.item, this.toString())")) 11 | return(dropdown) 12 | 13 | def callback(ddms): 14 | #!!!!!!!!!!! 15 | #This is where I've got to, need to collect drop down info and then 16 | # find what arrays to use etc. 17 | # then plot them using the correct function 18 | # might be best to keep it to straight line behaviour for now 19 | pass 20 | 21 | #define base function 22 | base_fn = bases.polygon 23 | 24 | #define number of points 25 | n = 11 26 | #get base 27 | x,y = base_fn(n) 28 | 29 | #load attributes 30 | ranges = load_attribute("Attributes/range.txt") 31 | levels = load_attribute("Attributes/levels.txt") 32 | area_types = load_attribute("Attributes/area_types.txt") 33 | dtypes = load_attribute("Attributes/damage_types.txt") 34 | schools = load_attribute("Attributes/school.txt") 35 | 36 | #initialise figure 37 | fig_size = 750 38 | p = figure(width = fig_size ,height = fig_size ,x_range = (-1.5,1.5), y_range = (-1.5,1.5),toolbar_location = None) 39 | p.outline_line_color = None 40 | p.grid.grid_line_color = None 41 | p.scatter(x,y,size = 10,fill_color = "black",line_color = "navy") 42 | p.axis.visible = False 43 | 44 | #setup dropdowns 45 | ranges_ddm = create_dropdown(ranges,"Range") 46 | levels_ddm = create_dropdown(levels,"Level") 47 | area_types_ddm = create_dropdown(area_types,"Area_type") 48 | dtypes_ddm = create_dropdown(dtypes,"damage_type") 49 | schools_ddm = create_dropdown(schools,"Schools") 50 | 51 | button = Button(label="Press Me") 52 | button.on_event('button_click', callback) 53 | if __name__ == "__main__": 54 | show(p) 55 | 56 | row1 = row(ranges_ddm,levels_ddm,area_types_ddm) 57 | row2 = row(dtypes_ddm,schools_ddm) 58 | 59 | curdoc().add_root(column(row1,row2,button, p)) -------------------------------------------------------------------------------- /line_shapes.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import math 3 | #functions for drawing lines between points 4 | def centre_circle(P,Q,thetas = None): 5 | #draws connecting circles between two lines with the centre being the average of the two points 6 | x1 = P[0] 7 | y1 = P[1] 8 | x2 = Q[0] 9 | y2 = Q[1] 10 | a = (x1+x2)/2 11 | b = (y1+y2)/2 12 | r = np.sqrt((a-x1)**2 + (b-y1)**2) 13 | if thetas == "Full": 14 | theta = np.linspace( 0 , 2 * np.pi , 150 ) 15 | else: 16 | theta0 = math.atan2(y1-b, x1-a) 17 | theta1 = math.atan2(y2-b, x2-a) 18 | if y2 < y1: 19 | theta0,theta1 = theta1 + np.pi,theta0+np.pi 20 | theta = np.linspace(theta0,theta1,150) 21 | X2 = r*np.cos(theta)+a 22 | Y2 = r*np.sin(theta)+b 23 | return(X2,Y2) 24 | 25 | def non_centre_circle(P,Q,b,thetas = None): 26 | #draws a connecting circle between two points with a centre defined by `b` away from the average. Always chooses the small arc. 27 | x1 = P[0] 28 | y1 = P[1] 29 | x2 = Q[0] 30 | y2 = Q[1] 31 | b2 = -b 32 | delta = x1**2 - x2**2 + y1**2 - y2**2 33 | a = (delta-2*(y1 - y2)*b)/(2*(x1 - x2)) 34 | a2 = (delta-2*(y1 - y2)*b2)/(2*(x1 - x2)) 35 | r = np.sqrt((x1-a)**2 + (y1 - b)**2) 36 | r2 = np.sqrt((x1-a2)**2 + (y1 - b2)**2) 37 | if r2 <= r: 38 | a = a2 39 | b = b2 40 | r = r2 41 | 42 | if thetas == "Full": 43 | theta1 = np.linspace( 0 , 2 * np.pi , 150 ) 44 | 45 | else: 46 | theta0 = math.atan2(y1-b, x1-a) 47 | theta1 = math.atan2(y2-b, x2-a) 48 | theta02 = theta0 49 | theta12 = theta1 50 | while theta1 < theta0: 51 | theta0 -= 2*np.pi 52 | while theta02 < theta12: 53 | theta12 -= 2*np.pi 54 | arc1 = r*(theta1 - theta0) 55 | arc2 = r*(theta02 - theta12) 56 | if arc1 < arc2 or np.sqrt(b**2) <1: 57 | theta = np.linspace(theta1,theta0,150) 58 | else: 59 | theta = np.linspace(theta02,theta12,150) 60 | 61 | X = r*np.cos(theta)+a 62 | Y = r*np.sin(theta)+b 63 | return(X,Y) 64 | 65 | def straight(P,Q): 66 | #A straight line between two points 67 | X = [P[0],Q[0]] 68 | Y = [P[1],Q[1]] 69 | return(X,Y) -------------------------------------------------------------------------------- /writer_live.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Bokeh Plot 6 | 15 | 16 | 19 | 20 | 21 |
22 | 23 | 26 | 60 | 61 | -------------------------------------------------------------------------------- /writer.py: -------------------------------------------------------------------------------- 1 | 2 | import bases 3 | import line_shapes 4 | import numpy as np 5 | import matplotlib.pyplot as plt 6 | import os 7 | from tqdm.auto import tqdm 8 | 9 | cmap = plt.get_cmap('viridis') 10 | #---------Functions for creating unique binary numbers------ 11 | def cycle_list(l,loops = 1): 12 | n = len(l) 13 | for t in range(loops): 14 | l = [l[(i+1) % n] for i in range(n)] 15 | return(l) 16 | 17 | def generate_unique_combinations(L): 18 | combinations = generate_binary_strings(L) 19 | non_repeating = [combinations[0]] 20 | for i in tqdm(range(len(combinations)),desc = "Genearting Unique Binary Numbers"): 21 | ref = list(combinations[i]) 22 | N = len(ref) 23 | test = 0 24 | for j in range(len(non_repeating)): 25 | for n in range(N): 26 | 27 | if cycle_list(list(non_repeating[j]),loops = n+1) == ref: 28 | test += 1 29 | 30 | if test == 0: 31 | non_repeating.append(combinations[i]) 32 | 33 | for i in np.arange(len(non_repeating)): 34 | non_repeating[i] = [int(s) for s in list(non_repeating[i])] 35 | return(non_repeating) 36 | 37 | def genbin(n, bs = ''): 38 | if n-1: 39 | genbin(n-1, bs + '0') 40 | genbin(n-1, bs + '1') 41 | else: 42 | print('1' + bs) 43 | 44 | def generate_binary_strings(bit_count): 45 | binary_strings = [] 46 | def genbin(n, bs=''): 47 | if len(bs) == n: 48 | binary_strings.append(bs) 49 | else: 50 | genbin(n, bs + '0') 51 | genbin(n, bs + '1') 52 | 53 | 54 | genbin(bit_count) 55 | return binary_strings 56 | 57 | #-------Functions for drawing runes 58 | def decode_shape(in_array,k=1,point_color = 'k',on_color = 'darkred',off_color = "grey", 59 | label = None,base_fn = bases.polygon,base_kwargs = [], 60 | shape_fn = line_shapes.straight,shape_kwargs = [], 61 | plot_base = False): 62 | #decodes a single array into a given base, use plot_base = True if you are plotting it on its own 63 | n = len(in_array) 64 | x,y = base_fn(n,*base_kwargs) 65 | if plot_base == True: 66 | plt.scatter(x[1:],y[1:],s = 70,facecolors = 'none', edgecolors = point_color) 67 | plt.scatter(x[0],y[0],s = 70,facecolors = point_color, edgecolors = point_color) 68 | plt.axis('off') 69 | plt.axis('scaled') 70 | for i,elem in enumerate(in_array): 71 | P = [x[i],y[i]] 72 | Q = [x[(i+k)%n],y[(i+k)%n]] 73 | X,Y = shape_fn(P,Q,*shape_kwargs) 74 | if elem == 0: 75 | plt.plot(X,Y,color = off_color,ls = "--",linewidth=0.25) 76 | elif elem == 1: 77 | plt.plot(X,Y,color = on_color,ls = "-",label = label if i == np.where(in_array == 1)[0][0] else None, 78 | linewidth = 2) 79 | else: 80 | print(f'elem {elem} at index {i} is not valid, input being skipped') 81 | 82 | 83 | def draw_multiple_inputs(in_array, 84 | base_fn = bases.polygon,base_kwargs = [], 85 | shape_fn = line_shapes.straight,shape_kwargs = [], 86 | point_color = 'k',labels = [],legend = False,colors = [], 87 | legend_loc = "upper left"): 88 | 89 | #draws multiple inputs on a single base 90 | if isinstance(colors,list) and len(colors) == 0: 91 | colors = [point_color]*in_array.shape[0] 92 | elif isinstance(colors,str): 93 | colors = [colors]*in_array.shape[0] 94 | n = in_array.shape[1] 95 | x,y = base_fn(n,*base_kwargs) 96 | plt.scatter(x[1:],y[1:],s = 70,facecolors = 'none', edgecolors = point_color) 97 | plt.scatter(x[0],y[0],s = 70,facecolors = point_color, edgecolors = point_color) 98 | 99 | if len(labels) != in_array.shape[0]: 100 | labels = [None]*in_array.shape[0] 101 | 102 | for i,k in enumerate(range(in_array.shape[0])): 103 | 104 | decode_shape(in_array[i],k = k+1,base_fn = base_fn,base_kwargs = base_kwargs, 105 | shape_fn = shape_fn,shape_kwargs = shape_kwargs,label = labels[i],on_color = colors[i]) 106 | 107 | if labels[0] != None and legend == True: 108 | plt.legend(loc = legend_loc,fontsize = 10) 109 | plt.axis('off') 110 | plt.axis('scaled') 111 | def load_attribute(fname): 112 | with open(fname,"r") as f: 113 | data = f.readlines() 114 | f.close() 115 | data = [d.replace("\n","").lower() for d in data] 116 | return(data) 117 | 118 | 119 | def draw_spell(level,rang,area,dtype,school,title = None, 120 | savename = "output.png",legend = False, 121 | base_fn = bases.polygon,base_kwargs = [], 122 | shape_fn = line_shapes.straight,shape_kwargs = [], 123 | colors = [],legend_loc = "upper left",breakdown = False): 124 | 125 | #draws a spell given certain values by comparing it to input txt 126 | ranges = load_attribute("Attributes/range.txt") 127 | levels = load_attribute("Attributes/levels.txt") 128 | area_types = load_attribute("Attributes/area_types.txt") 129 | dtypes = load_attribute("Attributes/damage_types.txt") 130 | schools = load_attribute("Attributes/school.txt") 131 | i_range = ranges.index(rang) 132 | i_levels = levels.index(str(level)) 133 | i_area = area_types.index(area) 134 | i_dtype = dtypes.index(dtype) 135 | i_school = schools.index(school) 136 | attributes = [i_levels,i_school,i_dtype,i_area,i_range] 137 | labels = [f"level: {level}", 138 | f"school: {school}", 139 | f"damage type: {dtype}", 140 | f"range: {rang}", 141 | f"area_type: {area}"] 142 | N = 2*len(attributes)+1 143 | 144 | if len(colors) == 0 and breakdown == True: 145 | colors = [cmap(i/len(attributes)) for i in range(len(attributes))] 146 | if not os.path.isdir("Uniques/"): 147 | os.makedirs("Uniques/") 148 | if os.path.isfile(f'Uniques/{N}.npy'): 149 | non_repeating = np.load(f'Uniques/{N}.npy') 150 | else: 151 | non_repeating = generate_unique_combinations(N) 152 | non_repeating = np.array(non_repeating) 153 | np.save(f"Uniques/{N}.npy",non_repeating) 154 | input_array = np.array([non_repeating[i] for i in attributes])#note +1 s.t. 0th option is always open for empty input 155 | #print(input_array) 156 | draw_multiple_inputs(input_array,labels = labels,legend = legend, 157 | base_fn = base_fn,base_kwargs = base_kwargs, 158 | shape_fn = shape_fn,shape_kwargs = shape_kwargs, 159 | colors = colors,legend_loc = legend_loc) 160 | 161 | plt.title(title,fontsize = "80") 162 | 163 | if savename is not None: 164 | plt.savefig(savename,transparent = False, bbox_inches='tight') 165 | plt.clf() 166 | else: 167 | plt.show() 168 | 169 | def draw_spell_2(level,rang,area,dtype,school,duration,concentration,ritual,title = None, 170 | savename = "output.png",legend = False, 171 | base_fn = bases.polygon,base_kwargs = [], 172 | shape_fn = line_shapes.straight,shape_kwargs = [], 173 | colors = [],legend_loc = "upper left",breakdown = False, 174 | base_dir = ""): 175 | 176 | #draws a spell given certain values by comparing it to input txt 177 | ranges = load_attribute(base_dir +"Attributes/range.txt") 178 | levels = load_attribute(base_dir +"Attributes/levels.txt") 179 | area_types = load_attribute(base_dir +"Attributes/area_types.txt") 180 | dtypes = load_attribute(base_dir +"Attributes/damage_types.txt") 181 | schools = load_attribute(base_dir +"Attributes/school.txt") 182 | durations = load_attribute(base_dir +"Attributes/duration.txt") 183 | i_range = ranges.index(rang) 184 | i_levels = levels.index(str(level)) 185 | i_area = area_types.index(area) 186 | i_dtype = dtypes.index(dtype) 187 | i_school = schools.index(school) 188 | i_duration = durations.index(duration) 189 | attributes = [i_levels,i_school,i_dtype,i_area,i_range,i_duration] 190 | labels = [f"level: {level}", 191 | f"school: {school}", 192 | f"damage type: {dtype}", 193 | f"range: {rang}", 194 | f"area_type: {area}", 195 | f'duration: {duration}'] 196 | N = 2*len(attributes)+1 197 | 198 | if len(colors) == 0 and breakdown == True: 199 | colors = [cmap(i/len(attributes)) for i in range(len(attributes))] 200 | if not os.path.isdir(base_dir +"Uniques/"): 201 | os.makedirs(base_dir +"Uniques/") 202 | if os.path.isfile(base_dir +f'Uniques/{N}.npy'): 203 | non_repeating = np.load(base_dir +f'Uniques/{N}.npy') 204 | else: 205 | non_repeating = generate_unique_combinations(N) 206 | non_repeating = np.array(non_repeating) 207 | np.save(base_dir +f"Uniques/{N}.npy",non_repeating) 208 | input_array = np.array([non_repeating[i] for i in attributes])#note +1 s.t. 0th option is always open for empty input 209 | 210 | draw_multiple_inputs(input_array,labels = labels,legend = legend, 211 | base_fn = base_fn,base_kwargs = base_kwargs, 212 | shape_fn = shape_fn,shape_kwargs = shape_kwargs, 213 | colors = colors,legend_loc = legend_loc) 214 | 215 | if concentration: 216 | plt.plot(0,0,"",markersize = 10,marker = ".",color = colors) 217 | if ritual: 218 | 219 | plt.plot(0,0,"",markersize = 10,marker = ".",color= colors) 220 | plt.plot(0,0,"",markersize = 20,marker = "o",color=colors,mfc='none',linewidth = 10) 221 | 222 | plt.title(title) 223 | if savename is not None: 224 | plt.savefig(savename,transparent = True, bbox_inches='tight') 225 | plt.clf() 226 | else: 227 | plt.show() 228 | 229 | #to run if the file is called 230 | def draw_attribute(level = None,rang = None, area = None, 231 | savename = "output.png",legend = False, 232 | dtype = None, school = None,duration = None, 233 | base_fn = bases.polygon,base_kwargs = [], 234 | shape_fn = line_shapes.straight,shape_kwargs = [], 235 | colors = [],legend_loc = "upper left",breakdown = False, 236 | title = None): 237 | ranges = load_attribute("Attributes/range.txt") 238 | levels = load_attribute("Attributes/levels.txt") 239 | area_types = load_attribute("Attributes/area_types.txt") 240 | dtypes = load_attribute("Attributes/damage_types.txt") 241 | schools = load_attribute("Attributes/school.txt") 242 | durations = load_attribute("Attributes/duration.txt") 243 | 244 | i_range,i_levels,i_school,i_dtype,i_area,i_duration = 0,0,0,0,0,0 245 | if rang is not None: 246 | i_range = ranges.index(rang) 247 | 248 | elif level is not None: 249 | i_levels = levels.index(str(level)) 250 | 251 | elif area is not None: 252 | i_area = area_types.index(area) 253 | 254 | elif dtype is not None: 255 | i_dtype = dtypes.index(dtype) 256 | 257 | elif school is not None: 258 | i_school = schools.index(school) 259 | 260 | elif duration is not None: 261 | i_duration = durations.index(duration) 262 | attributes = [i_levels,i_school,i_dtype,i_area,i_range,i_duration] 263 | labels = [f"level: {level}", 264 | f"school: {school}", 265 | f"damage type: {dtype}", 266 | f"range: {rang}", 267 | f"area_type: {area}", 268 | f"duration: {duration}"] 269 | 270 | N = 2*len(attributes)+1 271 | 272 | if isinstance(colors,list) and len(colors) == 0 and breakdown == True: 273 | colors = [cmap(i/len(attributes)) for i in range(len(attributes))] 274 | if not os.path.isdir("Uniques/"): 275 | os.makedirs("Uniques/") 276 | if os.path.isfile(f'Uniques/{N}.npy'): 277 | non_repeating = np.load(f'Uniques/{N}.npy') 278 | else: 279 | non_repeating = generate_unique_combinations(N) 280 | non_repeating = np.array(non_repeating) 281 | np.save(f"Uniques/{N}.npy",non_repeating) 282 | 283 | input_array = [] 284 | for j,i in enumerate(attributes): 285 | input_array.append(non_repeating[i]) 286 | input_array = np.array(input_array)#note +1 s.t. 0th option is always open for empty input 287 | #print(input_array) 288 | draw_multiple_inputs(input_array,labels = labels,legend = legend, 289 | base_fn = base_fn,base_kwargs = base_kwargs, 290 | shape_fn = shape_fn,shape_kwargs = shape_kwargs, 291 | colors = colors,legend_loc = legend_loc) 292 | plt.title(title,fontsize = 30) 293 | if savename is not None: 294 | plt.savefig(savename,dpi = 250,transparent = True, bbox_inches='tight') 295 | plt.clf() 296 | else: 297 | plt.show() 298 | 299 | if __name__ == "__main__": 300 | import argparse 301 | parser = argparse.ArgumentParser() 302 | parser.add_argument("-spell-name",help = "spell name") 303 | parser.add_argument("-level",help = "necessary input: level of the spell") 304 | parser.add_argument("-range",help = "necessary input: range of the spell") 305 | parser.add_argument("-area",help = "necessary input: area type of the spell") 306 | parser.add_argument("-dtype",help = "necessary input: dtype of the spell") 307 | parser.add_argument("-school",help = "necessary input: school of the spell") 308 | parser.add_argument("--title",help = "title in plot") 309 | parser.add_argument("--savename",help = "savename of file") 310 | parser.add_argument("--legend",help = "bool to print legend or not (0 = False,1 = True)") 311 | parser.add_argument("--breakdown",help = "bool to control whether to breakdown the lines with colour") 312 | parser.add_argument("-ah", "--arg_help",help = "Prints the available options for the chosen attributes",action=argparse.BooleanOptionalAction) 313 | args = parser.parse_args() 314 | 315 | if args.arg_help: 316 | if args.range: 317 | print("--------Range--------") 318 | print("\n".join(load_attribute("Attributes/range.txt"))) 319 | if args.level: 320 | print("--------Level--------") 321 | print("\n".join(load_attribute("Attributes/levels.txt"))) 322 | if args.area: 323 | print("--------Area--------") 324 | print("\n".join(load_attribute("Attributes/area_types.txt"))) 325 | if args.dtype: 326 | print("--------Damage Types--------") 327 | print("\n".join(load_attribute("Attributes/damage_types.txt"))) 328 | if args.school: 329 | print("--------School--------") 330 | print("\n".join(load_attribute("Attributes/school.txt"))) 331 | else: 332 | if args.legend: 333 | if args.legend == 1: 334 | legend = False 335 | else: 336 | legend = True 337 | else: 338 | legend = False 339 | 340 | if args.breakdown: 341 | if args.breakdown == 1: 342 | breakdown = False 343 | else: 344 | breakdown = True 345 | else: 346 | breakdown= False 347 | 348 | if not args.title: 349 | title = None 350 | if not args.savename: 351 | savename = "output.png" 352 | 353 | if not args.level: 354 | level = "3" 355 | else: 356 | level = args.level 357 | 358 | if not args.range: 359 | rang = "150 feet" 360 | else: 361 | rang = args.range 362 | 363 | if not args.area: 364 | area = "sphere (30)" 365 | else: 366 | area = args.area 367 | 368 | if not args.dtype: 369 | dtype = "fire" 370 | else: 371 | dtype = args.dtype 372 | 373 | if not args.school: 374 | school = "evocation" 375 | else: 376 | school = args.school 377 | 378 | draw_spell(level,rang,area,dtype,school,title = title,legend = legend, 379 | base_fn = bases.polygon,shape_fn = line_shapes.straight, 380 | breakdown = breakdown,savename = savename) 381 | plt.clf() 382 | 383 | -------------------------------------------------------------------------------- /spells.json: -------------------------------------------------------------------------------- 1 | {"count":319,"results":[{"index":"acid-arrow","name":"Acid Arrow","url":"/api/spells/acid-arrow"},{"index":"acid-splash","name":"Acid Splash","url":"/api/spells/acid-splash"},{"index":"aid","name":"Aid","url":"/api/spells/aid"},{"index":"alarm","name":"Alarm","url":"/api/spells/alarm"},{"index":"alter-self","name":"Alter Self","url":"/api/spells/alter-self"},{"index":"animal-friendship","name":"Animal Friendship","url":"/api/spells/animal-friendship"},{"index":"animal-messenger","name":"Animal Messenger","url":"/api/spells/animal-messenger"},{"index":"animal-shapes","name":"Animal Shapes","url":"/api/spells/animal-shapes"},{"index":"animate-dead","name":"Animate Dead","url":"/api/spells/animate-dead"},{"index":"animate-objects","name":"Animate Objects","url":"/api/spells/animate-objects"},{"index":"antilife-shell","name":"Antilife Shell","url":"/api/spells/antilife-shell"},{"index":"antimagic-field","name":"Antimagic Field","url":"/api/spells/antimagic-field"},{"index":"antipathy-sympathy","name":"Antipathy/Sympathy","url":"/api/spells/antipathy-sympathy"},{"index":"arcane-eye","name":"Arcane Eye","url":"/api/spells/arcane-eye"},{"index":"arcane-hand","name":"Arcane Hand","url":"/api/spells/arcane-hand"},{"index":"arcane-lock","name":"Arcane Lock","url":"/api/spells/arcane-lock"},{"index":"arcane-sword","name":"Arcane Sword","url":"/api/spells/arcane-sword"},{"index":"arcanists-magic-aura","name":"Arcanist's Magic Aura","url":"/api/spells/arcanists-magic-aura"},{"index":"astral-projection","name":"Astral Projection","url":"/api/spells/astral-projection"},{"index":"augury","name":"Augury","url":"/api/spells/augury"},{"index":"awaken","name":"Awaken","url":"/api/spells/awaken"},{"index":"bane","name":"Bane","url":"/api/spells/bane"},{"index":"banishment","name":"Banishment","url":"/api/spells/banishment"},{"index":"barkskin","name":"Barkskin","url":"/api/spells/barkskin"},{"index":"beacon-of-hope","name":"Beacon of Hope","url":"/api/spells/beacon-of-hope"},{"index":"bestow-curse","name":"Bestow Curse","url":"/api/spells/bestow-curse"},{"index":"black-tentacles","name":"Black Tentacles","url":"/api/spells/black-tentacles"},{"index":"blade-barrier","name":"Blade Barrier","url":"/api/spells/blade-barrier"},{"index":"bless","name":"Bless","url":"/api/spells/bless"},{"index":"blight","name":"Blight","url":"/api/spells/blight"},{"index":"blindness-deafness","name":"Blindness/Deafness","url":"/api/spells/blindness-deafness"},{"index":"blink","name":"Blink","url":"/api/spells/blink"},{"index":"blur","name":"Blur","url":"/api/spells/blur"},{"index":"branding-smite","name":"Branding Smite","url":"/api/spells/branding-smite"},{"index":"burning-hands","name":"Burning Hands","url":"/api/spells/burning-hands"},{"index":"call-lightning","name":"Call Lightning","url":"/api/spells/call-lightning"},{"index":"calm-emotions","name":"Calm Emotions","url":"/api/spells/calm-emotions"},{"index":"chain-lightning","name":"Chain Lightning","url":"/api/spells/chain-lightning"},{"index":"charm-person","name":"Charm Person","url":"/api/spells/charm-person"},{"index":"chill-touch","name":"Chill Touch","url":"/api/spells/chill-touch"},{"index":"circle-of-death","name":"Circle of Death","url":"/api/spells/circle-of-death"},{"index":"clairvoyance","name":"Clairvoyance","url":"/api/spells/clairvoyance"},{"index":"clone","name":"Clone","url":"/api/spells/clone"},{"index":"cloudkill","name":"Cloudkill","url":"/api/spells/cloudkill"},{"index":"color-spray","name":"Color Spray","url":"/api/spells/color-spray"},{"index":"command","name":"Command","url":"/api/spells/command"},{"index":"commune","name":"Commune","url":"/api/spells/commune"},{"index":"commune-with-nature","name":"Commune With Nature","url":"/api/spells/commune-with-nature"},{"index":"comprehend-languages","name":"Comprehend Languages","url":"/api/spells/comprehend-languages"},{"index":"compulsion","name":"Compulsion","url":"/api/spells/compulsion"},{"index":"cone-of-cold","name":"Cone of Cold","url":"/api/spells/cone-of-cold"},{"index":"confusion","name":"Confusion","url":"/api/spells/confusion"},{"index":"conjure-animals","name":"Conjure Animals","url":"/api/spells/conjure-animals"},{"index":"conjure-celestial","name":"Conjure Celestial","url":"/api/spells/conjure-celestial"},{"index":"conjure-elemental","name":"Conjure Elemental","url":"/api/spells/conjure-elemental"},{"index":"conjure-fey","name":"Conjure Fey","url":"/api/spells/conjure-fey"},{"index":"conjure-minor-elementals","name":"Conjure Minor Elementals","url":"/api/spells/conjure-minor-elementals"},{"index":"conjure-woodland-beings","name":"Conjure Woodland Beings","url":"/api/spells/conjure-woodland-beings"},{"index":"contact-other-plane","name":"Contact Other Plane","url":"/api/spells/contact-other-plane"},{"index":"contagion","name":"Contagion","url":"/api/spells/contagion"},{"index":"contingency","name":"Contingency","url":"/api/spells/contingency"},{"index":"continual-flame","name":"Continual Flame","url":"/api/spells/continual-flame"},{"index":"control-water","name":"Control Water","url":"/api/spells/control-water"},{"index":"control-weather","name":"Control Weather","url":"/api/spells/control-weather"},{"index":"counterspell","name":"Counterspell","url":"/api/spells/counterspell"},{"index":"create-food-and-water","name":"Create Food and Water","url":"/api/spells/create-food-and-water"},{"index":"create-or-destroy-water","name":"Create or Destroy Water","url":"/api/spells/create-or-destroy-water"},{"index":"create-undead","name":"Create Undead","url":"/api/spells/create-undead"},{"index":"creation","name":"Creation","url":"/api/spells/creation"},{"index":"cure-wounds","name":"Cure Wounds","url":"/api/spells/cure-wounds"},{"index":"dancing-lights","name":"Dancing Lights","url":"/api/spells/dancing-lights"},{"index":"darkness","name":"Darkness","url":"/api/spells/darkness"},{"index":"darkvision","name":"Darkvision","url":"/api/spells/darkvision"},{"index":"daylight","name":"Daylight","url":"/api/spells/daylight"},{"index":"death-ward","name":"Death Ward","url":"/api/spells/death-ward"},{"index":"delayed-blast-fireball","name":"Delayed Blast Fireball","url":"/api/spells/delayed-blast-fireball"},{"index":"demiplane","name":"Demiplane","url":"/api/spells/demiplane"},{"index":"detect-evil-and-good","name":"Detect Evil and Good","url":"/api/spells/detect-evil-and-good"},{"index":"detect-magic","name":"Detect Magic","url":"/api/spells/detect-magic"},{"index":"detect-poison-and-disease","name":"Detect Poison and Disease","url":"/api/spells/detect-poison-and-disease"},{"index":"detect-thoughts","name":"Detect Thoughts","url":"/api/spells/detect-thoughts"},{"index":"dimension-door","name":"Dimension Door","url":"/api/spells/dimension-door"},{"index":"disguise-self","name":"Disguise Self","url":"/api/spells/disguise-self"},{"index":"disintegrate","name":"Disintegrate","url":"/api/spells/disintegrate"},{"index":"dispel-evil-and-good","name":"Dispel Evil and Good","url":"/api/spells/dispel-evil-and-good"},{"index":"dispel-magic","name":"Dispel Magic","url":"/api/spells/dispel-magic"},{"index":"divination","name":"Divination","url":"/api/spells/divination"},{"index":"divine-favor","name":"Divine Favor","url":"/api/spells/divine-favor"},{"index":"divine-word","name":"Divine Word","url":"/api/spells/divine-word"},{"index":"dominate-beast","name":"Dominate Beast","url":"/api/spells/dominate-beast"},{"index":"dominate-monster","name":"Dominate Monster","url":"/api/spells/dominate-monster"},{"index":"dominate-person","name":"Dominate Person","url":"/api/spells/dominate-person"},{"index":"dream","name":"Dream","url":"/api/spells/dream"},{"index":"druidcraft","name":"Druidcraft","url":"/api/spells/druidcraft"},{"index":"earthquake","name":"Earthquake","url":"/api/spells/earthquake"},{"index":"eldritch-blast","name":"Eldritch Blast","url":"/api/spells/eldritch-blast"},{"index":"enhance-ability","name":"Enhance Ability","url":"/api/spells/enhance-ability"},{"index":"enlarge-reduce","name":"Enlarge/Reduce","url":"/api/spells/enlarge-reduce"},{"index":"entangle","name":"Entangle","url":"/api/spells/entangle"},{"index":"enthrall","name":"Enthrall","url":"/api/spells/enthrall"},{"index":"etherealness","name":"Etherealness","url":"/api/spells/etherealness"},{"index":"expeditious-retreat","name":"Expeditious Retreat","url":"/api/spells/expeditious-retreat"},{"index":"eyebite","name":"Eyebite","url":"/api/spells/eyebite"},{"index":"fabricate","name":"Fabricate","url":"/api/spells/fabricate"},{"index":"faerie-fire","name":"Faerie Fire","url":"/api/spells/faerie-fire"},{"index":"faithful-hound","name":"Faithful Hound","url":"/api/spells/faithful-hound"},{"index":"false-life","name":"False Life","url":"/api/spells/false-life"},{"index":"fear","name":"Fear","url":"/api/spells/fear"},{"index":"feather-fall","name":"Feather Fall","url":"/api/spells/feather-fall"},{"index":"feeblemind","name":"Feeblemind","url":"/api/spells/feeblemind"},{"index":"find-familiar","name":"Find Familiar","url":"/api/spells/find-familiar"},{"index":"find-steed","name":"Find Steed","url":"/api/spells/find-steed"},{"index":"find-the-path","name":"Find the Path","url":"/api/spells/find-the-path"},{"index":"find-traps","name":"Find Traps","url":"/api/spells/find-traps"},{"index":"finger-of-death","name":"Finger of Death","url":"/api/spells/finger-of-death"},{"index":"fire-bolt","name":"Fire Bolt","url":"/api/spells/fire-bolt"},{"index":"fire-shield","name":"Fire Shield","url":"/api/spells/fire-shield"},{"index":"fire-storm","name":"Fire Storm","url":"/api/spells/fire-storm"},{"index":"fireball","name":"Fireball","url":"/api/spells/fireball"},{"index":"flame-blade","name":"Flame Blade","url":"/api/spells/flame-blade"},{"index":"flame-strike","name":"Flame Strike","url":"/api/spells/flame-strike"},{"index":"flaming-sphere","name":"Flaming Sphere","url":"/api/spells/flaming-sphere"},{"index":"flesh-to-stone","name":"Flesh to Stone","url":"/api/spells/flesh-to-stone"},{"index":"floating-disk","name":"Floating Disk","url":"/api/spells/floating-disk"},{"index":"fly","name":"Fly","url":"/api/spells/fly"},{"index":"fog-cloud","name":"Fog Cloud","url":"/api/spells/fog-cloud"},{"index":"forbiddance","name":"Forbiddance","url":"/api/spells/forbiddance"},{"index":"forcecage","name":"Forcecage","url":"/api/spells/forcecage"},{"index":"foresight","name":"Foresight","url":"/api/spells/foresight"},{"index":"freedom-of-movement","name":"Freedom of Movement","url":"/api/spells/freedom-of-movement"},{"index":"freezing-sphere","name":"Freezing Sphere","url":"/api/spells/freezing-sphere"},{"index":"gaseous-form","name":"Gaseous Form","url":"/api/spells/gaseous-form"},{"index":"gate","name":"Gate","url":"/api/spells/gate"},{"index":"geas","name":"Geas","url":"/api/spells/geas"},{"index":"gentle-repose","name":"Gentle Repose","url":"/api/spells/gentle-repose"},{"index":"giant-insect","name":"Giant Insect","url":"/api/spells/giant-insect"},{"index":"glibness","name":"Glibness","url":"/api/spells/glibness"},{"index":"globe-of-invulnerability","name":"Globe of Invulnerability","url":"/api/spells/globe-of-invulnerability"},{"index":"glyph-of-warding","name":"Glyph of Warding","url":"/api/spells/glyph-of-warding"},{"index":"goodberry","name":"Goodberry","url":"/api/spells/goodberry"},{"index":"grease","name":"Grease","url":"/api/spells/grease"},{"index":"greater-invisibility","name":"Greater Invisibility","url":"/api/spells/greater-invisibility"},{"index":"greater-restoration","name":"Greater Restoration","url":"/api/spells/greater-restoration"},{"index":"guardian-of-faith","name":"Guardian of Faith","url":"/api/spells/guardian-of-faith"},{"index":"guards-and-wards","name":"Guards and Wards","url":"/api/spells/guards-and-wards"},{"index":"guidance","name":"Guidance","url":"/api/spells/guidance"},{"index":"guiding-bolt","name":"Guiding Bolt","url":"/api/spells/guiding-bolt"},{"index":"gust-of-wind","name":"Gust of Wind","url":"/api/spells/gust-of-wind"},{"index":"hallow","name":"Hallow","url":"/api/spells/hallow"},{"index":"hallucinatory-terrain","name":"Hallucinatory Terrain","url":"/api/spells/hallucinatory-terrain"},{"index":"harm","name":"Harm","url":"/api/spells/harm"},{"index":"haste","name":"Haste","url":"/api/spells/haste"},{"index":"heal","name":"Heal","url":"/api/spells/heal"},{"index":"healing-word","name":"Healing Word","url":"/api/spells/healing-word"},{"index":"heat-metal","name":"Heat Metal","url":"/api/spells/heat-metal"},{"index":"hellish-rebuke","name":"Hellish Rebuke","url":"/api/spells/hellish-rebuke"},{"index":"heroes-feast","name":"Heroes' Feast","url":"/api/spells/heroes-feast"},{"index":"heroism","name":"Heroism","url":"/api/spells/heroism"},{"index":"hideous-laughter","name":"Hideous Laughter","url":"/api/spells/hideous-laughter"},{"index":"hold-monster","name":"Hold Monster","url":"/api/spells/hold-monster"},{"index":"hold-person","name":"Hold Person","url":"/api/spells/hold-person"},{"index":"holy-aura","name":"Holy Aura","url":"/api/spells/holy-aura"},{"index":"hunters-mark","name":"Hunter's Mark","url":"/api/spells/hunters-mark"},{"index":"hypnotic-pattern","name":"Hypnotic Pattern","url":"/api/spells/hypnotic-pattern"},{"index":"ice-storm","name":"Ice Storm","url":"/api/spells/ice-storm"},{"index":"identify","name":"Identify","url":"/api/spells/identify"},{"index":"illusory-script","name":"Illusory Script","url":"/api/spells/illusory-script"},{"index":"imprisonment","name":"Imprisonment","url":"/api/spells/imprisonment"},{"index":"incendiary-cloud","name":"Incendiary Cloud","url":"/api/spells/incendiary-cloud"},{"index":"inflict-wounds","name":"Inflict Wounds","url":"/api/spells/inflict-wounds"},{"index":"insect-plague","name":"Insect Plague","url":"/api/spells/insect-plague"},{"index":"instant-summons","name":"Instant Summons","url":"/api/spells/instant-summons"},{"index":"invisibility","name":"Invisibility","url":"/api/spells/invisibility"},{"index":"irresistible-dance","name":"Irresistible Dance","url":"/api/spells/irresistible-dance"},{"index":"jump","name":"Jump","url":"/api/spells/jump"},{"index":"knock","name":"Knock","url":"/api/spells/knock"},{"index":"legend-lore","name":"Legend Lore","url":"/api/spells/legend-lore"},{"index":"lesser-restoration","name":"Lesser Restoration","url":"/api/spells/lesser-restoration"},{"index":"levitate","name":"Levitate","url":"/api/spells/levitate"},{"index":"light","name":"Light","url":"/api/spells/light"},{"index":"lightning-bolt","name":"Lightning Bolt","url":"/api/spells/lightning-bolt"},{"index":"locate-animals-or-plants","name":"Locate Animals or Plants","url":"/api/spells/locate-animals-or-plants"},{"index":"locate-creature","name":"Locate Creature","url":"/api/spells/locate-creature"},{"index":"locate-object","name":"Locate Object","url":"/api/spells/locate-object"},{"index":"longstrider","name":"Longstrider","url":"/api/spells/longstrider"},{"index":"mage-armor","name":"Mage Armor","url":"/api/spells/mage-armor"},{"index":"mage-hand","name":"Mage Hand","url":"/api/spells/mage-hand"},{"index":"magic-circle","name":"Magic Circle","url":"/api/spells/magic-circle"},{"index":"magic-jar","name":"Magic Jar","url":"/api/spells/magic-jar"},{"index":"magic-missile","name":"Magic Missile","url":"/api/spells/magic-missile"},{"index":"magic-mouth","name":"Magic Mouth","url":"/api/spells/magic-mouth"},{"index":"magic-weapon","name":"Magic Weapon","url":"/api/spells/magic-weapon"},{"index":"magnificent-mansion","name":"Magnificent Mansion","url":"/api/spells/magnificent-mansion"},{"index":"major-image","name":"Major Image","url":"/api/spells/major-image"},{"index":"mass-cure-wounds","name":"Mass Cure Wounds","url":"/api/spells/mass-cure-wounds"},{"index":"mass-heal","name":"Mass Heal","url":"/api/spells/mass-heal"},{"index":"mass-healing-word","name":"Mass Healing Word","url":"/api/spells/mass-healing-word"},{"index":"mass-suggestion","name":"Mass Suggestion","url":"/api/spells/mass-suggestion"},{"index":"maze","name":"Maze","url":"/api/spells/maze"},{"index":"meld-into-stone","name":"Meld Into Stone","url":"/api/spells/meld-into-stone"},{"index":"mending","name":"Mending","url":"/api/spells/mending"},{"index":"message","name":"Message","url":"/api/spells/message"},{"index":"meteor-swarm","name":"Meteor Swarm","url":"/api/spells/meteor-swarm"},{"index":"mind-blank","name":"Mind Blank","url":"/api/spells/mind-blank"},{"index":"minor-illusion","name":"Minor Illusion","url":"/api/spells/minor-illusion"},{"index":"mirage-arcane","name":"Mirage Arcane","url":"/api/spells/mirage-arcane"},{"index":"mirror-image","name":"Mirror Image","url":"/api/spells/mirror-image"},{"index":"mislead","name":"Mislead","url":"/api/spells/mislead"},{"index":"misty-step","name":"Misty Step","url":"/api/spells/misty-step"},{"index":"modify-memory","name":"Modify Memory","url":"/api/spells/modify-memory"},{"index":"moonbeam","name":"Moonbeam","url":"/api/spells/moonbeam"},{"index":"move-earth","name":"Move Earth","url":"/api/spells/move-earth"},{"index":"nondetection","name":"Nondetection","url":"/api/spells/nondetection"},{"index":"pass-without-trace","name":"Pass Without Trace","url":"/api/spells/pass-without-trace"},{"index":"passwall","name":"Passwall","url":"/api/spells/passwall"},{"index":"phantasmal-killer","name":"Phantasmal Killer","url":"/api/spells/phantasmal-killer"},{"index":"phantom-steed","name":"Phantom Steed","url":"/api/spells/phantom-steed"},{"index":"planar-ally","name":"Planar Ally","url":"/api/spells/planar-ally"},{"index":"planar-binding","name":"Planar Binding","url":"/api/spells/planar-binding"},{"index":"plane-shift","name":"Plane Shift","url":"/api/spells/plane-shift"},{"index":"plant-growth","name":"Plant Growth","url":"/api/spells/plant-growth"},{"index":"poison-spray","name":"Poison Spray","url":"/api/spells/poison-spray"},{"index":"polymorph","name":"Polymorph","url":"/api/spells/polymorph"},{"index":"power-word-kill","name":"Power Word Kill","url":"/api/spells/power-word-kill"},{"index":"power-word-stun","name":"Power Word Stun","url":"/api/spells/power-word-stun"},{"index":"prayer-of-healing","name":"Prayer of Healing","url":"/api/spells/prayer-of-healing"},{"index":"prestidigitation","name":"Prestidigitation","url":"/api/spells/prestidigitation"},{"index":"prismatic-spray","name":"Prismatic Spray","url":"/api/spells/prismatic-spray"},{"index":"prismatic-wall","name":"Prismatic Wall","url":"/api/spells/prismatic-wall"},{"index":"private-sanctum","name":"Private Sanctum","url":"/api/spells/private-sanctum"},{"index":"produce-flame","name":"Produce Flame","url":"/api/spells/produce-flame"},{"index":"programmed-illusion","name":"Programmed Illusion","url":"/api/spells/programmed-illusion"},{"index":"project-image","name":"Project Image","url":"/api/spells/project-image"},{"index":"protection-from-energy","name":"Protection From Energy","url":"/api/spells/protection-from-energy"},{"index":"protection-from-evil-and-good","name":"Protection from Evil and Good","url":"/api/spells/protection-from-evil-and-good"},{"index":"protection-from-poison","name":"Protection from Poison","url":"/api/spells/protection-from-poison"},{"index":"purify-food-and-drink","name":"Purify Food and Drink","url":"/api/spells/purify-food-and-drink"},{"index":"raise-dead","name":"Raise Dead","url":"/api/spells/raise-dead"},{"index":"ray-of-enfeeblement","name":"Ray of Enfeeblement","url":"/api/spells/ray-of-enfeeblement"},{"index":"ray-of-frost","name":"Ray of Frost","url":"/api/spells/ray-of-frost"},{"index":"regenerate","name":"Regenerate","url":"/api/spells/regenerate"},{"index":"reincarnate","name":"Reincarnate","url":"/api/spells/reincarnate"},{"index":"remove-curse","name":"Remove Curse","url":"/api/spells/remove-curse"},{"index":"resilient-sphere","name":"Resilient Sphere","url":"/api/spells/resilient-sphere"},{"index":"resistance","name":"Resistance","url":"/api/spells/resistance"},{"index":"resurrection","name":"Resurrection","url":"/api/spells/resurrection"},{"index":"reverse-gravity","name":"Reverse Gravity","url":"/api/spells/reverse-gravity"},{"index":"revivify","name":"Revivify","url":"/api/spells/revivify"},{"index":"rope-trick","name":"Rope Trick","url":"/api/spells/rope-trick"},{"index":"sacred-flame","name":"Sacred Flame","url":"/api/spells/sacred-flame"},{"index":"sanctuary","name":"Sanctuary","url":"/api/spells/sanctuary"},{"index":"scorching-ray","name":"Scorching Ray","url":"/api/spells/scorching-ray"},{"index":"scrying","name":"Scrying","url":"/api/spells/scrying"},{"index":"secret-chest","name":"Secret Chest","url":"/api/spells/secret-chest"},{"index":"see-invisibility","name":"See Invisibility","url":"/api/spells/see-invisibility"},{"index":"seeming","name":"Seeming","url":"/api/spells/seeming"},{"index":"sending","name":"Sending","url":"/api/spells/sending"},{"index":"sequester","name":"Sequester","url":"/api/spells/sequester"},{"index":"shapechange","name":"Shapechange","url":"/api/spells/shapechange"},{"index":"shatter","name":"Shatter","url":"/api/spells/shatter"},{"index":"shield","name":"Shield","url":"/api/spells/shield"},{"index":"shield-of-faith","name":"Shield of Faith","url":"/api/spells/shield-of-faith"},{"index":"shillelagh","name":"Shillelagh","url":"/api/spells/shillelagh"},{"index":"shocking-grasp","name":"Shocking Grasp","url":"/api/spells/shocking-grasp"},{"index":"silence","name":"Silence","url":"/api/spells/silence"},{"index":"silent-image","name":"Silent Image","url":"/api/spells/silent-image"},{"index":"simulacrum","name":"Simulacrum","url":"/api/spells/simulacrum"},{"index":"sleep","name":"Sleep","url":"/api/spells/sleep"},{"index":"sleet-storm","name":"Sleet Storm","url":"/api/spells/sleet-storm"},{"index":"slow","name":"Slow","url":"/api/spells/slow"},{"index":"spare-the-dying","name":"Spare the Dying","url":"/api/spells/spare-the-dying"},{"index":"speak-with-animals","name":"Speak with Animals","url":"/api/spells/speak-with-animals"},{"index":"speak-with-dead","name":"Speak with Dead","url":"/api/spells/speak-with-dead"},{"index":"speak-with-plants","name":"Speak with Plants","url":"/api/spells/speak-with-plants"},{"index":"spider-climb","name":"Spider Climb","url":"/api/spells/spider-climb"},{"index":"spike-growth","name":"Spike Growth","url":"/api/spells/spike-growth"},{"index":"spirit-guardians","name":"Spirit Guardians","url":"/api/spells/spirit-guardians"},{"index":"spiritual-weapon","name":"Spiritual Weapon","url":"/api/spells/spiritual-weapon"},{"index":"stinking-cloud","name":"Stinking Cloud","url":"/api/spells/stinking-cloud"},{"index":"stone-shape","name":"Stone Shape","url":"/api/spells/stone-shape"},{"index":"stoneskin","name":"Stoneskin","url":"/api/spells/stoneskin"},{"index":"storm-of-vengeance","name":"Storm of Vengeance","url":"/api/spells/storm-of-vengeance"},{"index":"suggestion","name":"Suggestion","url":"/api/spells/suggestion"},{"index":"sunbeam","name":"Sunbeam","url":"/api/spells/sunbeam"},{"index":"sunburst","name":"Sunburst","url":"/api/spells/sunburst"},{"index":"symbol","name":"Symbol","url":"/api/spells/symbol"},{"index":"telekinesis","name":"Telekinesis","url":"/api/spells/telekinesis"},{"index":"telepathic-bond","name":"Telepathic Bond","url":"/api/spells/telepathic-bond"},{"index":"teleport","name":"Teleport","url":"/api/spells/teleport"},{"index":"teleportation-circle","name":"Teleportation Circle","url":"/api/spells/teleportation-circle"},{"index":"thaumaturgy","name":"Thaumaturgy","url":"/api/spells/thaumaturgy"},{"index":"thunderwave","name":"Thunderwave","url":"/api/spells/thunderwave"},{"index":"time-stop","name":"Time Stop","url":"/api/spells/time-stop"},{"index":"tiny-hut","name":"Tiny Hut","url":"/api/spells/tiny-hut"},{"index":"tongues","name":"Tongues","url":"/api/spells/tongues"},{"index":"transport-via-plants","name":"Transport via Plants","url":"/api/spells/transport-via-plants"},{"index":"tree-stride","name":"Tree Stride","url":"/api/spells/tree-stride"},{"index":"true-polymorph","name":"True Polymorph","url":"/api/spells/true-polymorph"},{"index":"true-resurrection","name":"True Resurrection","url":"/api/spells/true-resurrection"},{"index":"true-seeing","name":"True Seeing","url":"/api/spells/true-seeing"},{"index":"true-strike","name":"True Strike","url":"/api/spells/true-strike"},{"index":"unseen-servant","name":"Unseen Servant","url":"/api/spells/unseen-servant"},{"index":"vampiric-touch","name":"Vampiric Touch","url":"/api/spells/vampiric-touch"},{"index":"vicious-mockery","name":"Vicious Mockery","url":"/api/spells/vicious-mockery"},{"index":"wall-of-fire","name":"Wall of Fire","url":"/api/spells/wall-of-fire"},{"index":"wall-of-force","name":"Wall of Force","url":"/api/spells/wall-of-force"},{"index":"wall-of-ice","name":"Wall of Ice","url":"/api/spells/wall-of-ice"},{"index":"wall-of-stone","name":"Wall of Stone","url":"/api/spells/wall-of-stone"},{"index":"wall-of-thorns","name":"Wall of Thorns","url":"/api/spells/wall-of-thorns"},{"index":"warding-bond","name":"Warding Bond","url":"/api/spells/warding-bond"},{"index":"water-breathing","name":"Water Breathing","url":"/api/spells/water-breathing"},{"index":"water-walk","name":"Water Walk","url":"/api/spells/water-walk"},{"index":"web","name":"Web","url":"/api/spells/web"},{"index":"weird","name":"Weird","url":"/api/spells/weird"},{"index":"wind-walk","name":"Wind Walk","url":"/api/spells/wind-walk"},{"index":"wind-wall","name":"Wind Wall","url":"/api/spells/wind-wall"},{"index":"wish","name":"Wish","url":"/api/spells/wish"},{"index":"word-of-recall","name":"Word of Recall","url":"/api/spells/word-of-recall"},{"index":"zone-of-truth","name":"Zone of Truth","url":"/api/spells/zone-of-truth"}]} --------------------------------------------------------------------------------