#!/usr/bin/env python3 import pickle import pprint import re from copy import deepcopy charsheet = { 'bab': 14, 'str': 23, 'dex': 18, 'con': 16, 'int': 18, 'wis': 10, 'cha': 10, 'level': [ ('fighter', 14) ], 'feat': [ 'toughness', 'armor proficiency, medium', 'armor proficiency, light', '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', 'dodge', 'artful dodge', 'cut from the air', 'spellcut', 'greater weapon focus', 'greater weapon focus with selected weapon', 'greater weapon specialization', 'greater weapon specialization with selected weapon', 'you have no levels in a class that has the grit class feature', 'any combat feat', 'any good alignment', 'bravery +1 class feature', 'bravery +2 class feature', 'bravery +3 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', 'martial weapon proficiency', 'proficiency with weapon', 'proficiency with chosen weapon', 'proficiency with armor spikes', 'proficient with armor spikes', 'proficient with scimitar', 'proficient with sing', 'proficient with sling', 'susceptibility to bleed damage', 'and proficiency with the selected weapon.', 'bravery class feature', 'proficiency with medium', ], 'skill': [ ('knowledge (arcane)', 14), ('knowledge (dungeoneering)', 14), ('knowledge (local)', 14), ('knowledge (nature)', 14), ('knowledge (planes)', 14), ('knowledge (religion)', 14), ('knowledge (engineering)', 2), ('knowledge (geography)', 2), ('knowledge (history)', 14), ('acrobatics', 14), ('climb', 4), ('swim', 4), ('stealth', 4), ('disable device', 1), ('spellcraft', 8), ('intimidate', 1), ('use magic device', 1), ('diplomacy', 1), ('perception', 1), ('survival', 1), ('linguistics', 4), ('lore (divine battles)', 9), ('lore (mythic founts)', 9), ('art (anatomy)', 1), ('perform (oratory)', 1), ], } 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 if 'and' in pr: for andpr in pr['and']: if not fillsreqs(andpr, charsheet): qual = False if 'or' in pr: orqual = False for orpr in pr['or']: if anyreqs(orpr, charsheet): orqual = True break if not orqual: qual = False return qual def anyreqs(pr, charsheet): for attr in ['str', 'dex', 'con', 'int', 'wis', 'cha', 'bab']: if attr in pr: if int(pr[attr][0]) <= charsheet[attr]: return True if 'level' in pr: for l in pr['level']: for ll in charsheet['level']: if l[0] == ll[0] and int(l[1]) <= ll[1]: return True if 'skill' in pr: for s in pr['skill']: for cs in charsheet['skill']: if s[0] == cs[0] and int(s[1]) <= cs[1]: return True if 'feat' in pr: for f in pr['feat']: if f != '' and f in charsheet['feat']: return True return False 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 addchain(feat, charsheet, depth=0): newchar = deepcopy(charsheet) newchar['feat'].append(feat['name'].lower()) newfeats = [] for f in qualfeats(allfeats, newchar): if 'feat' in f['prereqs']: if f['name'].lower() not in newchar['feat'] and feat['name'].lower() in f['prereqs']['feat']: if depth >= 1: newfeats.append(f) else: newfeats.append(addchain(f, newchar, depth + 1)) if len(newfeats) > 0: feat['chain'] = newfeats return feat def addchains(feats, charsheet): for f in feats: f = addchain(f, charsheet) 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'] if 'chain' in feat: featsnip += chainfeats(feat['chain']) 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: if 'chain' in f: chainers.append(f) else: singles.append(f) # sort both lists singles.sort(key=lambda x: x['name']) chainers.sort(key=lambda x: x['name']) # 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)