diff options
| author | Supremist <sergkarv@gmail.com> | 2025-10-29 20:58:42 +0200 |
|---|---|---|
| committer | Supremist <sergkarv@gmail.com> | 2025-10-29 20:58:42 +0200 |
| commit | 0ada03ebf6a879e0a77475d525141d1090bcd634 (patch) | |
| tree | c236288c623f3a2c59fd682784e014faab120c61 /game.py | |
| parent | c29738661837749c72e3743c71814703cd11e062 (diff) | |
| download | pf2e_calc-0ada03ebf6a879e0a77475d525141d1090bcd634.tar.xz pf2e_calc-0ada03ebf6a879e0a77475d525141d1090bcd634.zip | |
Track bonuses and penalties from conditions
Diffstat (limited to 'game.py')
| -rw-r--r-- | game.py | 120 |
1 files changed, 119 insertions, 1 deletions
@@ -1,4 +1,5 @@ import re +import copy from enum import Enum from dataclasses import dataclass from abc import ABC, abstractmethod @@ -71,7 +72,7 @@ class RollInfo: @classmethod def parse(cls, str_data): - # match strings like 2d6+8 (15) + # match strings like '2d6+8 (15)' match = re.fullmatch(r"^(\d+)d(\d+)\s*(\+\s*\d+)?\s*(\s*\(\s*\d+\s*\))?", str_data) if not match: @@ -91,3 +92,120 @@ class LevelingPlan(ABC): @abstractmethod def build(self, level: int): pass + + +class Features: + abilities = ["str", "dex", "int", "wis", "cha", "con"] + saves = ["reflex", "will", "fortitude"] + defences = ["unarmored", "light", "medium", "heavy"] + weapons = ["unarmed", "simple", "martial", "advanced"] + skill_ability = { + "perception": "wis", + "fortitude": "con", + "reflex": "dex", + "will": "wis", + "acrobatics": "dex", + "arcana": "int", + "athletics": "str", + "crafting": "int", + "deception": "cha", + "diplomacy": "cha", + "intimidation": "cha", + "medicine": "wis", + "nature": "wis", + "occultism": "int", + "performance": "cha", + "religion": "wis", + "society": "int", + "stealth": "dex", + "survival": "wis", + "thievery": "dex", + } + + @staticmethod + def all(): + res = set(Features.defences) | set(Features.weapons) + res |= set(Features.saves) + res |= Features.skill_ability.keys() + return list(res) + + @staticmethod + def expand(shortcuts: list[str]): + if "all" in shortcuts: + return Features.all() + res = set() + for shortcut in shortcuts: + for skill, ability in Features.skill_ability.items(): + if shortcut == ability: + res.add(skill) + for feature in ["saves", "defences", "weapons"]: + if shortcut == feature: + res |= set(getattr(Features, feature)) + return list(res) + + +class Modifier: + names = ["circumstance", "status", "item"] + + def __init__(self, circumstance=0, status=0, item=0, untyped=0) -> None: + self.untyped = untyped + self.bonuses = dict() + self.penalties = dict() + for name in Modifier.names: + val = locals()[name] + if val > 0: + self.bonuses[name] = val + elif val < 0: + self.penalties[name] = val + + def total(self): + res = self.untyped + for val in self.bonuses.values(): + res += val + for val in self.penalties.values(): + res += val + return res + + def __iadd__(self, other): + if isinstance(other, int): + self.untyped += other + return self + if not isinstance(other, Modifier): + raise NotImplementedError + + self.untyped += other.untyped + for name in (self.bonuses.keys() & other.bonuses.keys()): + self.bonuses[name] = max(self.bonuses.get(name, 0), + other.bonuses.get(name, 0)) + for name in (self.penalties.keys() & other.penalties.keys()): + self.penalties[name] = min(self.penalties.get(name, 0), + other.penalties.get(name, 0)) + return self + + def __add__(self, other): + res = copy.deepcopy(self) + res += other + return res + + __radd__ = __add__ + + +@dataclass +class Condition: + name: str + filter: list + modifier: Modifier + + +class Conditions(list): + def modifier(self, features, exclude = None): + exclude = set(exclude) if exclude is not None else set() + features = set(features) + res = Modifier() + for condition in self: + positive = set(condition.filter) & features + negative = set(condition.filter) & exclude + if positive and not negative: + res += condition.modifier + return res + |
