dice: cypher GM intrusions should not be auto-fails
this also has some code reorg around IRC styles Signed-off-by: Brian S. Stephan <bss@incorporeal.org>
This commit is contained in:
+35
-43
@@ -10,7 +10,7 @@ rand = random.SystemRandom()
|
||||
|
||||
|
||||
def cypher_roll(difficulty: Optional[int] = None, mod: int = 0, is_attack: bool = False) \
|
||||
-> Tuple[int, Optional[int], Optional[bool], Optional[str], Optional[str], int, Optional[int]]:
|
||||
-> Tuple[int, int, Optional[bool], Optional[str], Optional[str], int, Optional[int]]:
|
||||
"""Make a Cypher System roll.
|
||||
|
||||
Args:
|
||||
@@ -34,10 +34,8 @@ def cypher_roll(difficulty: Optional[int] = None, mod: int = 0, is_attack: bool
|
||||
else:
|
||||
net_difficulty = None
|
||||
|
||||
if roll == 1:
|
||||
return (roll, None, False if difficulty else None, None, 'a GM intrusion', result_lvl, net_difficulty)
|
||||
|
||||
effect = None
|
||||
negative_effect = None
|
||||
if roll == 17 and is_attack:
|
||||
effect = '+1 damage'
|
||||
elif roll == 18 and is_attack:
|
||||
@@ -46,19 +44,34 @@ def cypher_roll(difficulty: Optional[int] = None, mod: int = 0, is_attack: bool
|
||||
effect = 'a minor effect'
|
||||
elif roll == 20:
|
||||
effect = 'a MAJOR EFFECT'
|
||||
elif roll == 1:
|
||||
negative_effect = 'a GM intrusion'
|
||||
|
||||
# if we know the difficulty, the mod would adjust the difficulty, but for the case where we don't,
|
||||
# and maybe just in general, it's easier to modify the difficulty that the roll beats, so we flip the logic
|
||||
# if incoming eases are a negative number, they should add to the difficulty the roll beats
|
||||
beats = result_lvl - mod
|
||||
beats = 0 if beats < 0 else beats
|
||||
return (roll, beats, difficulty <= beats if difficulty else None, effect, None, result_lvl, net_difficulty)
|
||||
return (roll, beats, difficulty <= beats if difficulty else None, effect, negative_effect,
|
||||
result_lvl, net_difficulty)
|
||||
|
||||
|
||||
def cypher_result(difficulty: Optional[int] = None, mod: int = 0, is_attack: bool = False, comment: str = '',
|
||||
stat: str = 'Unknown', ircify: bool = False) \
|
||||
-> Tuple[int, Optional[int], Optional[bool], Optional[str], Optional[str], int, Optional[int], str]:
|
||||
-> Tuple[int, int, Optional[bool], Optional[str], Optional[str], int, Optional[int], str]:
|
||||
"""Turn the results of a Cypher System roll into a display string."""
|
||||
# prep some IRC stuff
|
||||
if ircify:
|
||||
good_style_prefix = '9'
|
||||
bad_style_prefix = '4'
|
||||
comment_style_prefix = '14'
|
||||
style_suffix = ''
|
||||
else:
|
||||
good_style_prefix = ''
|
||||
bad_style_prefix = ''
|
||||
comment_style_prefix = ''
|
||||
style_suffix = ''
|
||||
|
||||
# tweak the comment to show task/attack in cases of empty string
|
||||
if comment:
|
||||
display_comment = f"{comment} {stat} roll"
|
||||
@@ -69,33 +82,22 @@ def cypher_result(difficulty: Optional[int] = None, mod: int = 0, is_attack: boo
|
||||
result, beats, success, effect, neg_effect, result_lvl, diff_level = cypher_roll(
|
||||
difficulty=difficulty, mod=mod, is_attack=is_attack,
|
||||
)
|
||||
|
||||
if success is not None:
|
||||
# we know the difficulty so we should always know the success/failure
|
||||
# TODO: less nested ifs
|
||||
if success:
|
||||
if ircify:
|
||||
style_prefix = '9'
|
||||
style_suffix = ''
|
||||
else:
|
||||
style_prefix = ''
|
||||
style_suffix = ''
|
||||
|
||||
if effect:
|
||||
result_str = f"{style_prefix}succeeded, with {effect}!{style_suffix}"
|
||||
result_str = f"{good_style_prefix}succeeded, with {effect}!{style_suffix}"
|
||||
elif neg_effect:
|
||||
result_str = (f"{good_style_prefix}succeeded{style_suffix}, "
|
||||
f"{bad_style_prefix}with {neg_effect}!{style_suffix}")
|
||||
else:
|
||||
result_str = f"{style_prefix}succeeded!{style_suffix}"
|
||||
result_str = f"{good_style_prefix}succeeded!{style_suffix}"
|
||||
else:
|
||||
if ircify:
|
||||
style_prefix = '4'
|
||||
style_suffix = ''
|
||||
else:
|
||||
style_prefix = ''
|
||||
style_suffix = ''
|
||||
|
||||
if neg_effect:
|
||||
result_str = f"{style_prefix}failed, with {neg_effect}!{style_suffix}"
|
||||
result_str = f"{bad_style_prefix}failed, with {neg_effect}!{style_suffix}"
|
||||
else:
|
||||
result_str = f"{style_prefix}failed.{style_suffix}"
|
||||
result_str = f"{bad_style_prefix}failed.{style_suffix}"
|
||||
|
||||
# show the adjusted difficulty
|
||||
if mod > 0:
|
||||
@@ -105,23 +107,16 @@ def cypher_result(difficulty: Optional[int] = None, mod: int = 0, is_attack: boo
|
||||
else:
|
||||
modded_diff = str(difficulty)
|
||||
|
||||
if ircify:
|
||||
detail_str = f"14(result {result_lvl} (d20={result}) vs. difficulty {diff_level} ({modded_diff}))"
|
||||
else:
|
||||
detail_str = f"(result {result_lvl} (d20={result}) vs. difficulty {diff_level} ({modded_diff}))"
|
||||
detail_str = (f"{comment_style_prefix}(result {result_lvl} (d20={result}) vs. difficulty {diff_level} "
|
||||
f"({modded_diff})){style_suffix}")
|
||||
else:
|
||||
# we don't know the difficulty, so don't know success or not
|
||||
if beats is not None:
|
||||
if effect:
|
||||
result_str = f"beats a difficulty {beats} task, with {effect}!"
|
||||
else:
|
||||
result_str = f"beats a difficulty {beats} task."
|
||||
if effect:
|
||||
result_str = f"beats a difficulty {beats} task, {good_style_prefix}with {effect}!{style_suffix}"
|
||||
elif neg_effect:
|
||||
result_str = f"beats a difficulty {beats} task, {bad_style_prefix}with {neg_effect}!{style_suffix}"
|
||||
else:
|
||||
# this can only happen on an intrusion
|
||||
if ircify:
|
||||
result_str = f"4beats nothing, with {neg_effect}!"
|
||||
else:
|
||||
result_str = f"beats nothing, with {neg_effect}!"
|
||||
result_str = f"beats a difficulty {beats} task."
|
||||
|
||||
# show the adjusted difficulty
|
||||
if mod > 0:
|
||||
@@ -131,10 +126,7 @@ def cypher_result(difficulty: Optional[int] = None, mod: int = 0, is_attack: boo
|
||||
else:
|
||||
modded_diff = ""
|
||||
|
||||
if ircify:
|
||||
detail_str = f"14(result {result_lvl}{modded_diff} (d20={result}))"
|
||||
else:
|
||||
detail_str = f"(result {result_lvl}{modded_diff} (d20={result}))"
|
||||
detail_str = f"{comment_style_prefix}(result {result_lvl}{modded_diff} (d20={result})){style_suffix}"
|
||||
|
||||
return (result, beats, success, effect, neg_effect, result_lvl, diff_level,
|
||||
f"{display_comment} {result_str} {detail_str}")
|
||||
|
||||
+17
-3
@@ -29,7 +29,7 @@ class CypherRollTestCase(TestCase):
|
||||
"""Test rolling a 1."""
|
||||
with mock.patch('random.SystemRandom.randint', return_value=1):
|
||||
result = dice.lib.cypher_roll(difficulty=1)
|
||||
self.assertEqual(result, (1, None, False, None, 'a GM intrusion', 0, 1))
|
||||
self.assertEqual(result, (1, 0, False, None, 'a GM intrusion', 0, 1))
|
||||
|
||||
def test_attack_17(self):
|
||||
"""Test an attack with bonus damage."""
|
||||
@@ -89,7 +89,7 @@ class CypherRollTestCase(TestCase):
|
||||
|
||||
with mock.patch('random.SystemRandom.randint', return_value=1):
|
||||
result = dice.lib.cypher_roll()
|
||||
self.assertEqual(result, (1, None, None, None, 'a GM intrusion', 0, None))
|
||||
self.assertEqual(result, (1, 0, None, None, 'a GM intrusion', 0, None))
|
||||
|
||||
def test_unknown_difficulty(self):
|
||||
"""Test general "don't know the difficulty" kind of checks."""
|
||||
@@ -102,6 +102,12 @@ class CypherRollTestCase(TestCase):
|
||||
result = dice.lib.cypher_roll(mod=2)
|
||||
self.assertEqual(result, (10, 1, None, None, None, 3, None))
|
||||
|
||||
def test_success_despite_intrusion(self):
|
||||
"""Test that successes can occur even if a 1 is rolled."""
|
||||
with mock.patch('random.SystemRandom.randint', return_value=1):
|
||||
result = dice.lib.cypher_roll(difficulty=2, mod=-2)
|
||||
self.assertEqual(result, (1, 2, True, None, "a GM intrusion", 0, 0))
|
||||
|
||||
|
||||
class DiceLibTestCase(TestCase):
|
||||
"""Test that a variety of dice rolls work as expected."""
|
||||
@@ -155,7 +161,7 @@ class CypherRollStringTestCase(TestCase):
|
||||
with mock.patch('random.SystemRandom.randint', return_value=1):
|
||||
_, _, _, _, _, _, _, result = dice.lib.cypher_result(mod=0, stat="Might",
|
||||
comment="Test")
|
||||
self.assertEqual(result, "Test Might roll beats nothing, with a GM intrusion! (result 0 (d20=1))")
|
||||
self.assertEqual(result, "Test Might roll beats a difficulty 0 task, with a GM intrusion! (result 0 (d20=1))")
|
||||
|
||||
def test_unknown_difficulty_effect(self):
|
||||
"""Test the output when the difficulty is unknown."""
|
||||
@@ -163,3 +169,11 @@ class CypherRollStringTestCase(TestCase):
|
||||
_, _, _, _, _, _, _, result = dice.lib.cypher_result(mod=0, stat="Might",
|
||||
comment="Test")
|
||||
self.assertEqual(result, "Test Might roll beats a difficulty 6 task, with a MAJOR EFFECT! (result 6 (d20=20))")
|
||||
|
||||
def test_success_despite_intrusion(self):
|
||||
"""Test that successes can occur even if a 1 is rolled."""
|
||||
with mock.patch('random.SystemRandom.randint', return_value=1):
|
||||
_, _, _, _, _, _, _, result = dice.lib.cypher_result(difficulty=2, mod=-2, stat="Might",
|
||||
comment="Test")
|
||||
self.assertEqual(result,
|
||||
"Test Might roll succeeded, with a GM intrusion! (result 0 (d20=1) vs. difficulty 0 (2-2))")
|
||||
|
||||
@@ -83,4 +83,5 @@ class DiceRollerTestCase(TestCase):
|
||||
"""Roll a Cypher System roll without difficulty, just mods."""
|
||||
with mock.patch('random.SystemRandom.randint', return_value=1):
|
||||
result = self.roller.do_roll('-1 might attack')
|
||||
self.assertEqual(result, 'attack might roll beats nothing, with a GM intrusion! (result 0+1 (d20=1))')
|
||||
self.assertEqual(result, 'attack might roll beats a difficulty 1 task, '
|
||||
'with a GM intrusion! (result 0+1 (d20=1))')
|
||||
|
||||
Reference in New Issue
Block a user