Skip to content

Instantly share code, notes, and snippets.

@zhengyangchoong
Created September 21, 2017 11:07
Show Gist options
  • Save zhengyangchoong/24847454a12dee2203619657db8801e5 to your computer and use it in GitHub Desktop.
Save zhengyangchoong/24847454a12dee2203619657db8801e5 to your computer and use it in GitHub Desktop.
monster statblock generator (with fuzzy search)
# Writes to main.tex with \\latex code for monster manuals
# argparse to get monster names. Write to latex file with statblocks, and compile
# uses Evan Bergeron's awesome latex library. https://github.com/evanbergeron/DND-5e-LaTeX-Template
# and also a compiled json file of dnd stats by /u/droiddruid https://www.reddit.com/r/dndnext/comments/43a09o/srd_monsters_in_json_format/?ref=share&ref_source=link
import argparse
import subprocess
import json
import string
import os, sys
from fuzzywuzzy import fuzz
from fuzzywuzzy import process
import copy
def write_preamble(f):
preamble = "\\documentclass[10pt,twoside,twocolumn,openany]{article} \n \\usepackage[a4paper]{geometry} \n \\usepackage[bg-print]{dnd} \n \\usepackage[english]{babel} \n \\usepackage[utf8]{inputenc} \n \\begin{document} \n \\fontfamily{ppl}\\selectfont \n"
f.write(preamble)
return f
def main(fileOutput, monsterlist):
monsterlist_indices = []
mm = open("5e-SRD-Monsters.json", "r")
_mm = json.load(mm)
f = open("latex/"+fileOutput + ".tex", "w")
f = write_preamble(f)
monster_index = [string.lower(_mm[i]["name"]) for i in xrange(len(_mm) - 1)]
""" the very last one is the copyright info which doesn't have the name info """
for i in monsterlist:
try:
monsterlist_indices.append(monster_index.index(i))
except ValueError:
a = [fuzz.ratio(i,monster_index[_]) for _ in xrange(len(monster_index))]
print max(a)
monsterlist_indices.append(a.index(max(a)))
# generate the monster box in latex
x = ""
for i in monsterlist_indices:
z =_mm[i] # im lazy
#print z["name"]
x += "\\begin{{monsterbox}}{{{}}}\n".format(z["name"])
# size subtype type alignment
x += "\\textit{{{0} {1} ({2}), {3}}}\\\\\n".format(z["size"], z["subtype"], z["type"], z["alignment"])
# AC, hp, speed
x += "\\hline\n\\basics[armorclass={},hitpoints=\\dice{{{}}}, speed = {}]\n\\hline\n".format(z["armor_class"], z["hit_dice"], z["speed"])
# stats
x += "\\stats[STR = \\stat{{{}}}, DEX = \\stat{{{}}}, CON = \\stat{{{}}}, INT = \\stat{{{}}}, WIS = \\stat{{{}}}, CHA = \\stat{{{}}}]\n\\hline\n".format(z["strength"], z["dexterity"], z["constitution"], z["intelligence"], z["wisdom"], z["charisma"])
# saving throws, immunities, skills, vulnerabilities
zz = copy.deepcopy(z)
def extra_details(zz):
try:
zz.pop('actions', None)
zz.pop('special_abilities', None)
zz.pop('legendary_actions', None)
zz.pop('name', None)
for i in ['charisma', 'intelligence', 'dexterity', 'constitution', 'wisdom', 'strength', 'languages', 'hit_points', 'hit_dice', 'subtype', 'senses', 'speed', 'alignment', 'armor_class', 'type', 'challenge_rating', 'size']:
zz.pop(i, None)
except:
pass
saves = [x for x in zz.keys() if "save" in x]
condition_stuff = [x for x in zz.keys() if "immunities" in x or "damage" in x]
_x = ""
if len(saves) > 0:
_x += "savingthrows={"
for i in saves:
_x += ("{} {}, ".format(i[:3].upper(), zz[i]))
_x += "}, "
def addthing(zz, thing):
_x = ""
if len(zz[thing]) > 0:
_x += "{} = ".format(''.join(thing.split("_")))
_x += "{"
_x += "{}".format(zz[thing])
_x += "}, "
return _x
for i in condition_stuff:
_x += addthing(zz, i)
for i in (saves + condition_stuff):
zz.pop(i)
if len(zz) > 0: # more than one skill. zz is a dictionary.
_x += "skills={"
for i in zz:
_x += "{} {}, ".format(i.title(), zz[i])
_x += "},"
return _x
extrastuff = extra_details(zz)
# details
x += "\\details[senses = {{{}}}, challenge={}, languages={{{}}}, {}]\n\\hline\\\\[1mm]".format(z["senses"], z["challenge_rating"], z["languages"], extrastuff)
try: # special abilities. sa is a dictionary
for sa in z["special_abilities"]:
x += "\\begin{{monsteraction}}[{}]\n".format(sa["name"])
x += "{}\n\\end{{monsteraction}}\n\n".format(sa["desc"])
except KeyError:
pass
# monster actions
x += "\\monstersection{Actions}\n"
for ab in z["actions"]:
x += "\\begin{{monsteraction}}[{}]\n".format(ab["name"])
x += "{}\n\\end{{monsteraction}}\n\n".format(ab["desc"])
try:
_ = z["legendary_actions"]
x += "\\monstersection{Legendary Actions}\n"
for la in z["legendary_actions"]:
x += "\\begin{{monsteraction}}[{}]\n".format(la["name"])
x += "{}\n\\end{{monsteraction}}\n\n".format(la["desc"])
except KeyError:
pass
x += "\\end{monsterbox}\n"
x += ("\\end{document}\n")
#print x
f.write(x)
f.close()
os.chdir("latex")
subprocess.Popen(["pdflatex","-interaction=nonstopmode", fileOutput])
#print monsterlist
parser = argparse.ArgumentParser(description="Get monster list")
parser.add_argument("fileOutput", type = str, help = "name of output file (no .tex behind)")
parser.add_argument("monsters", nargs="+", help= "type in the monster name from the 5e rulebook. if it has spaces, enclose it with quotation marks. separate monsters by spaces")
c = parser.parse_args()
#print c.fileOutput
main(c.fileOutput, c.monsters)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment