queryfeats.py 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. #!/usr/bin/env python3
  2. import pickle
  3. import pprint
  4. import re
  5. from copy import deepcopy
  6. charsheet = {
  7. 'bab': 9,
  8. 'str': 20,
  9. 'dex': 14,
  10. 'con': 14,
  11. 'int': 16,
  12. 'wis': 10,
  13. 'cha': 10,
  14. 'level': [
  15. ('fighter', 10)
  16. ],
  17. 'feat': [
  18. 'toughness',
  19. 'armor proficiency, medium',
  20. 'power attack',
  21. 'combat stamina',
  22. 'improved unarmed strike',
  23. 'combat expertise',
  24. 'point blank shot',
  25. 'point-blank shot',
  26. 'deadly aim',
  27. 'improved bull rush',
  28. 'improved overrun',
  29. 'improved trip',
  30. 'weapon focus',
  31. 'weapon focus with chosen weapon',
  32. 'weapon focus (greataxe)',
  33. 'weapon focus (glaive)',
  34. 'weapon focus with chosen melee weapon',
  35. 'weapon focus with selected weapon',
  36. 'weapon focus with the chosen weapon',
  37. 'weapon focus (any two-handed reach weapon)',
  38. 'weapon specialization',
  39. 'weapon specialization with selected weapon',
  40. 'improved critical',
  41. 'armor focus',
  42. 'proficiency with selected armor',
  43. 'medium armor proficiency',
  44. 'light armor proficiency',
  45. 'proficiency with light armor',
  46. 'proficiency with medium armor',
  47. 'armor training class feature',
  48. 'armor trainingclass feature',
  49. 'martial focus',
  50. 'weapon training class feature',
  51. 'combat reflexes',
  52. 'you have no levels in a class that has the grit class feature',
  53. 'any combat feat',
  54. 'any good alignment',
  55. 'bravery +1 class feature',
  56. 'human',
  57. 'medium size',
  58. 'no levels in a class that has the favored enemy class feature',
  59. 'no levels in a class that has the panache class feature',
  60. 'nonlawful',
  61. 'proficiency with selected weapon',
  62. 'proficient with all martial weapons',
  63. 'proficiency with weapon',
  64. 'proficiency with chosen weapon',
  65. 'proficiency with armor spikes',
  66. 'proficient with armor spikes',
  67. 'proficient with scimitar',
  68. 'proficient with sing',
  69. 'susceptibility to bleed damage',
  70. 'and proficiency with the selected weapon.',
  71. 'bravery class feature',
  72. ],
  73. 'skill': [
  74. ('knowledge (arcane)', 9),
  75. ('knowledge (dungeoneering)', 9),
  76. ('knowledge (local)', 9),
  77. ('knowledge (nature)', 9),
  78. ('knowledge (planes)', 9),
  79. ('knowledge (religion)', 9),
  80. ('knowledge (engineering)', 1),
  81. ('knowledge (geography)', 2),
  82. ('knowledge (history)', 4),
  83. ('acrobatics', 9),
  84. ('climb', 3),
  85. ('atealth', 1),
  86. ('disable device', 1),
  87. ('spellcraft', 1),
  88. ('intimidate', 1),
  89. ('use magic device', 1),
  90. ('diplomacy', 1),
  91. ('perception', 1),
  92. ('linguistics', 4),
  93. ('lore (divine battles)', 4),
  94. ('art (anatomy)', 1),
  95. ('perform (oratory)', 2),
  96. ],
  97. }
  98. with open('feats.pickle', 'rb') as f:
  99. allfeats = pickle.load(f)
  100. def texify(textstr):
  101. return textstr
  102. def grabfeat(feats, featname):
  103. for f in feats:
  104. if f['name'].lower() == featname.lower():
  105. return f
  106. def fillsreqs(pr, charsheet):
  107. qual = True
  108. for attr in ['str', 'dex', 'con', 'int', 'wis', 'cha', 'bab']:
  109. if attr in pr:
  110. if int(pr[attr][0]) > charsheet[attr]:
  111. qual = False
  112. if 'level' in pr:
  113. for l in pr['level']:
  114. llqual = False
  115. for ll in charsheet['level']:
  116. if l[0] == ll[0] and int(l[1]) <= ll[1]:
  117. llqual = True
  118. break
  119. if llqual == False:
  120. qual = False
  121. break
  122. if 'skill' in pr:
  123. for s in pr['skill']:
  124. squal = False
  125. for cs in charsheet['skill']:
  126. if s[0] == cs[0] and int(s[1]) <= cs[1]:
  127. squal = True
  128. break
  129. if squal == False:
  130. qual = False
  131. break
  132. if 'feat' in pr:
  133. for f in pr['feat']:
  134. if f != '' and f not in charsheet['feat']:
  135. qual = False
  136. return qual
  137. def qualfeat(feat, charsheet):
  138. if feat['prereqs']:
  139. return fillsreqs(feat['prereqs'], charsheet)
  140. else:
  141. return True
  142. def qualfeats(feats, charsheet):
  143. return [f for f in feats if qualfeat(f, charsheet) and f['name'].lower() not in charsheet['feat']]
  144. def addchains(feats, charsheet):
  145. featnames = [f['name'] for f in feats]
  146. for f in feats:
  147. newreqs = deepcopy(charsheet)
  148. newreqs['feat'].append(f['name'].lower())
  149. newcandidates = qualfeats(allfeats, newreqs)
  150. newfeats = [ nf for nf in newcandidates if nf['name'] not in featnames ]
  151. if len(newfeats) > 0:
  152. f['chain'] = newfeats
  153. def prefeats(feats):
  154. for f in allfeats:
  155. prefeats = {}
  156. fs = f['prereqs']['feat']
  157. if isinstance(fs, list):
  158. for pf in fs:
  159. if pf in prefeats:
  160. prefeats[pf] += 1
  161. else:
  162. prefeats[pf] = 1
  163. else:
  164. if fs in prefeats:
  165. prefeats[fs] += 1
  166. else:
  167. prefeats[fs] = 1
  168. return prefeats
  169. def asfeat(feat):
  170. featsnip = '\subsubsection*{%s}\n\n' % feat['name']
  171. featsnip += '\\textbf{Benefit:} %s\n\n' % feat['benefit']
  172. if feat['trick']:
  173. featsnip += '\\textbf{Combat Trick:} %s\n\n' % feat['trick']
  174. if feat['special']:
  175. featsnip += '\\textbf{Special:} %s\n\n' % feat['special']
  176. if 'chain' in feat:
  177. featsnip += chainfeats(feat['chain'])
  178. return featsnip
  179. def aschainfeat(feat):
  180. featsnip = '\item \\textbf{%s} %s\n\n' % (feat['name'], feat['benefit'])
  181. if feat['trick']:
  182. featsnip += '\\textbf{Combat Trick:} %s\n\n' % feat['trick']
  183. if feat['special']:
  184. featsnip += '\\textbf{Special:} %s\n\n' % feat['special']
  185. return featsnip
  186. def chainfeats(feats):
  187. chainsnip = '\\begin{itemize}\n\n'
  188. for f in feats:
  189. chainsnip += aschainfeat(f)
  190. chainsnip += '\end{itemize}\n\n'
  191. return chainsnip
  192. def takenfeats(cs):
  193. taken = []
  194. for f in sorted(charsheet['feat']):
  195. ff = grabfeat(allfeats, f)
  196. if ff:
  197. taken.append(ff)
  198. return taken
  199. if __name__ == '__main__':
  200. # generate LaTeX for taken feats
  201. sectsnip = ''
  202. for f in takenfeats(charsheet):
  203. sectsnip += asfeat(f)
  204. with open('taken.snip', 'w') as f:
  205. print(sectsnip, file=f)
  206. # generate lists of qualifying feats and one-step chains
  207. cantake = qualfeats(allfeats, charsheet)
  208. addchains(cantake, charsheet)
  209. # split list into one-offs, and feats with chaining
  210. singles = []
  211. chainers = []
  212. for f in cantake:
  213. print(f['name'])
  214. if 'chain' in f:
  215. chainers.append(f)
  216. else:
  217. singles.append(f)
  218. # LaTeXify both lists
  219. singlesnip = ''
  220. for f in singles:
  221. singlesnip += asfeat(f)
  222. with open('singles.snip', 'w') as f:
  223. print(singlesnip, file=f)
  224. chainsnip = ''
  225. for f in chainers:
  226. chainsnip += asfeat(f)
  227. with open('chainers.snip', 'w') as f:
  228. print(chainsnip, file = f)