193 lines
3.9 KiB
Python
193 lines
3.9 KiB
Python
# SPDX-FileCopyrightText: 2025 2025
|
||
# SPDX-FileContributor: Gergely Polonkai
|
||
#
|
||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||
"""Calculate the odds using Mythic RPG’s 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
|