gm-assistant/gm_assistant/fate_chart.py

193 lines
3.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# SPDX-FileCopyrightText: 2025 2025
# SPDX-FileContributor: Gergely Polonkai
#
# SPDX-License-Identifier: GPL-3.0-or-later
"""Calculate the odds using Mythic RPGs Fate Chart"""
from enum import Enum
from .dice import Die
class FateOutcome(Enum):
"""Possible outcomes"""
EXC_YES = "eyes"
YES = "yes"
NO = "no"
EXC_NO = "eno"
class FateOdds(Enum):
"""Odds of a “yes” answer"""
IMPOSSIBLE = 0
NO_WAY = 1
VERY_UNLIKELY = 2
UNLIKELY = 3
FIFTY_FIFTY = 4
SOMEWHAT_LIKELY = 5
LIKELY = 6
VERY_LIKELY = 7
NEAR_SURE_THING = 8
SURE_THING = 9
HAS_TO_BE = 10
HAS_TO_BE2 = 11
TABLE: dict[FateOdds, list[tuple[int, int, int]]] = {
FateOdds.IMPOSSIBLE: [
(0, -20, 77),
(0, 0, 81),
(0, 0, 81),
(1, 5, 82),
(1, 5, 82),
(2, 10, 83),
(3, 15, 84),
(5, 25, 86),
(10, 50, 91),
],
FateOdds.NO_WAY: [
(0, 0, 81),
(1, 5, 82),
(1, 5, 82),
(2, 10, 83),
(3, 15, 84),
(5, 25, 86),
(7, 35, 88),
(10, 50, 91),
(15, 75, 96),
],
FateOdds.VERY_UNLIKELY: [
(1, 5, 82),
(1, 5, 82),
(2, 10, 83),
(3, 15, 84),
(5, 25, 86),
(9, 45, 90),
(10, 50, 91),
(13, 65, 94),
(16, 85, 97),
],
FateOdds.UNLIKELY: [
(1, 5, 82),
(2, 10, 83),
(3, 15, 84),
(4, 20, 85),
(7, 35, 88),
(10, 50, 91),
(11, 55, 92),
(15, 75, 96),
(18, 90, 99),
],
FateOdds.FIFTY_FIFTY: [
(2, 10, 83),
(3, 15, 84),
(5, 25, 86),
(7, 35, 88),
(10, 50, 91),
(13, 65, 94),
(15, 75, 96),
(16, 85, 97),
(19, 95, 100),
],
FateOdds.SOMEWHAT_LIKELY: [
(4, 20, 85),
(5, 25, 86),
(9, 45, 90),
(10, 50, 91),
(13, 65, 94),
(16, 80, 97),
(16, 85, 97),
(18, 90, 99),
(19, 95, 100),
],
FateOdds.LIKELY: [
(5, 25, 86),
(7, 35, 88),
(10, 50, 91),
(11, 55, 92),
(15, 75, 96),
(16, 85, 97),
(18, 90, 99),
(19, 95, 100),
(20, 100, 0),
],
FateOdds.VERY_LIKELY: [
(9, 45, 90),
(10, 50, 91),
(13, 65, 94),
(15, 75, 96),
(16, 85, 97),
(18, 90, 99),
(19, 95, 100),
(19, 95, 100),
(21, 105, 0),
],
FateOdds.NEAR_SURE_THING: [
(10, 50, 91),
(11, 55, 92),
(15, 75, 96),
(16, 80, 97),
(18, 90, 99),
(19, 95, 100),
(19, 95, 100),
(20, 100, 0),
(23, 115, 0),
],
FateOdds.SURE_THING: [
(11, 55, 92),
(13, 65, 94),
(16, 80, 97),
(16, 85, 97),
(18, 90, 99),
(19, 95, 100),
(19, 95, 100),
(22, 110, 0),
(25, 125, 0),
],
FateOdds.HAS_TO_BE: [
(16, 80, 97),
(16, 85, 97),
(18, 90, 99),
(19, 95, 100),
(19, 95, 100),
(20, 100, 0),
(20, 100, 0),
(26, 130, 0),
(26, 145, 0),
],
FateOdds.HAS_TO_BE2: [
(18, 90, 99),
(19, 95, 100),
(19, 95, 100),
(20, 100, 0),
(22, 110, 0),
(24, 120, 0),
(24, 120, 0),
(30, 150, 0),
(29, 165, 0),
],
}
def decide(odds: FateOdds, chaos_level: int = 5) -> FateOutcome:
"""Decide the outcome of a situation using the Fate Chart"""
# Chaos levels range from 1 and 9 (both included), but list indices start at 0
chaos_level -= 1
eyes, yes, eno = TABLE[odds][chaos_level]
value = Die("d100").roll()
if value <= eyes:
return FateOutcome.EXC_YES
if value <= yes:
return FateOutcome.YES
if value >= eno:
return FateOutcome.EXC_NO
return FateOutcome.NO