Browse Source

Formatted actions more closely with the MM

master
Graham Northup 4 years ago
parent
commit
c6910046d9
Signed by untrusted user: grissess GPG Key ID: 5D000E6F539376FB
  1. 7
      Monsters/apocrypha.txt
  2. 10
      Monsters/matryoshka.txt
  3. 14
      Monsters/pirate_skirmisher.txt
  4. 10
      Monsters/reverse_mimic.txt
  5. 99
      app/monsters.py
  6. 13
      app/static/style.css
  7. 11
      app/templates/macros.html
  8. 15
      app/templates/monsters.html

7
Monsters/apocrypha.txt

@ -29,9 +29,10 @@ Descript {
Actions {
Mind Drain {
Hit 1d20+2
Reach 5
Damage 1d4+3
Kind Ranged Spell
Hit +2
Reach 5'
Damage 1d4+3 psychic
Special {
Once this attack lands, the apocrypha stays attached to the target, and at the start of each of the apocrypha's turns (until it gets detached), the target takes 1d4+3 damage, and 1 INT damage. Accrued INT damage can be healed by reading the apocrypha (likely once it is killed). Be careful not to destroy the corpse!
}

10
Monsters/matryoshka.txt

@ -32,17 +32,15 @@ Descript {
Actions {
Slam x2 {
Hit 1d20+8
Reach 5
Damage 2d8+5
Kind Melee Weapon
Hit +8
Reach 5'
Damage 2d8+5 bludgeoning
Special {
If below 50% HP, this attack does 2 more damage.
}
}
Hatch {
Hit 0
Reach 0
Damage 0
Special {
This action cannot be taken if you are small in size.
Split open vertically and erupt out another Matryoshka one size level smaller.

14
Monsters/pirate_skirmisher.txt

@ -27,14 +27,16 @@ Descript {
Actions {
Cutlass {
Hit 1d20+6
Reach 5
Damage 1d8+4
Kind Melee Weapon
Hit +6
Reach 5'
Damage 1d8+4 slashing
}
Pistol {
Hit 1d20+6
Range 30
Damage 1d6
Kind Ranged Weapon
Hit +6
Range 30'
Damage 1d6 piercing
Special {
Flintlock pistols are not very reliable. On a hit roll of 1, the pistol jams and is unusable.
}

10
Monsters/reverse_mimic.txt

@ -29,14 +29,12 @@ Descript {
Actions {
Bite {
Hit 1d20+5
Reach 5
Damage 1d8+3
Kind Melee Weapon
Hit +5
Reach 5'
Damage 1d8+3 piercing
}
Transform {
Hit 0
Reach 0
Damage 0
Special {
Select an inanimate object of size at most medium. Over the course of the next 10 minutes, the object transforms to appear like one creature you have seen before. You determine the pose and expression of the creature, however it cannot move. This lasts for 24 hours.
}

99
app/monsters.py

@ -1,3 +1,5 @@
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):
@ -9,39 +11,84 @@ def getProperty(strs,prop):
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.hit = "0"
self.reach = "0"
self.damage = "0"
self.special = ""
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[0][:-1].strip()
self.hit = ''.join(lines[1].split(" ")[1:]).strip()
self.reach = lines[2].split(" ")[1].strip()
self.damage = lines[3].split(" ")[1].strip()
if len(lines) > 4:
if "Special" in lines[4]:
i = 5
while not "}" in lines[i]:
self.special += lines[i].strip() + "\n"
i += 1
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
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):

13
app/static/style.css

@ -151,3 +151,16 @@ table.rolls td, table.rolls th {
.centered {
text-align: center;
}
.action {
padding-top: 1em;
text-align: left;
}
.action .name {
font-weight: bold;
}
.action .kind, .action .onhit, .action .name {
font-style: italic;
}

11
app/templates/macros.html

@ -19,3 +19,14 @@
{% endfor %}
</table>
{%- endmacro %}
{% macro monsterAction(ma) -%}
<div class="action">
<span class="name">{{ ma.name }}.</span>
{% if ma.has_normal_attack_parts() %}
{% if ma.kind %}<span class="kind">{{ ma.kind }} Attack.</span>{% endif %}
{{ ma.atk_line() }}. {% if ma.damage %}<span class="onhit">Hit:</span> {{ ma.guess_expected_damage() }}.{% endif %}
{% endif %}
{% if ma.special %}{{ ma.special }}{% endif %}
</div>
{%- endmacro %}

15
app/templates/monsters.html

@ -20,15 +20,14 @@
{% endfor %}
</table>
</th>
<td class="freeform monospace">{{monster.getDescription()}}
<hr>{{monster.getNotes()}}
<td class="monospace">{{monster.getDescription()}}
<hr/>
{{monster.getNotes()}}
<hr/>
{% for action in monster.exportActions() %}
{{ macros.monsterAction(action) }}
{% endfor %}
</td>
<td class="freeform monospace centered">{% for action in monster.exportActions() %}{{action.name}}<table class="props">
{% for (key,val) in action.export() %}
<tr><th>{{key}}</th><td>{{val}}</td></tr>
{% endfor %}
</table>{% if not loop.last %}<br>{% endif %}{% endfor %}</td>
</tr>
{% endfor %}

Loading…
Cancel
Save