From d25706163c7ea2aecfeaec6bfd7db1db6a255de3 Mon Sep 17 00:00:00 2001 From: helldh Date: Tue, 13 Jan 2026 09:10:49 +0300 Subject: [PATCH] Initial Commit --- .gitignore | 1 + .idea/.gitignore | 3 + .idea/dnd_dm_toolkit.iml | 10 + .../inspectionProfiles/profiles_settings.xml | 6 + .idea/misc.xml | 7 + .idea/modules.xml | 8 + .idea/vcs.xml | 6 + README.md | 0 src/__init__.py | 0 src/dnd_dm_toolkit/__init__.py | 0 src/dnd_dm_toolkit/core/__init__.py | 0 src/dnd_dm_toolkit/core/constants.py | 5 + src/dnd_dm_toolkit/core/enums/__init__.py | 0 src/dnd_dm_toolkit/core/enums/game.py | 244 ++++++++++++++++++ src/dnd_dm_toolkit/core/event_bus.py | 0 src/dnd_dm_toolkit/core/hooks.py | 0 src/dnd_dm_toolkit/core/sentinel.py | 17 ++ src/dnd_dm_toolkit/core/stuctures/__init__.py | 0 .../core/stuctures/character.py | 204 +++++++++++++++ src/dnd_dm_toolkit/core/stuctures/event.py | 72 ++++++ src/dnd_dm_toolkit/data/__init__.py | 0 src/dnd_dm_toolkit/data/stats.py | 45 ++++ src/dnd_dm_toolkit/entities/__init__.py | 0 src/dnd_dm_toolkit/entities/classes.py | 152 +++++++++++ .../entities/featues/__init__.py | 0 src/dnd_dm_toolkit/entities/featues/func.py | 4 + .../entities/featues/objects.py | 6 + src/dnd_dm_toolkit/entities/languages.py | 160 ++++++++++++ src/dnd_dm_toolkit/entities/races.py | 0 src/dnd_dm_toolkit/rules/__init__.py | 0 src/dnd_dm_toolkit/rules/core.py | 37 +++ src/dnd_dm_toolkit/scrapper.sh | 18 ++ src/dnd_dm_toolkit/utils/__init__.py | 0 src/dnd_dm_toolkit/utils/languages.py | 30 +++ 34 files changed, 1035 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/dnd_dm_toolkit.iml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 README.md create mode 100644 src/__init__.py create mode 100644 src/dnd_dm_toolkit/__init__.py create mode 100644 src/dnd_dm_toolkit/core/__init__.py create mode 100644 src/dnd_dm_toolkit/core/constants.py create mode 100644 src/dnd_dm_toolkit/core/enums/__init__.py create mode 100644 src/dnd_dm_toolkit/core/enums/game.py create mode 100644 src/dnd_dm_toolkit/core/event_bus.py create mode 100644 src/dnd_dm_toolkit/core/hooks.py create mode 100644 src/dnd_dm_toolkit/core/sentinel.py create mode 100644 src/dnd_dm_toolkit/core/stuctures/__init__.py create mode 100644 src/dnd_dm_toolkit/core/stuctures/character.py create mode 100644 src/dnd_dm_toolkit/core/stuctures/event.py create mode 100644 src/dnd_dm_toolkit/data/__init__.py create mode 100644 src/dnd_dm_toolkit/data/stats.py create mode 100644 src/dnd_dm_toolkit/entities/__init__.py create mode 100644 src/dnd_dm_toolkit/entities/classes.py create mode 100644 src/dnd_dm_toolkit/entities/featues/__init__.py create mode 100644 src/dnd_dm_toolkit/entities/featues/func.py create mode 100644 src/dnd_dm_toolkit/entities/featues/objects.py create mode 100644 src/dnd_dm_toolkit/entities/languages.py create mode 100644 src/dnd_dm_toolkit/entities/races.py create mode 100644 src/dnd_dm_toolkit/rules/__init__.py create mode 100644 src/dnd_dm_toolkit/rules/core.py create mode 100755 src/dnd_dm_toolkit/scrapper.sh create mode 100644 src/dnd_dm_toolkit/utils/__init__.py create mode 100644 src/dnd_dm_toolkit/utils/languages.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b694934 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.venv \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/dnd_dm_toolkit.iml b/.idea/dnd_dm_toolkit.iml new file mode 100644 index 0000000..ee96967 --- /dev/null +++ b/.idea/dnd_dm_toolkit.iml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..0fbb8a9 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..26ad403 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/dnd_dm_toolkit/__init__.py b/src/dnd_dm_toolkit/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/dnd_dm_toolkit/core/__init__.py b/src/dnd_dm_toolkit/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/dnd_dm_toolkit/core/constants.py b/src/dnd_dm_toolkit/core/constants.py new file mode 100644 index 0000000..288caa5 --- /dev/null +++ b/src/dnd_dm_toolkit/core/constants.py @@ -0,0 +1,5 @@ +from .sentinel import _AllStatsSentinel + +ALL_STATS = _AllStatsSentinel() + +MAXIMUM_STAT_VALUE = 30 \ No newline at end of file diff --git a/src/dnd_dm_toolkit/core/enums/__init__.py b/src/dnd_dm_toolkit/core/enums/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/dnd_dm_toolkit/core/enums/game.py b/src/dnd_dm_toolkit/core/enums/game.py new file mode 100644 index 0000000..4afaba6 --- /dev/null +++ b/src/dnd_dm_toolkit/core/enums/game.py @@ -0,0 +1,244 @@ +from enum import Enum, auto + +class Ability(Enum): + """ + The six core ability scores in Dungeons & Dragons, which represent a character's + innate physical and mental capabilities. These abilities influence skill checks, + attack rolls, saving throws, and other mechanics. + + - STRENGTH: Physical power, affects melee attacks and lifting/carrying capacity. + - DEXTERITY: Agility and reflexes, affects ranged attacks, AC, and initiative. + - CONSTITUTION: Endurance and health, affects hit points and resistance to fatigue/poison. + - INTELLIGENCE: Reasoning and memory, affects knowledge-based skills and spellcasting for some classes. + - WISDOM: Perception and insight, affects awareness, intuition, and some spellcasting. + - CHARISMA: Force of personality, affects social interactions and some spellcasting. + """ + STRENGTH = auto() + DEXTERITY = auto() + CONSTITUTION = auto() + INTELLIGENCE = auto() + WISDOM = auto() + CHARISMA = auto() + +class Skill(Enum): + """ + List of character skills, each tied to a specific ability score. + Skills determine how well a character performs certain tasks and checks in the game. + + - ACROBATICS (DEX): Balance, agility, and tumbling. + - ANIMAL_HANDLING (WIS): Calming or controlling animals. + - ARCANA (INT): Knowledge of magic, spells, and magical traditions. + - ATHLETICS (STR): Climbing, swimming, jumping, and other physical feats. + - DECEPTION (CHA): Lying, misleading, or bluffing. + - HISTORY (INT): Knowledge of historical events and civilizations. + - INSIGHT (WIS): Detecting motives, emotions, or hidden truths. + - INTIMIDATION (CHA): Coercing or threatening others. + - INVESTIGATION (INT): Searching for clues, analyzing evidence. + - MEDICINE (WIS): Treating wounds, diagnosing illness. + - NATURE (INT): Knowledge of plants, animals, terrain, and weather. + - PERCEPTION (WIS): Spotting, hearing, or noticing hidden things. + - PERFORMANCE (CHA): Acting, singing, dancing, or entertaining. + - PERSUASION (CHA): Convincing others through charm or negotiation. + - RELIGION (INT): Knowledge of deities, rituals, and sacred traditions. + - SLEIGHT_OF_HAND (DEX): Pickpocketing, trickery with hands. + - STEALTH (DEX): Moving silently or hiding. + - SURVIVAL (WIS): Tracking, foraging, and surviving in the wild. + """ + ACROBATICS = auto() + ANIMAL_HANDLING = auto() + ARCANA = auto() + ATHLETICS = auto() + DECEPTION = auto() + HISTORY = auto() + INSIGHT = auto() + INTIMIDATION = auto() + INVESTIGATION = auto() + MEDICINE = auto() + NATURE = auto() + PERCEPTION = auto() + PERFORMANCE = auto() + PERSUASION = auto() + RELIGION = auto() + SLEIGHT_OF_HAND = auto() + STEALTH = auto() + SURVIVAL = auto() + +class CheckDifficulty(Enum): + """ + Difficulty class (DC) levels for ability and skill checks. + These represent the target number a player must meet or exceed on a d20 roll + to succeed at a task. + + - VERY_EASY: DC 5 + - EASY: DC 10 + - MEDIUM: DC 15 + - HARD: DC 20 + - VERY_HARD: DC 25 + - NEARLY_IMPOSSIBLE: DC 30 + """ + VERY_EASY = 5 + EASY = 10 + MEDIUM = 15 + HARD = 20 + VERY_HARD = 25 + NEARLY_IMPOSSIBLE = 30 + +class TravelPace(Enum): + """ + Standard travel paces for overland movement, given in feet per hour, feet per day, + and maximum hours per day. Adjusted for character movement and terrain. + + - FAST: Covers more distance but reduces perception and may cause exhaustion. + - NORMAL: Default pace for most travel. + - SLOW: Moves cautiously, allowing better perception and reduced fatigue. + """ + FAST = (400, 4000, 30000) # (feet per minute, feet per hour, feet per day) + NORMAL = (300, 3000, 24000) + SLOW = (200, 2000, 18000) + +class CreatureSize(Enum): + """ + Represents creature sizes and their occupied space on the battle grid. + + Each value is a tuple of (side length in feet, grid cells). + + - TINY: 2.5 ft, 0 cells (½ × ½ grid) + - SMALL: 5 ft, 1 cell (1 × 1 grid) + - MEDIUM: 5 ft, 1 cell (1 × 1 grid) + - LARGE: 10 ft, 2 cells (2 × 2 grid) + - HUGE: 15 ft, 3 cells (3 × 3 grid) + - GARGANTUAN: 20 ft, 4 cells (4 × 4 grid) + """ + TINY = (2.5, 0.25) + SMALL = (5, 1) + MEDIUM = (5, 1) + LARGE = (10, 2) + HUGE = (15, 3) + GARGANTUAN = (20, 4) + +class CoverType(Enum): + """ + Types of cover in combat, affecting how hard it is to hit a creature. + + Attributes: + NO_COVER: Creature is fully exposed, no protection. + HALF: Creature has partial cover, some protection from attacks. + THREE_QUARTERS: Creature is mostly behind cover, harder to hit. + TOTAL: Creature is completely behind cover, nearly immune to attacks. + """ + NO_COVER = None + HALF = auto() + THREE_QUARTERS = auto() + TOTAL = auto() + +class AlignmentLC(Enum): + """ + Law–Chaos alignment axis. + + This axis describes a character’s attitude toward order, tradition, + authority, and structure versus freedom, individuality, and spontaneity. + + - LAWFUL: Values order, rules, hierarchy, and tradition. + - VERITABLE: Neutral position on the Law–Chaos axis. The character does not + strongly favor either structure or anarchy. The term is intentionally + chosen to emphasize balance and internal consistency rather than indifference. + - CHAOTIC: Values personal freedom, adaptability, and rejection of imposed order. + """ + LAWFUL = auto() + VERITABLE = auto() + CHAOTIC = auto() + +class AlignmentGE(Enum): + """ + Good–Evil alignment axis. + + This axis represents a character’s moral outlook and concern for the + well-being of others. + + - GOOD: Acts with altruism, compassion, and respect for life. + - NEUTRAL: Lacks strong moral commitment toward good or evil. + - EVIL: Acts with cruelty, selfishness, or disregard for others. + """ + GOOD = auto() + NEUTRAL = auto() + EVIL = auto() + +class LanguageScript(Enum): + """Writing systems used by languages.""" + NONE = auto() # Language hasn't writing system + COMMON = auto() + DWARVISH = auto() + ELVISH = auto() + INFERNAL = auto() + DRACONIC = auto() + CELESTIAL = auto() + +class LanguageRarity(Enum): + """How common a language is.""" + STANDARD = auto() + EXOTIC = auto() + SECRET = auto() + +class CreatureType(Enum): + HUMANOID = auto() + +class FeatureSource(Enum): + RACE = auto() + CLASS = auto() + SPELL = auto() + ITEM = auto() + +class LightCoverage(Enum): + FULL = auto() + DIM = auto() + DARK = auto() + +class EventType(Enum): + """All possible game events that can be hooked.""" + + # Attack Events + BEFORE_ATTACK = auto() + AFTER_ATTACK = auto() + ATTACK_HIT = auto() + ATTACK_MISS = auto() + CRITICAL_HIT = auto() + + # Damage Events + BEFORE_DAMAGE = auto() + AFTER_DAMAGE = auto() + DAMAGE_TAKEN = auto() + DAMAGE_DEALT = auto() + + # Saving Throw Events + BEFORE_SAVING_THROW = auto() + AFTER_SAVING_THROW = auto() + SAVING_THROW_SUCCESS = auto() + SAVING_THROW_FAILURE = auto() + + # Ability Check Events + BEFORE_ABILITY_CHECK = auto() + AFTER_ABILITY_CHECK = auto() + + # Skill Check Events + BEFORE_SKILL_CHECK = auto() + AFTER_SKILL_CHECK = auto() + + # Movement Events + BEFORE_MOVE = auto() + AFTER_MOVE = auto() + + # HP Events + HP_GAINED = auto() + HP_LOST = auto() + HP_ZERO = auto() + + # Turn Events + TURN_START = auto() + TURN_END = auto() + + # Rest Events + SHORT_REST = auto() + LONG_REST = auto() + + # Level Events + LEVEL_UP = auto() \ No newline at end of file diff --git a/src/dnd_dm_toolkit/core/event_bus.py b/src/dnd_dm_toolkit/core/event_bus.py new file mode 100644 index 0000000..e69de29 diff --git a/src/dnd_dm_toolkit/core/hooks.py b/src/dnd_dm_toolkit/core/hooks.py new file mode 100644 index 0000000..e69de29 diff --git a/src/dnd_dm_toolkit/core/sentinel.py b/src/dnd_dm_toolkit/core/sentinel.py new file mode 100644 index 0000000..8c17c4a --- /dev/null +++ b/src/dnd_dm_toolkit/core/sentinel.py @@ -0,0 +1,17 @@ +class _AllStatsSentinel: + """Sentinel для обозначения 'любые навыки'""" + _instance = None + + def __new__(cls): + if cls._instance is None: + cls._instance = super().__new__(cls) + return cls._instance + + def __repr__(self): + return "ALL_STATS" + + def __eq__(self, other: object) -> bool: + return isinstance(other, _AllStatsSentinel) + + def __hash__(self) -> int: + return hash("ALL_STATS") \ No newline at end of file diff --git a/src/dnd_dm_toolkit/core/stuctures/__init__.py b/src/dnd_dm_toolkit/core/stuctures/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/dnd_dm_toolkit/core/stuctures/character.py b/src/dnd_dm_toolkit/core/stuctures/character.py new file mode 100644 index 0000000..9583569 --- /dev/null +++ b/src/dnd_dm_toolkit/core/stuctures/character.py @@ -0,0 +1,204 @@ +import warnings +from typing import List, Callable, Protocol, Any +from dataclasses import dataclass +from ..enums.game import ( + Ability, + Skill, + AlignmentLC, + AlignmentGE, + CreatureSize, + LanguageScript, + LanguageRarity, + CreatureType +) +from ..sentinel import _AllStatsSentinel +from ..constants import MAXIMUM_STAT_VALUE + +@dataclass +class Attributes: + """ + Represents the six core ability scores of a character. + + Ability scores define a character’s physical and mental capabilities + and serve as the foundation for most game mechanics, including skill + checks, saving throws, attacks, and spellcasting. + + Each value is typically in the range 1–30. + """ + strength: int + dexterity: int + constitution: int + intelligence: int + wisdom: int + charisma: int + + def __post_init__(self): + """Validate class definition.""" + for name, value in self.__dict__.items(): + if getattr(self, name) > MAXIMUM_STAT_VALUE: + warnings.warn("Current rules configuration does not support " + f"creating character with stat's higher that {MAXIMUM_STAT_VALUE}, " + f"{name} attribute set to {MAXIMUM_STAT_VALUE}", + UserWarning) + setattr(self, name, MAXIMUM_STAT_VALUE) + +@dataclass(frozen=True) +class CharacterClass: + """ + Describes a character’s class. + + A character class defines the character’s role, core mechanics, and + progression framework, including hit dice, proficiencies, and + recommended abilities. + + This object is intended to be immutable and reusable, allowing both + SRD and homebrew classes to be defined as constants. + """ + name: str + hit_die: int + description: str + skill_choices: int + saving_throws: List[Ability] + abilities: List[Ability] | _AllStatsSentinel | None = None + skills: List[Skill] | _AllStatsSentinel | None = None + + def __post_init__(self): + """Validate class definition.""" + if self.skills is None: + raise ValueError(f"{self.name}: 'skills' must be specified explicitly") + + if self.abilities is None: + object.__setattr__(self, 'abilities', []) + + if self.hit_die not in {6, 8, 10, 12}: + raise ValueError( + f"{self.name}: hit_die must be 6, 8, 10, or 12 (got {self.hit_die})" + ) + + if self.skill_choices < 0: + raise ValueError( + f"{self.name}: skill_choices cannot be negative" + ) + + if self.skill_choices > 10: + warnings.warn( + f"{self.name}: skill_choices={self.skill_choices} is unusually high. " + f"Are you sure this is intended?", + UserWarning + ) + + if not isinstance(self.skills, _AllStatsSentinel): + if len(self.skills) < self.skill_choices: + raise ValueError( + f"{self.name}: skill_choices={self.skill_choices} but only " + f"{len(self.skills)} skills available" + ) + +@dataclass +class Alignment: + """ + Represents a character’s alignment using two independent axes: + Law–Chaos and Good–Evil. + + This structure follows the standard Dungeons & Dragons alignment model + as defined in the SRD, without extensions or intermediate states. + """ + lc_axis: AlignmentLC + ge_axis: AlignmentGE + +@dataclass(frozen=True) +class ActionDefinition: + name: str + related_abilities: list[Ability] + related_skills: list[Skill] + description: str + +@dataclass +class CharacterItem: + pass + +@dataclass +class CharacterToolKits(CharacterItem): + pass + +class Featureable(Protocol): + pass + +@dataclass +class Feature: + """Abstract mechanic/feature""" + name: str + source: Featureable + description: str + stackable: bool + apply: Callable[[Featureable], None] | None = None # If None = Feature not mechanical + condition: Callable[[Featureable], bool] | None = None # If None = Feature always works + + def __post_init__(self): + if not self.apply: + if self.condition: + warnings.warn("Non-mechanical features cannot have the condition " + "`self.condition` field rewritten to `None`", + UserWarning) + self.condition = None + +@dataclass(frozen=True) +class Background: + """ + Represents a character background. + + A background describes a character’s life before adventuring and + provides narrative context, recommended abilities, proficiencies, + and special features. + + The behavioral logic of a background (such as granting proficiencies + or feats) is intended to be applied externally via a callable. + """ + name: str + recommended_abilities: List[Ability] + background_specs: List[Feature] + +@dataclass(frozen=True) +class Language: + """ + Represents a language in the game world. + + Attributes: + name: Language name (e.g., "Common", "Elvish"). + script: Writing system used, or None for spoken-only languages. + rarity: How common the language is (standard/exotic/secret). + description: Brief description of the language. + """ + name: str + script: LanguageScript + rarity: LanguageRarity + description: str = "" + +@dataclass(frozen=True) +class Race: + """ + Defines a playable race with its inherent traits and abilities. + + A race represents a character's species and provides ability score + increases, size, speed, languages, and special features. + + Attributes: + name: Race name (e.g., "Hill Dwarf", "High Elf"). + ability_score_increases: Mapping of abilities to their bonuses. + size: The race's size category. + speed: Base walking speed in feet. + languages: Languages the race knows automatically. + features: List of racial features (darkvision, resistances, etc.). + age_description: Optional description of the race's lifespan. + alignment_tendency: Optional typical alignment for the race. + """ + name: str + ability_score_increases: dict[Ability, int] + size: CreatureSize + speed: int + type: CreatureType + languages: list[Language] + features: list[Feature] + age_description: str = "" + alignment_tendency: str = "" + extra_language_choices: int = 0 \ No newline at end of file diff --git a/src/dnd_dm_toolkit/core/stuctures/event.py b/src/dnd_dm_toolkit/core/stuctures/event.py new file mode 100644 index 0000000..2d0cf1c --- /dev/null +++ b/src/dnd_dm_toolkit/core/stuctures/event.py @@ -0,0 +1,72 @@ +from typing import Dict, Any +from dataclasses import dataclass +from ..enums.game import ( + EventType, + Ability, + Skill +) + +@dataclass +class GameEvent: + """ + Base class for all game events. + + Attributes: + event_type: The type of event. + source: Entity that triggered the event (e.g., Character, Monster). + target: Entity affected by the event (optional). + data: Additional event-specific data. + cancelled: If True, the event is cancelled and won't proceed. + """ + event_type: EventType + source: Any + target: Any | None = None + data: Dict[str, Any] | None = None + cancelled: bool = False + + def cancel(self): + """Cancel this event.""" + self.cancelled = True + +# Specific Event Types + +@dataclass +class AttackEvent(GameEvent): + """Event for attacks.""" + attacker: Any = None + defender: Any = None + weapon: Any | None = None + attack_roll: int | None = None + is_hit: bool = False + is_critical: bool = False + +@dataclass +class DamageEvent(GameEvent): + """Event for damage.""" + amount: int = 0 + damage_type: str = "untyped" + is_critical: bool = False + +@dataclass +class SavingThrowEvent(GameEvent): + """Event for saving throws.""" + ability: Ability = None + dc: int = 10 + roll_result: int | None = None + is_success: bool = False + +@dataclass +class AbilityCheckEvent(GameEvent): + """Event for ability checks.""" + ability: Ability = None + dc: int = 10 + roll_result: int | None = None + is_success: bool = False + +@dataclass +class SkillCheckEvent(GameEvent): + """Event for skill checks.""" + skill: Skill = None + dc: int = 10 + roll_result: int | None = None + is_success: bool = False \ No newline at end of file diff --git a/src/dnd_dm_toolkit/data/__init__.py b/src/dnd_dm_toolkit/data/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/dnd_dm_toolkit/data/stats.py b/src/dnd_dm_toolkit/data/stats.py new file mode 100644 index 0000000..76a8783 --- /dev/null +++ b/src/dnd_dm_toolkit/data/stats.py @@ -0,0 +1,45 @@ +from ..core.enums.game import Ability, Skill + +skill_to_ability = { + Skill.ACROBATICS: Ability.DEXTERITY, + Skill.ANIMAL_HANDLING: Ability.WISDOM, + Skill.ARCANA: Ability.INTELLIGENCE, + Skill.ATHLETICS: Ability.STRENGTH, + Skill.DECEPTION: Ability.CHARISMA, + Skill.HISTORY: Ability.INTELLIGENCE, + Skill.INSIGHT: Ability.WISDOM, + Skill.INTIMIDATION: Ability.CHARISMA, + Skill.INVESTIGATION: Ability.INTELLIGENCE, + Skill.MEDICINE: Ability.WISDOM, + Skill.NATURE: Ability.INTELLIGENCE, + Skill.PERCEPTION: Ability.WISDOM, + Skill.PERFORMANCE: Ability.CHARISMA, + Skill.PERSUASION: Ability.CHARISMA, + Skill.RELIGION: Ability.INTELLIGENCE, + Skill.SLEIGHT_OF_HAND: Ability.DEXTERITY, + Skill.STEALTH: Ability.DEXTERITY, + Skill.SURVIVAL: Ability.WISDOM, +} + +proficiency_bonus_by_level = { + 1: 2, + 2: 2, + 3: 2, + 4: 2, + 5: 3, + 6: 3, + 7: 3, + 8: 3, + 9: 4, + 10: 4, + 11: 4, + 12: 4, + 13: 5, + 14: 5, + 15: 5, + 16: 5, + 17: 6, + 18: 6, + 19: 6, + 20: 6 +} \ No newline at end of file diff --git a/src/dnd_dm_toolkit/entities/__init__.py b/src/dnd_dm_toolkit/entities/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/dnd_dm_toolkit/entities/classes.py b/src/dnd_dm_toolkit/entities/classes.py new file mode 100644 index 0000000..7a82639 --- /dev/null +++ b/src/dnd_dm_toolkit/entities/classes.py @@ -0,0 +1,152 @@ +from ..core.constants import ALL_STATS +from ..core.enums.game import Ability, Skill +from ..core.stuctures.character import CharacterClass + +BARBARIAN = CharacterClass( + name="Barbarian", + hit_die=12, + description="Savage warriors of unbridled fury.", + saving_throws=[Ability.STRENGTH, Ability.CONSTITUTION], + skill_choices=2, + skills=[ + Skill.ANIMAL_HANDLING, Skill.ATHLETICS, Skill.INTIMIDATION, + Skill.NATURE, Skill.PERCEPTION, Skill.SURVIVAL + ], +) + +BARD = CharacterClass( + name="Bard", + hit_die=8, + description="Inspirational performers whose music weaves magic.", + saving_throws=[Ability.DEXTERITY, Ability.CHARISMA], + skill_choices=3, + skills=ALL_STATS +) + +CLERIC = CharacterClass( + name="Cleric", + hit_die=8, + description="Divine agents wielding holy power to heal or harm.", + saving_throws=[Ability.WISDOM, Ability.CHARISMA], + skill_choices=2, + skills=[ + Skill.HISTORY, Skill.INSIGHT, Skill.MEDICINE, + Skill.PERSUASION, Skill.RELIGION + ], +) + +DRUID = CharacterClass( + name="Druid", + hit_die=8, + description="Nature speakers who command elemental forces.", + saving_throws=[Ability.INTELLIGENCE, Ability.WISDOM], + skill_choices=2, + skills=[ + Skill.ARCANA, Skill.INSIGHT, Skill.MEDICINE, + Skill.NATURE, Skill.PERCEPTION, Skill.RELIGION, + Skill.SURVIVAL + ], +) + +FIGHTER = CharacterClass( + name="Fighter", + hit_die=10, + description="Versatile warriors trained in weapons and armor.", + saving_throws=[Ability.STRENGTH, Ability.CONSTITUTION], + skill_choices=2, + skills=[ + Skill.ACROBATICS, Skill.ANIMAL_HANDLING, Skill.ATHLETICS, + Skill.HISTORY, Skill.INSIGHT, Skill.INTIMIDATION, + Skill.PERCEPTION, Skill.SURVIVAL + ], +) + +MONK = CharacterClass( + name="Monk", + hit_die=8, + description="Masters of martial arts and mystic ki.", + saving_throws=[Ability.STRENGTH, Ability.DEXTERITY], + skill_choices=2, + skills=[ + Skill.ACROBATICS, Skill.ATHLETICS, Skill.HISTORY, + Skill.INSIGHT, Skill.RELIGION, Skill.STEALTH + ], +) + +PALADIN = CharacterClass( + name="Paladin", + hit_die=10, + description="Holy knights who smite foes and protect allies.", + saving_throws=[Ability.WISDOM, Ability.CHARISMA], + skill_choices=2, + skills=[ + Skill.ATHLETICS, Skill.INSIGHT, Skill.INTIMIDATION, + Skill.MEDICINE, Skill.PERSUASION, Skill.RELIGION + ], +) + +# Ranger +RANGER = CharacterClass( + name="Ranger", + hit_die=10, + description="Skilled hunters and wilderness warriors.", + saving_throws=[Ability.STRENGTH, Ability.DEXTERITY], + skill_choices=3, + skills=[ + Skill.ANIMAL_HANDLING, Skill.ATHLETICS, Skill.INSIGHT, + Skill.INVESTIGATION, Skill.NATURE, Skill.PERCEPTION, + Skill.STEALTH, Skill.SURVIVAL + ], +) + +ROGUE = CharacterClass( + name="Rogue", + hit_die=8, + description="Stealthy opportunists and skill experts.", + saving_throws=[Ability.DEXTERITY, Ability.INTELLIGENCE], + skill_choices=4, + skills=[ + Skill.ACROBATICS, Skill.ATHLETICS, Skill.DECEPTION, + Skill.INSIGHT, Skill.INTIMIDATION, Skill.INVESTIGATION, + Skill.PERCEPTION, Skill.PERFORMANCE, + Skill.PERSUASION, Skill.SLEIGHT_OF_HAND, + Skill.STEALTH, Skill.SURVIVAL + ], +) + +SORCERER = CharacterClass( + name="Sorcerer", + hit_die=6, + description="Innate spellcasters fueled by raw power.", + saving_throws=[Ability.CONSTITUTION, Ability.CHARISMA], + skill_choices=2, + skills=[ + Skill.ARCANA, Skill.DECEPTION, Skill.INSIGHT, + Skill.INTIMIDATION, Skill.PERSUASION, Skill.RELIGION + ], +) + +WARLOCK = CharacterClass( + name="Warlock", + hit_die=8, + description="Pact-bound wielders of forbidden power.", + saving_throws=[Ability.WISDOM, Ability.CHARISMA], + skill_choices=2, + skills=[ + Skill.ARCANA, Skill.DECEPTION, Skill.HISTORY, + Skill.INSIGHT, Skill.INTIMIDATION, Skill.INVESTIGATION, + Skill.PERCEPTION, Skill.RELIGION + ], +) + +WIZARD = CharacterClass( + name="Wizard", + hit_die=6, + description="Scholars of the arcane arts.", + saving_throws=[Ability.INTELLIGENCE, Ability.WISDOM], + skill_choices=2, + skills=[ + Skill.ARCANA, Skill.HISTORY, Skill.INSIGHT, + Skill.INVESTIGATION, Skill.MEDICINE, Skill.RELIGION + ], +) \ No newline at end of file diff --git a/src/dnd_dm_toolkit/entities/featues/__init__.py b/src/dnd_dm_toolkit/entities/featues/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/dnd_dm_toolkit/entities/featues/func.py b/src/dnd_dm_toolkit/entities/featues/func.py new file mode 100644 index 0000000..210692e --- /dev/null +++ b/src/dnd_dm_toolkit/entities/featues/func.py @@ -0,0 +1,4 @@ +from ...core.stuctures.character import Featureable + +def darkvision(entity: Featureable): + pass \ No newline at end of file diff --git a/src/dnd_dm_toolkit/entities/featues/objects.py b/src/dnd_dm_toolkit/entities/featues/objects.py new file mode 100644 index 0000000..e2f0e45 --- /dev/null +++ b/src/dnd_dm_toolkit/entities/featues/objects.py @@ -0,0 +1,6 @@ +from ...core.enums.game import FeatureSource +from ...core.stuctures.character import Feature + +DARKVISION = Feature( + +) \ No newline at end of file diff --git a/src/dnd_dm_toolkit/entities/languages.py b/src/dnd_dm_toolkit/entities/languages.py new file mode 100644 index 0000000..a0028a0 --- /dev/null +++ b/src/dnd_dm_toolkit/entities/languages.py @@ -0,0 +1,160 @@ +from ..core.stuctures.character import ( + LanguageScript, + LanguageRarity, + Language +) + +# === STANDARD LANGUAGES === + +COMMON = Language( + name="Common", + script=LanguageScript.COMMON, + rarity=LanguageRarity.STANDARD, + description="The trade language spoken across most civilized lands." +) + +COMMON_SIGN_LANG = Language( + name="Common", + script=LanguageScript.NONE, + rarity=LanguageRarity.STANDARD, + description="The trade sign language spoken across most civilized lands." +) + +DRACONIC = Language( + name="Draconic", + script=LanguageScript.DRACONIC, + rarity=LanguageRarity.STANDARD, + description="One of the oldest languages, often used in magic studies." +) + +DWARVISH = Language( + name="Dwarvish", + script=LanguageScript.DWARVISH, + rarity=LanguageRarity.STANDARD, + description="Full of hard consonants and guttural sounds." +) + +ELVISH = Language( + name="Elvish", + script=LanguageScript.ELVISH, + rarity=LanguageRarity.STANDARD, + description="Fluid, with subtle intonations and intricate grammar." +) + +GIANT = Language( + name="Giant", + script=LanguageScript.DWARVISH, + rarity=LanguageRarity.STANDARD, + description="The language of giants and their kin." +) + +GNOMISH = Language( + name="Gnomish", + script=LanguageScript.DWARVISH, + rarity=LanguageRarity.STANDARD, + description="Renowned for technical treatises and catalogs of knowledge." +) + +GOBLIN = Language( + name="Goblin", + script=LanguageScript.DWARVISH, + rarity=LanguageRarity.STANDARD, + description="Harsh and strident, spoken by goblins, hobgoblins, and bugbears." +) + +HALFLING = Language( + name="Halfling", + script=LanguageScript.COMMON, + rarity=LanguageRarity.STANDARD, + description="Not often written; halflings have a strong oral tradition." +) + +ORC = Language( + name="Orc", + script=LanguageScript.DWARVISH, + rarity=LanguageRarity.STANDARD, + description="Harsh and grating, with hard consonants and no script of its own." +) + +# === EXOTIC LANGUAGES === + +ABYSSAL = Language( + name="Abyssal", + script=LanguageScript.INFERNAL, + rarity=LanguageRarity.EXOTIC, + description="The language of demons and the Abyss." +) + +CELESTIAL = Language( + name="Celestial", + script=LanguageScript.CELESTIAL, + rarity=LanguageRarity.EXOTIC, + description="The tongue of angels and other good outsiders." +) + +DEEP_SPEECH = Language( + name="Deep Speech", + script=LanguageScript.NONE, + rarity=LanguageRarity.EXOTIC, + description="An alien language spoken by aberrations from the Far Realm." +) + +INFERNAL = Language( + name="Infernal", + script=LanguageScript.INFERNAL, + rarity=LanguageRarity.EXOTIC, + description="The language of devils and the Nine Hells." +) + +PRIMORDIAL = Language( + name="Primordial", + script=LanguageScript.DWARVISH, + rarity=LanguageRarity.EXOTIC, + description="The language of elementals, with dialects: Aquan, Auran, Ignan, Terran." +) + +SYLVAN = Language( + name="Sylvan", + script=LanguageScript.ELVISH, + rarity=LanguageRarity.EXOTIC, + description="The language of the Feywild and its inhabitants." +) + +UNDERCOMMON = Language( + name="Undercommon", + script=LanguageScript.ELVISH, + rarity=LanguageRarity.EXOTIC, + description="The trade language of the Underdark." +) + +# === SECRET LANGUAGES === + +DRUIDIC = Language( + name="Druidic", + script=LanguageScript.NONE, + rarity=LanguageRarity.SECRET, + description="Secret language of druids. Druids are forbidden to teach it to non-druids." +) + +THIEVES_CANT = Language( + name="Thieves' Cant", + script=LanguageScript.NONE, + rarity=LanguageRarity.SECRET, + description="A secret mix of slang and coded messages used by criminals." +) + +# Convenience: all languages in one place +ALL_LANGUAGES = [ + COMMON, DWARVISH, ELVISH, GIANT, GNOMISH, GOBLIN, HALFLING, ORC, + ABYSSAL, CELESTIAL, DRACONIC, DEEP_SPEECH, INFERNAL, PRIMORDIAL, + SYLVAN, UNDERCOMMON, DRUIDIC, THIEVES_CANT +] + +STANDARD_LANGUAGES = [ + COMMON, DRACONIC, DWARVISH, ELVISH, GIANT, GNOMISH, GOBLIN, HALFLING, ORC +] + +EXOTIC_LANGUAGES = [ + ABYSSAL, CELESTIAL, DEEP_SPEECH, INFERNAL, PRIMORDIAL, + SYLVAN, UNDERCOMMON +] \ No newline at end of file diff --git a/src/dnd_dm_toolkit/entities/races.py b/src/dnd_dm_toolkit/entities/races.py new file mode 100644 index 0000000..e69de29 diff --git a/src/dnd_dm_toolkit/rules/__init__.py b/src/dnd_dm_toolkit/rules/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/dnd_dm_toolkit/rules/core.py b/src/dnd_dm_toolkit/rules/core.py new file mode 100644 index 0000000..1e2e968 --- /dev/null +++ b/src/dnd_dm_toolkit/rules/core.py @@ -0,0 +1,37 @@ +from typing import List +from ..core.stuctures.character import CharacterClass, Race, Attributes, Background, Language, Featureable + +class Character(Featureable): + def __init__(self, + race: Race, + dnd_class: CharacterClass, + attrs: Attributes, + background: Background, + languages: List[Language] + ): + self._char_race = race + self._char_cls = dnd_class + self._char_attrs = attrs + self._char_background = background + self._char_languages = languages + + # Special Fields + self._char_features = list() + self._current_level = 0 + self._char_available_languages = list() + + self._init_languages() + self._init_features() + self.level_up() + + def _init_languages(self): + pass + + def _init_features(self): + all_features = self._char_race.features + self._char_background.background_specs + + for feature in all_features: + pass + + def level_up(self): + pass \ No newline at end of file diff --git a/src/dnd_dm_toolkit/scrapper.sh b/src/dnd_dm_toolkit/scrapper.sh new file mode 100755 index 0000000..7c70281 --- /dev/null +++ b/src/dnd_dm_toolkit/scrapper.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +OUTPUT="all_code.txt" +ROOT="." + +> "$OUTPUT" + +find "$ROOT" \ + -type d -name "__pycache__" -prune -o \ + -type f -name "*.py" ! -name "*.pyc" \ + -print | sort | while read -r file; do + echo "########################################" >> "$OUTPUT" + echo "# FILE: $file" >> "$OUTPUT" + echo "########################################" >> "$OUTPUT" + echo >> "$OUTPUT" + cat "$file" >> "$OUTPUT" + echo -e "\n\n" >> "$OUTPUT" +done diff --git a/src/dnd_dm_toolkit/utils/__init__.py b/src/dnd_dm_toolkit/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/dnd_dm_toolkit/utils/languages.py b/src/dnd_dm_toolkit/utils/languages.py new file mode 100644 index 0000000..2018db6 --- /dev/null +++ b/src/dnd_dm_toolkit/utils/languages.py @@ -0,0 +1,30 @@ +from ..core.enums.game import LanguageRarity +from ..core.stuctures.character import Language, Race + +def get_available_languages( + race: Race, + exclude_secret: bool = True +) -> list[Language]: + """ + Returns languages available for a character to learn. + + Args: + race: The character's race. + exclude_secret: If True, excludes secret languages (Druidic, Thieves' Cant). + + Returns: + List of learnable languages. + """ + from ..entities.languages import ALL_LANGUAGES + + # Already known languages + known = set(race.languages) + + # Available languages + available = [ + lang for lang in ALL_LANGUAGES + if lang not in known + and (not exclude_secret or lang.rarity != LanguageRarity.SECRET) + ] + + return available \ No newline at end of file