queryfeats.py 9.2 KB

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