rollcake bot
This commit is contained in:
parent
02586f1d34
commit
86e4c583b4
170
zulip_bots/zulip_bots/bots/rollcake/rollcake.py
Normal file
170
zulip_bots/zulip_bots/bots/rollcake/rollcake.py
Normal file
|
@ -0,0 +1,170 @@
|
||||||
|
import random
|
||||||
|
import re
|
||||||
|
|
||||||
|
USAGE = '''@ me to roll RPG dice. Examples:
|
||||||
|
|
||||||
|
@**Rollcake** 2d6+2
|
||||||
|
|
||||||
|
@**Rollcake** pbta -1 adv
|
||||||
|
*(Powered by the Apocalypse, -1 penalty, roll with advantage: 3d6, add highest 2, subtract 1)*
|
||||||
|
|
||||||
|
@**Rollcake** fitd 2
|
||||||
|
*(Forged in the Dark, action rating 2: 2d6, take highest)*
|
||||||
|
|
||||||
|
@**Rollcake** sbr 3 danger
|
||||||
|
*(Sparked by the Resistance, dice pool 3, dangerous: 3d10, remove highest 2, take highest remaining)*
|
||||||
|
|
||||||
|
@**Rollcake** void 3
|
||||||
|
*(Voidheart Symphony city roll, stress gauge 3: 2d6, check if either/both are above 3)*
|
||||||
|
'''
|
||||||
|
|
||||||
|
PBTA_RE = re.compile(r'(pbta|apoc(alypse)?|void(heart)? castle)\s+(?P<mod>[-+]?[0-9])(\s+(?P<adv>adv|dis))?')
|
||||||
|
FITD_RE = re.compile(r'(fitd|forged)\s+(?P<rating>[0-9])\b')
|
||||||
|
SBR_RE = re.compile(r'(sbr|res(istance)?)\s+(?P<pool>[0-9])(\s+(?P<risk>risk|dang))?')
|
||||||
|
VOID_RE = re.compile(r'(void(heart)?( city)?)\s+(?P<stress>[0-6])(\s+(?P<adv>adv|dis))?')
|
||||||
|
DICE_RE = re.compile(r'\b(?P<count>[0-9]*)d(?P<sides>[0-9]+)\s*(?P<mod>[-+][0-9]+)?')
|
||||||
|
|
||||||
|
def roll(count, sides):
|
||||||
|
return [random.randint(1, sides) for i in range(count)]
|
||||||
|
|
||||||
|
def handle_roll(content):
|
||||||
|
content = content.lower()
|
||||||
|
|
||||||
|
dice = DICE_RE.search(content)
|
||||||
|
if dice:
|
||||||
|
count = int(dice.group('count') or '1')
|
||||||
|
sides = int(dice.group('sides'))
|
||||||
|
mod = int(dice.group('mod') or '0')
|
||||||
|
results = roll(count, sides)
|
||||||
|
total = sum(results) + mod
|
||||||
|
return 'Total: **{}**\n(max {}, min {})\nRolls: {}'.format(
|
||||||
|
total,
|
||||||
|
max(results),
|
||||||
|
min(results),
|
||||||
|
', '.join(str(n) for n in results),
|
||||||
|
)
|
||||||
|
|
||||||
|
pbta = PBTA_RE.search(content)
|
||||||
|
if pbta:
|
||||||
|
mod = int(pbta.group('mod') or '0')
|
||||||
|
adv = pbta.group('adv')
|
||||||
|
if adv:
|
||||||
|
results = roll(3, 6)
|
||||||
|
idx = results.index(min(results) if adv == 'adv' else max(results))
|
||||||
|
else:
|
||||||
|
results = roll(2, 6)
|
||||||
|
idx = -1
|
||||||
|
total = sum(n for (i, n) in enumerate(results) if i != idx) + mod
|
||||||
|
if total >= 10:
|
||||||
|
outcome = 'Strong hit'
|
||||||
|
elif total >= 7:
|
||||||
|
outcome = 'Weak hit'
|
||||||
|
else:
|
||||||
|
outcome = 'Miss'
|
||||||
|
return '**{}**\n**{}**\nRolls: {}'.format(
|
||||||
|
outcome,
|
||||||
|
total,
|
||||||
|
', '.join(
|
||||||
|
('~~{}~~' if i == idx else '{}').format(n)
|
||||||
|
for (i, n) in enumerate(results)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
void = VOID_RE.search(content)
|
||||||
|
if void:
|
||||||
|
stress = int(void.group('stress'))
|
||||||
|
adv = void.group('adv')
|
||||||
|
if adv:
|
||||||
|
results = roll(3, 6)
|
||||||
|
idx = results.index(min(results) if adv == 'adv' else max(results))
|
||||||
|
else:
|
||||||
|
results = roll(2, 6)
|
||||||
|
idx = -1
|
||||||
|
count = sum(1 for (i, n) in enumerate(results) if i != idx and n > stress)
|
||||||
|
return '**{}**\nRolls: {}'.format(
|
||||||
|
['Miss', 'Weak hit', 'Strong hit'][count],
|
||||||
|
', '.join(
|
||||||
|
('~~{}~~' if i == idx else '{}').format(n)
|
||||||
|
for (i, n) in enumerate(results)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
fitd = FITD_RE.search(content)
|
||||||
|
if fitd:
|
||||||
|
rating = int(fitd.group('rating'))
|
||||||
|
if rating == 0:
|
||||||
|
results = roll(2, 6)
|
||||||
|
idx = results.index(max(results))
|
||||||
|
value = min(results)
|
||||||
|
crit = False
|
||||||
|
else:
|
||||||
|
results = roll(rating, 6)
|
||||||
|
idx = -1
|
||||||
|
value = max(results)
|
||||||
|
crit = results.count(6) >= 2
|
||||||
|
if crit:
|
||||||
|
outcome = 'Crit'
|
||||||
|
elif value == 6:
|
||||||
|
outcome = 'Full success'
|
||||||
|
elif value >= 4:
|
||||||
|
outcome = 'Mixed success'
|
||||||
|
else:
|
||||||
|
outcome = 'Failure'
|
||||||
|
return '**{}**\n**{}**\nRolls: {}'.format(
|
||||||
|
outcome,
|
||||||
|
"6, 6" if crit else value,
|
||||||
|
', '.join(
|
||||||
|
('~~{}~~' if i == idx else '{}').format(n)
|
||||||
|
for (i, n) in enumerate(results)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
sbr = SBR_RE.search(content)
|
||||||
|
if sbr:
|
||||||
|
pool = int(sbr.group('pool'))
|
||||||
|
risk = sbr.group('risk')
|
||||||
|
remove = 2 if risk == 'dang' else (1 if risk == 'risk' else 0)
|
||||||
|
if remove >= pool:
|
||||||
|
result = roll(1, 10)[0]
|
||||||
|
return '**{}**\n{}'.format(
|
||||||
|
'Success at a cost' if result == 10 else 'Failure',
|
||||||
|
result,
|
||||||
|
)
|
||||||
|
results = roll(pool, 10)
|
||||||
|
indices = list(range(pool))
|
||||||
|
indices.sort(key=lambda i: results[i], reverse=True)
|
||||||
|
indices = indices[:remove]
|
||||||
|
best = max(n for (i, n) in enumerate(results) if i not in indices)
|
||||||
|
if best == 10:
|
||||||
|
outcome = 'Critical success'
|
||||||
|
elif best >= 8:
|
||||||
|
outcome = 'Success'
|
||||||
|
elif best >= 6:
|
||||||
|
outcome = 'Success at a cost'
|
||||||
|
elif best >= 2:
|
||||||
|
outcome = 'Failure'
|
||||||
|
else:
|
||||||
|
outcome = 'Critical failure'
|
||||||
|
return '**{}**\n**{}**\nRolls: {}'.format(
|
||||||
|
outcome,
|
||||||
|
best,
|
||||||
|
', '.join(
|
||||||
|
('~~{}~~' if i in indices else '{}').format(n)
|
||||||
|
for (i, n) in enumerate(results)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return USAGE
|
||||||
|
|
||||||
|
class RollcakeHandler(object):
|
||||||
|
'''
|
||||||
|
A Zulip bot to roll RPG dice.
|
||||||
|
'''
|
||||||
|
|
||||||
|
def usage(self):
|
||||||
|
return USAGE
|
||||||
|
|
||||||
|
def handle_message(self, message, bot_handler):
|
||||||
|
bot_handler.send_reply(message, handle_roll(message['content']))
|
||||||
|
|
||||||
|
handler_class = RollcakeHandler
|
Loading…
Reference in a new issue