Initial Commit
This commit is contained in:
commit
d25706163c
34 changed files with 1035 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
.venv
|
||||
3
.idea/.gitignore
generated
vendored
Normal file
3
.idea/.gitignore
generated
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
10
.idea/dnd_dm_toolkit.iml
generated
Normal file
10
.idea/dnd_dm_toolkit.iml
generated
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="Python 3.13 (dnd_dm_toolkit)" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
||||
7
.idea/misc.xml
generated
Normal file
7
.idea/misc.xml
generated
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Black">
|
||||
<option name="sdkName" value="Python 3.13 (dnd_dm_toolkit)" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.13 (dnd_dm_toolkit)" project-jdk-type="Python SDK" />
|
||||
</project>
|
||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/dnd_dm_toolkit.iml" filepath="$PROJECT_DIR$/.idea/dnd_dm_toolkit.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
0
README.md
Normal file
0
README.md
Normal file
0
src/__init__.py
Normal file
0
src/__init__.py
Normal file
0
src/dnd_dm_toolkit/__init__.py
Normal file
0
src/dnd_dm_toolkit/__init__.py
Normal file
0
src/dnd_dm_toolkit/core/__init__.py
Normal file
0
src/dnd_dm_toolkit/core/__init__.py
Normal file
5
src/dnd_dm_toolkit/core/constants.py
Normal file
5
src/dnd_dm_toolkit/core/constants.py
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
from .sentinel import _AllStatsSentinel
|
||||
|
||||
ALL_STATS = _AllStatsSentinel()
|
||||
|
||||
MAXIMUM_STAT_VALUE = 30
|
||||
0
src/dnd_dm_toolkit/core/enums/__init__.py
Normal file
0
src/dnd_dm_toolkit/core/enums/__init__.py
Normal file
244
src/dnd_dm_toolkit/core/enums/game.py
Normal file
244
src/dnd_dm_toolkit/core/enums/game.py
Normal file
|
|
@ -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()
|
||||
0
src/dnd_dm_toolkit/core/event_bus.py
Normal file
0
src/dnd_dm_toolkit/core/event_bus.py
Normal file
0
src/dnd_dm_toolkit/core/hooks.py
Normal file
0
src/dnd_dm_toolkit/core/hooks.py
Normal file
17
src/dnd_dm_toolkit/core/sentinel.py
Normal file
17
src/dnd_dm_toolkit/core/sentinel.py
Normal file
|
|
@ -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")
|
||||
0
src/dnd_dm_toolkit/core/stuctures/__init__.py
Normal file
0
src/dnd_dm_toolkit/core/stuctures/__init__.py
Normal file
204
src/dnd_dm_toolkit/core/stuctures/character.py
Normal file
204
src/dnd_dm_toolkit/core/stuctures/character.py
Normal file
|
|
@ -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
|
||||
72
src/dnd_dm_toolkit/core/stuctures/event.py
Normal file
72
src/dnd_dm_toolkit/core/stuctures/event.py
Normal file
|
|
@ -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
|
||||
0
src/dnd_dm_toolkit/data/__init__.py
Normal file
0
src/dnd_dm_toolkit/data/__init__.py
Normal file
45
src/dnd_dm_toolkit/data/stats.py
Normal file
45
src/dnd_dm_toolkit/data/stats.py
Normal file
|
|
@ -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
|
||||
}
|
||||
0
src/dnd_dm_toolkit/entities/__init__.py
Normal file
0
src/dnd_dm_toolkit/entities/__init__.py
Normal file
152
src/dnd_dm_toolkit/entities/classes.py
Normal file
152
src/dnd_dm_toolkit/entities/classes.py
Normal file
|
|
@ -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
|
||||
],
|
||||
)
|
||||
0
src/dnd_dm_toolkit/entities/featues/__init__.py
Normal file
0
src/dnd_dm_toolkit/entities/featues/__init__.py
Normal file
4
src/dnd_dm_toolkit/entities/featues/func.py
Normal file
4
src/dnd_dm_toolkit/entities/featues/func.py
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
from ...core.stuctures.character import Featureable
|
||||
|
||||
def darkvision(entity: Featureable):
|
||||
pass
|
||||
6
src/dnd_dm_toolkit/entities/featues/objects.py
Normal file
6
src/dnd_dm_toolkit/entities/featues/objects.py
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
from ...core.enums.game import FeatureSource
|
||||
from ...core.stuctures.character import Feature
|
||||
|
||||
DARKVISION = Feature(
|
||||
|
||||
)
|
||||
160
src/dnd_dm_toolkit/entities/languages.py
Normal file
160
src/dnd_dm_toolkit/entities/languages.py
Normal file
|
|
@ -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
|
||||
]
|
||||
0
src/dnd_dm_toolkit/entities/races.py
Normal file
0
src/dnd_dm_toolkit/entities/races.py
Normal file
0
src/dnd_dm_toolkit/rules/__init__.py
Normal file
0
src/dnd_dm_toolkit/rules/__init__.py
Normal file
37
src/dnd_dm_toolkit/rules/core.py
Normal file
37
src/dnd_dm_toolkit/rules/core.py
Normal file
|
|
@ -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
|
||||
18
src/dnd_dm_toolkit/scrapper.sh
Executable file
18
src/dnd_dm_toolkit/scrapper.sh
Executable file
|
|
@ -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
|
||||
0
src/dnd_dm_toolkit/utils/__init__.py
Normal file
0
src/dnd_dm_toolkit/utils/__init__.py
Normal file
30
src/dnd_dm_toolkit/utils/languages.py
Normal file
30
src/dnd_dm_toolkit/utils/languages.py
Normal file
|
|
@ -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
|
||||
Loading…
Add table
Add a link
Reference in a new issue