123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347 |
- #!/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)
|