Contact me to be added to this repository. Push the creations you make for D&D, and they will be displayed in a nice website. See the website for how to contribute.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

188 lines
5.8 KiB

import re, math
MONSTER_PROPS = ["CR","Size","Type","Align","AC","HP","Speed","Str","Dex","Con","Int","Wis","Cha","Senses","Resist","Immune","Weak"] #properties must appear in this order... for now
def getProperty(strs,prop):
lineData = strs[0].split(":")
if lineData[0] == prop:
return lineData[1]
return None
class MonsterAction(object):
ATTR_MAP = {
'Kind': 'kind',
'Hit': 'hit',
'Reach': 'reach',
'Range': 'range',
'Damage': 'damage',
'Special': 'special',
}
DICE_RE = re.compile(r'\s*((\d+)d(\d+)\s*(\+\s*(\d+))?)(.*)')
def __init__(self):
self.name = "INVALID"
self.kind = None
self.hit = None
self.reach = None
self.range = None
self.damage = None
self.special = None
def parse(self,text):
#assumes first line is first opening brace of action
#and last line is last closing brace of action
lines = [line.strip() for line in text.split("\n") if line]
self.name = lines.pop(0).partition('{')[0].strip()
while lines:
buffer = []
ln = lines.pop(0).strip()
if ln == '}':
if lines:
print(len(lines), 'were ignored in action', self.name)
break
key, _, value = ln.partition(' ')
if value == '{':
while lines:
ln = lines.pop(0)
if ln == '}':
break
buffer.append(ln.strip())
else:
buffer = [value]
attr = self.ATTR_MAP.get(key)
if attr is None:
print('WARN: Unknown attr', key, 'in action', self.name)
else:
setattr(self, attr, ' '.join(buffer))
def atk_line(self):
parts = []
if self.hit:
parts.append(str(self.hit) + ' to hit')
if self.reach:
parts.append('reach ' + str(self.reach))
if self.range:
parts.append('range ' + str(self.range))
return ', '.join(parts)
def has_normal_attack_parts(self):
return any((self.kind, self.reach, self.range, self.hit, self.damage))
def guess_expected_damage(self):
match = self.DICE_RE.match(self.damage)
if match:
ev = int(match.group(2)) * (1.0 + int(match.group(3))) / 2.0 + int(match.group(5) if match.group(5) else 0)
return '%d (%s) %s' % (int(math.ceil(ev)), match.group(1).strip(), match.group(6))
return self.damage
# def export(self):
# export_list = []
# if not self.hit == "0":
# export_list.append(("Hit",self.hit))
# if not self.reach == "0":
# export_list.append(("Reach",self.reach))
# if not self.damage == "0":
# export_list.append(("Damage",self.damage))
# if not self.special == "":
# export_list.append(("Special",self.special))
# return export_list
def __str__(self):
return str(self.__dict__)
class Monster(object):
def __init__(self,fpath):
with open(fpath,'r') as f:
text = list(filter(bool,f.read().split("\n")))
self.name = text.pop(0)
text.pop(0) #clear ------
for p in MONSTER_PROPS:
prop = getProperty(text,p)
if prop:
text.pop(0)
setattr(self,p,prop)
#now for brace parsing
self.descript = ""
if "Descript" in text[0]:
text.pop(0)
while not "}" in text[0]:
self.descript += text[0] + "\n"
text.pop(0)
text.pop(0) #remove last }
if "Actions" in text[0]:
self.actions = []
#print("Debug: parsing actions")
text.pop(0)
parenCount = 1
actionBuffer = ""
while parenCount >= 1:
actionBuffer += text[0]+"\n"
if "{" in text[0]:
parenCount += 1
#print("Upping paren count")
if "}" in text[0]:
parenCount -= 1
#print("Downing paren count")
if parenCount == 1:
#print("Making action")
a = MonsterAction()
a.parse(actionBuffer)
self.actions.append(a)
#print("Made action out of")
#print(''.join(actionBuffer))
actionBuffer = ""
#print("Reading line")
text.pop(0)
#we do not need to remove last brace here, as it is consumed by the loop this time
if "Notes" in text[0]:
text.pop(0)
self.notes = ""
while not "}" in text[0]:
self.notes += text[0] + "\n"
text.pop(0)
text.pop(0) #remove last }
if len(text) > 0:
print("Unparsed: ")
print(text)
def exportProps(self):
prop_list = []
for prop in MONSTER_PROPS:
if hasattr(self,prop):
prop_list.append((prop,getattr(self,prop)))
return prop_list
def getDescription(self):
return self.descript.strip()
def getNotes(self):
return self.notes.strip()
def exportActions(self):
return self.actions
def __str__(self):
return str(self.__dict__).replace("\',","\'\n")
#driver code
#m = Monster("../Monsters/reverse_mimic.txt")
#print(m)
#m = Monster("../Monsters/apocrypha.txt")
#print(m)
#m = Monster("../Monsters/matryoshka.txt")
#print(m)
#m = Monster("../Monsters/pirate_skirmisher.txt")
#print(m)