summaryrefslogtreecommitdiff
path: root/game.py
diff options
context:
space:
mode:
Diffstat (limited to 'game.py')
-rw-r--r--game.py120
1 files changed, 119 insertions, 1 deletions
diff --git a/game.py b/game.py
index 75162ee..7adcc8a 100644
--- a/game.py
+++ b/game.py
@@ -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
+