#!/usr/bin/env python3 import pickle import pprint import re from copy import deepcopy charsheet = { 'bab': 9, 'str': 20, 'dex': 14, 'con': 14, 'int': 16, 'wis': 10, 'cha': 10, 'level': [ ('fighter', 10) ], 'feat': [ 'toughness', 'armor proficiency, medium', 'power attack', 'combat stamina', 'improved unarmed strike', 'combat expertise', 'point blank shot', 'point-blank shot', 'deadly aim', 'improved bull rush', 'improved overrun', 'improved trip', 'weapon focus', 'weapon focus with chosen weapon', 'weapon focus (greataxe)', 'weapon focus (glaive)', 'weapon focus with chosen melee weapon', 'weapon focus with selected weapon', 'weapon focus with the chosen weapon', 'weapon focus (any two-handed reach weapon)', 'weapon specialization', 'weapon specialization with selected weapon', 'improved critical', 'armor focus', 'proficiency with selected armor', 'medium armor proficiency', 'light armor proficiency', 'proficiency with light armor', 'proficiency with medium armor', 'armor training class feature', 'armor trainingclass feature', 'martial focus', 'weapon training class feature', 'combat reflexes', 'you have no levels in a class that has the grit class feature', 'any combat feat', 'any good alignment', 'bravery +1 class feature', 'human', 'medium size', 'no levels in a class that has the favored enemy class feature', 'no levels in a class that has the panache class feature', 'nonlawful', 'proficiency with selected weapon', 'proficient with all martial weapons', 'proficiency with weapon', 'proficiency with chosen weapon', 'proficiency with armor spikes', 'proficient with armor spikes', 'proficient with scimitar', 'proficient with sing', 'susceptibility to bleed damage', 'and proficiency with the selected weapon.', 'bravery class feature', ], 'skill': [ ('knowledge (arcane)', 9), ('knowledge (dungeoneering)', 9), ('knowledge (local)', 9), ('knowledge (nature)', 9), ('knowledge (planes)', 9), ('knowledge (religion)', 9), ('knowledge (engineering)', 1), ('knowledge (geography)', 2), ('knowledge (history)', 4), ('acrobatics', 9), ('climb', 3), ('atealth', 1), ('disable device', 1), ('spellcraft', 1), ('intimidate', 1), ('use magic device', 1), ('diplomacy', 1), ('perception', 1), ('linguistics', 4), ('lore (divine battles)', 4), ('art (anatomy)', 1), ('perform (oratory)', 2), ], } with open('feats.pickle', 'rb') as f: allfeats = pickle.load(f) def texify(textstr): return textstr def grabfeat(feats, featname): for f in feats: if f['name'].lower() == featname.lower(): return f def fillsreqs(pr, charsheet): qual = True for attr in ['str', 'dex', 'con', 'int', 'wis', 'cha', 'bab']: if attr in pr: if int(pr[attr][0]) > charsheet[attr]: qual = False if 'level' in pr: for l in pr['level']: llqual = False for ll in charsheet['level']: if l[0] == ll[0] and int(l[1]) <= ll[1]: llqual = True break if llqual == False: qual = False break if 'skill' in pr: for s in pr['skill']: squal = False for cs in charsheet['skill']: if s[0] == cs[0] and int(s[1]) <= cs[1]: squal = True break if squal == False: qual = False break if 'feat' in pr: for f in pr['feat']: if f != '' and f not in charsheet['feat']: qual = False return qual def qualfeat(feat, charsheet): if feat['prereqs']: return fillsreqs(feat['prereqs'], charsheet) else: return True def qualfeats(feats, charsheet): return [f for f in feats if qualfeat(f, charsheet) and f['name'].lower() not in charsheet['feat']] def addchains(feats, charsheet): featnames = [f['name'] for f in feats] for f in feats: newreqs = deepcopy(charsheet) newreqs['feat'].append(f['name'].lower()) newcandidates = qualfeats(allfeats, newreqs) newfeats = [ nf for nf in newcandidates if nf['name'] not in featnames ] if len(newfeats) > 0: f['chain'] = newfeats def prefeats(feats): for f in allfeats: prefeats = {} fs = f['prereqs']['feat'] if isinstance(fs, list): for pf in fs: if pf in prefeats: prefeats[pf] += 1 else: prefeats[pf] = 1 else: if fs in prefeats: prefeats[fs] += 1 else: prefeats[fs] = 1 return prefeats def asfeat(feat): featsnip = '\subsubsection*{%s}\n\n' % feat['name'] featsnip += '\\textbf{Benefit:} %s\n\n' % feat['benefit'] if feat['trick']: featsnip += '\\textbf{Combat Trick:} %s\n\n' % feat['trick'] if feat['special']: featsnip += '\\textbf{Special:} %s\n\n' % feat['special'] if 'chain' in feat: featsnip += chainfeats(feat['chain']) return featsnip def aschainfeat(feat): featsnip = '\item \\textbf{%s} %s\n\n' % (feat['name'], feat['benefit']) if feat['trick']: featsnip += '\\textbf{Combat Trick:} %s\n\n' % feat['trick'] if feat['special']: featsnip += '\\textbf{Special:} %s\n\n' % feat['special'] return featsnip def chainfeats(feats): chainsnip = '\\begin{itemize}\n\n' for f in feats: chainsnip += aschainfeat(f) chainsnip += '\end{itemize}\n\n' return chainsnip def takenfeats(cs): taken = [] for f in sorted(charsheet['feat']): ff = grabfeat(allfeats, f) if ff: taken.append(ff) return taken if __name__ == '__main__': # generate LaTeX for taken feats sectsnip = '' for f in takenfeats(charsheet): sectsnip += asfeat(f) with open('taken.snip', 'w') as f: print(sectsnip, file=f) # generate lists of qualifying feats and one-step chains cantake = qualfeats(allfeats, charsheet) addchains(cantake, charsheet) # split list into one-offs, and feats with chaining singles = [] chainers = [] for f in cantake: print(f['name']) if 'chain' in f: chainers.append(f) else: singles.append(f) # LaTeXify both lists singlesnip = '' for f in singles: singlesnip += asfeat(f) with open('singles.snip', 'w') as f: print(singlesnip, file=f) chainsnip = '' for f in chainers: chainsnip += asfeat(f) with open('chainers.snip', 'w') as f: print(chainsnip, file = f)