aboutsummaryrefslogtreecommitdiff
path: root/parser/utils.py
diff options
context:
space:
mode:
authorYour Name <you@example.com>2021-04-13 15:14:34 -0400
committerYour Name <you@example.com>2021-04-13 15:14:34 -0400
commit2ab51e507d620c4479e07ca0ec47d22c8c66bc90 (patch)
tree90906ecb043c01034280c767b83a88eb6df6956f /parser/utils.py
downloaddmtool-2ab51e507d620c4479e07ca0ec47d22c8c66bc90.tar.gz
dmtool-2ab51e507d620c4479e07ca0ec47d22c8c66bc90.tar.bz2
dmtool-2ab51e507d620c4479e07ca0ec47d22c8c66bc90.zip
Initial commit
Diffstat (limited to 'parser/utils.py')
-rwxr-xr-xparser/utils.py152
1 files changed, 152 insertions, 0 deletions
diff --git a/parser/utils.py b/parser/utils.py
new file mode 100755
index 0000000..1798621
--- /dev/null
+++ b/parser/utils.py
@@ -0,0 +1,152 @@
+#!/usr/bin/env python3
+
+import json
+import re
+from fractions import Fraction
+
+skillsByAbility = {'str': ['Athletics'], 'dex': ['Acrobatics', 'Sleight of Hand', 'Stealth'], 'con': [], 'int': ['Arcana', 'History', 'Investigation', 'Nature', 'Religion'], 'wis': ['Animal Handling', 'Insight', 'Medicine', 'Perception', 'Survival'], 'cha': ['Deception', 'Intimidation', 'Performance', 'Persuasion']}
+
+armorByType = {'light': [('padded', 11), ('leather', 11), ('studded leather', 12)], 'medium': [('hide', 12), ('chain shirt', 13), ('scale mail', 14), ('breastplate', 14), ('half plate', 15)], 'heavy': [('ring mail', 14), ('chain mail', 16), ('splint', 17), ('plate', 18)], 'misc': [('shield', 2), ('ring of protection', 1)]}
+
+def procSkill(skillStr):
+ skill = skillStr.strip()
+ skillName = re.search('[^\+]*?(?= \+)', skill).group(0)
+ skillBonus = int(re.search('\+(\d+)', skill).group(1))
+ ability = ''
+ for a in skillsByAbility:
+ if skillName in skillsByAbility[a]:
+ ability = a
+ if not ability:
+ print('Could not find ability for skill {}'.format(skillName))
+ return skillName, skillBonus, ability
+
+def guessProficiency(desc):
+ if desc['cr'] <= 1:
+ return 2
+ def getBonus(ability):
+ return (desc['stats'][ability] - 10) // 2
+ # Guess proficiency based on saves, skills, and attacks
+ if desc['saves']:
+ for save in desc['saves'].split(','):
+ save = save.strip()
+ ability = save.split(' ')[0].lower()
+ saveBonus = int(save.split('+')[1])
+ return saveBonus - getBonus(ability) # This is the answer.
+ skillGuesses = []
+ if desc['skills']:
+ for skill in desc['skills'].split(','):
+ skillName, skillBonus, ability = procSkill(skill)
+ skillGuesses.append(skillBonus - getBonus(ability))
+ attackGuesses = []
+ for action in desc['actions'].values():
+ if re.match('.*Attack:', action):
+ toHit = int(re.search('\+(\d+) to hit', action).group(1))
+ dmgBonusMatch = re.search('\d+d\d+ (\+|−) (\d+)\)', action)
+ if dmgBonusMatch:
+ dmgBonus = int(dmgBonusMatch.group(2))
+ if dmgBonusMatch.group(1) == '−':
+ #print('We match here for the {}!'.format(desc['name']))
+ dmgBonus *= -1
+ else:
+ dmgBonus = 0
+ if toHit - dmgBonus > 1:
+ attackGuesses.append(toHit - dmgBonus)
+ if not skillGuesses and not attackGuesses:
+ print('We got here for the {}!'.format(desc['name']))
+ return 2
+ else:
+ profGuesses = skillGuesses + attackGuesses
+ if min(profGuesses) != 0 and any(guess % min(profGuesses) != 0 for guess in profGuesses):
+ print('We had conflicting guesses for {}: {}'.format(desc['name'], profGuesses))
+ best = (0, 0)
+ for guess in profGuesses:
+ numHappy = sum(1 for other in profGuesses if other % guess == 0)
+ if numHappy > best[1]:
+ best = (guess, numHappy)
+ return best[0]
+
+def cost2copper(cost):
+ amnt = int(cost.split(' ')[0].replace(',', ''))
+ den = cost.split(' ')[1]
+ if den == 'pp':
+ return amnt * 1000
+ elif den == 'gp':
+ return amnt * 100
+ elif den == 'ep':
+ return amnt * 50
+ elif den == 'sp':
+ return amnt * 10
+ elif den == 'cp':
+ return amnt
+
+def getArmor():
+ with open('../../5thSRD/docs/adventuring/equipment/armor.md') as f:
+ data = f.read()
+ tables = re.search('(?sm)(?<=## Armor Table).*?(?=##)', data).group(0)
+ armors = []
+ header = ''
+ for armor in re.findall('\| (.*) \| (.*) \| (.*) \| (.*) \| (.*) \| (.*) \|', tables):
+ armor = [part.strip().lower() for part in armor]
+ if armor[1] == 'cost':
+ header = armor[0]
+ else:
+ armors.append({'name': armor[0], 'cost': cost2copper(armor[1]), 'ac': int(armor[2].split(' ')[0]), 'strength': int(armor[3].replace('-', '0').split(' ')[-1]), 'disadvantage': armor[4] == 'Disadvantage', 'weight': float(armor[5].split(' ')[0]), 'type': header.split(' ')[0]})
+ return armors
+
+weapons = []
+def getWeapons():
+ global weapons
+ if weapons:
+ return weapons
+ with open('../../5thSRD/docs/adventuring/equipment/weapons.md') as f:
+ data = f.read()
+ special = {}
+ for s in ['Lance', 'Net']:
+ special[s.lower()] = re.search('(?<=\*\*{}.\*\*).*'.format(s), data).group(0).strip()
+ tables = re.search('(?sm)## Weapons Table.*', data).group(0)
+ weapons = []
+ header = ''
+ for weapon in re.findall('\| (.*) \| (.*) \| (.*) \| (.*) \| (.*) \|', tables):
+ weapon = [part.strip().lower() for part in weapon]
+ if weapon[1] == 'cost':
+ header = weapon[0]
+ else:
+ name = weapon[0]
+ if ',' in name:
+ parts = name.split(', ')
+ name = parts[1] + ' ' + parts[0]
+ if weapon[2] == '-':
+ weapon[2] = '0d0 -'
+ damage = {'dmg_type': weapon[2].split(' ')[1]}
+ if 'd' in weapon[2].split(' ')[0]:
+ damage['dmg_die_count'] = int(weapon[2].split('d')[0])
+ damage['dmg_die_sides'] = int(weapon[2].split(' ')[0].split('d')[1])
+ else:
+ damage['dmg_die_count'] = 1
+ damage['dmg_die_sides'] = 1
+ rang = [0, 0]
+ reach = 5
+ proporties = []
+ if weapon[4] != '-':
+ proporties = weapon[4].split(', ')
+ for i, p in enumerate(list(proporties)):
+ if 'versatile' in p:
+ proporties[i] = 'versatile'
+ elif 'range' in p:
+ proporties[i] = p.split(' (')[0]
+ rang = [int(r) for r in p.split(' ')[-1][:-1].split('/')]
+ if 'ammunition' in p:
+ reach = 0
+ elif 'reach' in p:
+ reach += 5
+ if name in special:
+ proporties.append(special[name])
+ weapons.append({'name': name, 'cost': cost2copper(weapon[1]), 'damage': damage, 'weight': float(Fraction(weapon[3].split(' ')[0].replace('-', '0'))), 'range': rang, 'reach': reach, 'properties': proporties, 'type': header, 'text': 'Provided from PHB'})
+ return weapons
+
+def formatWeapon(name, rangeShort, rangeLong, reach, dmgType, dmgCount, dmgSides, text):
+ baseWeapon = {'cost': 0, 'weight': 0.0, 'properties': [], 'type': 'unknown'}
+ for weapon in weapons:
+ if weapon['name'] == name:
+ baseWeapon = weapon
+ return {'name': name, 'cost': baseWeapon['cost'], 'damage': {'dmg_type': dmgType, 'dmg_die_count': dmgCount, 'dmg_die_sides': dmgSides}, 'weight': baseWeapon['weight'], 'range': [rangeShort, rangeLong], 'reach': reach, 'properties': baseWeapon['properties'], 'type': baseWeapon['type'], 'text': text}