dnd5e-character-builder 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dnd5e_character_builder-0.1.0/LICENSE +21 -0
- dnd5e_character_builder-0.1.0/PKG-INFO +181 -0
- dnd5e_character_builder-0.1.0/README.md +159 -0
- dnd5e_character_builder-0.1.0/pyproject.toml +84 -0
- dnd5e_character_builder-0.1.0/pyproject.toml.orig +61 -0
- dnd5e_character_builder-0.1.0/src/dnd5e_character_builder/__init__.py +34 -0
- dnd5e_character_builder-0.1.0/src/dnd5e_character_builder/advance.py +208 -0
- dnd5e_character_builder-0.1.0/src/dnd5e_character_builder/build.py +323 -0
- dnd5e_character_builder-0.1.0/src/dnd5e_character_builder/check.py +380 -0
- dnd5e_character_builder-0.1.0/src/dnd5e_character_builder/content.py +453 -0
- dnd5e_character_builder-0.1.0/src/dnd5e_character_builder/data/5e-database/5e-SRD-Levels.json +8680 -0
- dnd5e_character_builder-0.1.0/src/dnd5e_character_builder/data/5e-database/5e-SRD-Spells.json +13514 -0
- dnd5e_character_builder-0.1.0/src/dnd5e_character_builder/data/5e-database/LICENSE.md +21 -0
- dnd5e_character_builder-0.1.0/src/dnd5e_character_builder/data/srd.yaml +40 -0
- dnd5e_character_builder-0.1.0/src/dnd5e_character_builder/py.typed +0 -0
- dnd5e_character_builder-0.1.0/src/dnd5e_character_builder/sheet.py +96 -0
- dnd5e_character_builder-0.1.0/src/dnd5e_character_builder/spells.py +294 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ben Strackany
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dnd5e-character-builder
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Build, level up and check D&D 2024 (SRD 5.2.1) character sheets, on top of the nat20 rules engine.
|
|
5
|
+
Keywords: dnd,5e,2024,srd,character-sheet,ttrpg
|
|
6
|
+
Author: Ben Strackany
|
|
7
|
+
Author-email: Ben Strackany <ben@developmentnow.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
License-File: src/dnd5e_character_builder/data/5e-database/LICENSE.md
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
13
|
+
Classifier: Topic :: Games/Entertainment :: Role-Playing
|
|
14
|
+
Classifier: Typing :: Typed
|
|
15
|
+
Requires-Dist: dnd5e-engine>=0.7,<0.8
|
|
16
|
+
Requires-Dist: dnd5e-srd-data>=0.7,<0.8
|
|
17
|
+
Requires-Dist: pydantic>=2.14.2
|
|
18
|
+
Requires-Dist: pyyaml>=6.0.3
|
|
19
|
+
Requires-Python: >=3.14
|
|
20
|
+
Project-URL: Repository, https://github.com/bstrackany/dnd5e-character-builder
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# dnd5e-character-builder
|
|
24
|
+
|
|
25
|
+
Build, level up and check D&D 2024 (SRD 5.2.1) character sheets in Python.
|
|
26
|
+
|
|
27
|
+
A sheet written by a person or a model is free text: "Rogue (Arcane Trickster)", "Shield — level 1, prepared".
|
|
28
|
+
This library reads it against the rules, lists every problem at once, writes in the numbers the rules derive, and says
|
|
29
|
+
what the character may still pick now or at the next level. It sits on [nat20](https://github.com/tapestria/nat20)'s
|
|
30
|
+
rules engine and SRD corpus; nat20 decides whether a build is constructible and derives its HP, AC, saves and slots.
|
|
31
|
+
This library adds the parts of a sheet nat20 takes as given: class spell lists, prepared and cantrip counts, wizard
|
|
32
|
+
spellbooks, feat slots, choice counts, skills and ability-score generation.
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
pip install dnd5e-character-builder
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Python 3.14 or later.
|
|
39
|
+
|
|
40
|
+
## Use
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
from dnd5e_character_builder import Feat, Sheet, check, fill, level_up, options
|
|
44
|
+
|
|
45
|
+
sheet = Sheet.model_validate({
|
|
46
|
+
'name': 'Ana',
|
|
47
|
+
'species': 'Human',
|
|
48
|
+
'class': 'Fighter',
|
|
49
|
+
'level': 4,
|
|
50
|
+
'subclass': 'Champion',
|
|
51
|
+
'background': 'Soldier',
|
|
52
|
+
'abilities': {'str': 17, 'dex': 15, 'con': 14, 'int': 8, 'wis': 12, 'cha': 10},
|
|
53
|
+
'skills': ['Athletics', 'Intimidation', 'Perception', 'Survival', 'Acrobatics'],
|
|
54
|
+
'feats': ['Savage Attacker', 'Alert', Feat(name='Grappler', level=4)],
|
|
55
|
+
'choices': ['Defense'],
|
|
56
|
+
'inventory': ['Chain Mail', 'Longsword', 'Shield'],
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
sheet = fill(sheet) # max_hp, ac, saving_throws and spell_slots from nat20
|
|
60
|
+
for issue in check(sheet): # every problem at once
|
|
61
|
+
print(issue.severity, issue.code, issue.message)
|
|
62
|
+
|
|
63
|
+
options(sheet) # every pick at this level (subclass, feature choices, feat slots, skills, spells) and what fills it
|
|
64
|
+
level_up(sheet, 5) # what level 5 gives automatically and which picks it makes due
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
`level_up` and `options` report; neither changes the sheet. Write the picks, then run `fill` and `check`.
|
|
68
|
+
|
|
69
|
+
`build_spec(sheet)` and `derive(sheet)` give nat20's `CharacterBuildSpec` and `DerivedSheet` for a sheet, for hosts
|
|
70
|
+
that run nat20 combat.
|
|
71
|
+
|
|
72
|
+
### The sheet
|
|
73
|
+
|
|
74
|
+
| Field | Holds |
|
|
75
|
+
|---|---|
|
|
76
|
+
| `species`, `class`, `subclass`, `background` | Names as written. A subclass in parentheses after the class is read too. |
|
|
77
|
+
| | A background the content does not know is a custom one: +2/+1 or +1/+1/+1 to any abilities, any two skills and any one origin feat. |
|
|
78
|
+
| `level` | 1-20, one class. |
|
|
79
|
+
| `abilities` | Final scores by `str` ... `cha`. |
|
|
80
|
+
| `skills`, `expertise` | Skill names; `"Stealth (expertise)"` in `skills` also counts as Expertise. |
|
|
81
|
+
| `feats` | `Feat(name, level)`. `level` is the class level whose Ability Score Improvement the feat took; leave it `None` for an origin feat. A bare string is a feat with no level. |
|
|
82
|
+
| `choices` | Class, subclass and species options by name: `"Archery"`, `"Agonizing Blast"`, `"Protector"`. |
|
|
83
|
+
| `spells` | Cantrips known and spells prepared. |
|
|
84
|
+
| `spellbook` | A wizard's level 1+ spells. |
|
|
85
|
+
| `magic_initiate` | One record per Magic Initiate: list, ability, two cantrips, one level 1 spell. |
|
|
86
|
+
| `max_hp`, `ac`, `spell_slots`, `saving_throws` | The sheet's numbers; `fill` replaces them. |
|
|
87
|
+
|
|
88
|
+
### Issues
|
|
89
|
+
|
|
90
|
+
Each `Issue` has a stable `code`, a `severity` (`error` breaks the rules, `warning` is likely wrong, `info` is a
|
|
91
|
+
note), the sheet `field`, a `message` and a `detail` dict.
|
|
92
|
+
|
|
93
|
+
| Code | Severity | Meaning |
|
|
94
|
+
|---|---|---|
|
|
95
|
+
| `build.rejected` | error | nat20 cannot construct the build; its reason is in `detail.reason` |
|
|
96
|
+
| `class.unknown`, `class.multiclass` | error | No class the content knows, or more than one (multiclass is not supported yet) |
|
|
97
|
+
| `species.unknown`, `species.missing` | error | |
|
|
98
|
+
| `subclass.unknown`, `subclass.missing` | error | Not in the content, or none chosen at the subclass level |
|
|
99
|
+
| `feat.unknown` | error | Not in the content |
|
|
100
|
+
| `feat.no_slot` | error | Nothing gives this feat: no Ability Score Improvement at that level, the slot is taken, or an origin feat beyond the background's and species' |
|
|
101
|
+
| `feat.level_missing` | info | A feat in an Ability Score Improvement without the level it was taken at; it is checked against the first free one |
|
|
102
|
+
| `feat.slot_empty` | warning | An Ability Score Improvement level with no feat recorded |
|
|
103
|
+
| `feat.missing` | warning | The background's feat (a custom background's chosen origin feat) is not on the sheet |
|
|
104
|
+
| `abilities.not_buildable` | warning | Not the standard array or a 27-point buy plus the background's increase and the Ability Score Improvements taken |
|
|
105
|
+
| `abilities.over_cap` | error | Above 20 (30 with an Epic Boon) |
|
|
106
|
+
| `saves.mismatch` | error / warning | Not the class's saving throws (warning when the sheet lists none) |
|
|
107
|
+
| `skills.unknown` | error | Not a skill |
|
|
108
|
+
| `skills.too_many`, `skills.too_few` | warning | More or fewer than background, class, species, subclass and feats give |
|
|
109
|
+
| `skills.granted_missing` | warning | A skill given outright is not on the sheet |
|
|
110
|
+
| `expertise.too_many`, `expertise.not_proficient` | error | |
|
|
111
|
+
| `choice.unknown`, `choice.too_many` | error | Not an option this build has, or more picks than its level allows |
|
|
112
|
+
| `choice.missing` | warning | Picks still due |
|
|
113
|
+
| `spell.unknown` | error | Not in the content |
|
|
114
|
+
| `spell.not_on_list` | error | Not on the class list and nothing else grants it |
|
|
115
|
+
| `spell.too_many_cantrips`, `spell.too_many_prepared` | error | Over the class table; granted spells do not count |
|
|
116
|
+
| `spell.above_slot` | error | Prepared above the highest spell slot |
|
|
117
|
+
| `spellbook.missing`, `spellbook.not_wizard_spell` | error | Prepared but not in the book; a book entry that is not a level 1+ wizard spell |
|
|
118
|
+
| `spellbook.too_few` | warning | Fewer than 6 + 2 per level after the first |
|
|
119
|
+
| `magic_initiate.not_on_list`, `magic_initiate.too_many` | error | |
|
|
120
|
+
| `magic_initiate.unrecorded` | info | A Magic Initiate whose picks are not recorded |
|
|
121
|
+
| `max_hp.mismatch`, `ac.mismatch`, `spell_slots.mismatch` | warning | The sheet's number differs from the derived one |
|
|
122
|
+
| `numbers.unchecked` | info | HP, AC and slots not compared because something the derivation needs is unknown |
|
|
123
|
+
|
|
124
|
+
Spells a character has without preparing them do not count against the class table: species and subclass grants,
|
|
125
|
+
spells a feature's own text grants ("You always have the Hunter's Mark spell prepared"), the Circle of the Land table
|
|
126
|
+
the sheet matches best, Mystic Arcanum, Magic Initiate, and the College of Lore's Magical Discoveries.
|
|
127
|
+
|
|
128
|
+
## Homebrew
|
|
129
|
+
|
|
130
|
+
`load_content(*paths)` layers YAML or JSON files (or folders of them) over the SRD; pass the result as
|
|
131
|
+
`content=` to any call. A file holds nat20-schema entries and this library's tables:
|
|
132
|
+
|
|
133
|
+
```yaml
|
|
134
|
+
subclasses: [...] # nat20 schema; also classes, species, backgrounds, feats, features, spells
|
|
135
|
+
spell_lists: # class -> spell level -> spells added to its list; a name with no entry is known by name and level
|
|
136
|
+
wizard: {1: [Chromatic Orb], 2: [Dragon's Breath]}
|
|
137
|
+
subclass_spells: # always-prepared spells by class level
|
|
138
|
+
oath-of-glory: {3: [Guiding Bolt, Heroism], 5: [Enhance Ability, Magic Weapon]}
|
|
139
|
+
subclass_spell_choices: # one table per option, as Circle of the Land
|
|
140
|
+
circle-of-the-reef: {shallows: {3: [Fog Cloud, Water Breathing]}}
|
|
141
|
+
class_tables: # cantrips and prepared per level, 20 each; a casting subclass also names progression and spell_list
|
|
142
|
+
eldritch-knight: {cantrips: [...], prepared: [...], progression: third, spell_list: wizard}
|
|
143
|
+
aliases:
|
|
144
|
+
subclasses: {glory: oath-of-glory}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
A path may also be a mapping of the same shape. `load_content(loader=...)` builds on your own nat20 `AssetLoader` instead of nat20's bundled corpus.
|
|
148
|
+
|
|
149
|
+
Loading refuses anything the library could not check, with a `ContentError` naming it: a spellcasting class without a
|
|
150
|
+
table or spell list, a subclass with a Spellcasting feature and no table, a subclass whose spells feature grants no
|
|
151
|
+
spells, a spell listed at the wrong level, an alias to nothing. Entries without nat20 provenance get a placeholder one
|
|
152
|
+
so they pass nat20's schema.
|
|
153
|
+
|
|
154
|
+
## Limits
|
|
155
|
+
|
|
156
|
+
Single class only; see [BACKLOG.md](BACKLOG.md) for the rest.
|
|
157
|
+
|
|
158
|
+
## Data and attribution
|
|
159
|
+
|
|
160
|
+
- Rules engine and SRD corpus: [nat20](https://github.com/tapestria/nat20) (`dnd5e-engine`, `dnd5e-srd-data`), MIT.
|
|
161
|
+
- Class spell lists and the Cantrips and Prepared Spells columns: `5e-SRD-Spells.json` and `5e-SRD-Levels.json` from
|
|
162
|
+
[5e-database](https://github.com/5e-bits/5e-database) (`src/2024/en`, commit `6f6299e`), MIT, vendored unchanged
|
|
163
|
+
in `src/dnd5e_character_builder/data/5e-database` with its licence. Two columns are corrected from the SRD in
|
|
164
|
+
`data/srd.yaml`.
|
|
165
|
+
- This work includes material from the System Reference Document 5.2.1 ("SRD 5.2.1") by Wizards of the Coast LLC,
|
|
166
|
+
available at <https://www.dndbeyond.com/srd>. The SRD 5.2.1 is licensed under the Creative Commons Attribution 4.0
|
|
167
|
+
International License, available at <https://creativecommons.org/licenses/by/4.0/legalcode>.
|
|
168
|
+
|
|
169
|
+
This library's code is MIT; see [LICENSE](LICENSE).
|
|
170
|
+
|
|
171
|
+
## Develop
|
|
172
|
+
|
|
173
|
+
```
|
|
174
|
+
uv sync
|
|
175
|
+
uv run ruff format --check src tests && uv run ruff check src tests && uv run mypy src tests && uv run pytest
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Release
|
|
179
|
+
|
|
180
|
+
Bump `version` in `pyproject.toml`, commit, then `git tag v<version> && git push origin v<version>`. The
|
|
181
|
+
`release.yml` workflow runs the checks and publishes to PyPI through Trusted Publishing.
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# dnd5e-character-builder
|
|
2
|
+
|
|
3
|
+
Build, level up and check D&D 2024 (SRD 5.2.1) character sheets in Python.
|
|
4
|
+
|
|
5
|
+
A sheet written by a person or a model is free text: "Rogue (Arcane Trickster)", "Shield — level 1, prepared".
|
|
6
|
+
This library reads it against the rules, lists every problem at once, writes in the numbers the rules derive, and says
|
|
7
|
+
what the character may still pick now or at the next level. It sits on [nat20](https://github.com/tapestria/nat20)'s
|
|
8
|
+
rules engine and SRD corpus; nat20 decides whether a build is constructible and derives its HP, AC, saves and slots.
|
|
9
|
+
This library adds the parts of a sheet nat20 takes as given: class spell lists, prepared and cantrip counts, wizard
|
|
10
|
+
spellbooks, feat slots, choice counts, skills and ability-score generation.
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
pip install dnd5e-character-builder
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Python 3.14 or later.
|
|
17
|
+
|
|
18
|
+
## Use
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
from dnd5e_character_builder import Feat, Sheet, check, fill, level_up, options
|
|
22
|
+
|
|
23
|
+
sheet = Sheet.model_validate({
|
|
24
|
+
'name': 'Ana',
|
|
25
|
+
'species': 'Human',
|
|
26
|
+
'class': 'Fighter',
|
|
27
|
+
'level': 4,
|
|
28
|
+
'subclass': 'Champion',
|
|
29
|
+
'background': 'Soldier',
|
|
30
|
+
'abilities': {'str': 17, 'dex': 15, 'con': 14, 'int': 8, 'wis': 12, 'cha': 10},
|
|
31
|
+
'skills': ['Athletics', 'Intimidation', 'Perception', 'Survival', 'Acrobatics'],
|
|
32
|
+
'feats': ['Savage Attacker', 'Alert', Feat(name='Grappler', level=4)],
|
|
33
|
+
'choices': ['Defense'],
|
|
34
|
+
'inventory': ['Chain Mail', 'Longsword', 'Shield'],
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
sheet = fill(sheet) # max_hp, ac, saving_throws and spell_slots from nat20
|
|
38
|
+
for issue in check(sheet): # every problem at once
|
|
39
|
+
print(issue.severity, issue.code, issue.message)
|
|
40
|
+
|
|
41
|
+
options(sheet) # every pick at this level (subclass, feature choices, feat slots, skills, spells) and what fills it
|
|
42
|
+
level_up(sheet, 5) # what level 5 gives automatically and which picks it makes due
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`level_up` and `options` report; neither changes the sheet. Write the picks, then run `fill` and `check`.
|
|
46
|
+
|
|
47
|
+
`build_spec(sheet)` and `derive(sheet)` give nat20's `CharacterBuildSpec` and `DerivedSheet` for a sheet, for hosts
|
|
48
|
+
that run nat20 combat.
|
|
49
|
+
|
|
50
|
+
### The sheet
|
|
51
|
+
|
|
52
|
+
| Field | Holds |
|
|
53
|
+
|---|---|
|
|
54
|
+
| `species`, `class`, `subclass`, `background` | Names as written. A subclass in parentheses after the class is read too. |
|
|
55
|
+
| | A background the content does not know is a custom one: +2/+1 or +1/+1/+1 to any abilities, any two skills and any one origin feat. |
|
|
56
|
+
| `level` | 1-20, one class. |
|
|
57
|
+
| `abilities` | Final scores by `str` ... `cha`. |
|
|
58
|
+
| `skills`, `expertise` | Skill names; `"Stealth (expertise)"` in `skills` also counts as Expertise. |
|
|
59
|
+
| `feats` | `Feat(name, level)`. `level` is the class level whose Ability Score Improvement the feat took; leave it `None` for an origin feat. A bare string is a feat with no level. |
|
|
60
|
+
| `choices` | Class, subclass and species options by name: `"Archery"`, `"Agonizing Blast"`, `"Protector"`. |
|
|
61
|
+
| `spells` | Cantrips known and spells prepared. |
|
|
62
|
+
| `spellbook` | A wizard's level 1+ spells. |
|
|
63
|
+
| `magic_initiate` | One record per Magic Initiate: list, ability, two cantrips, one level 1 spell. |
|
|
64
|
+
| `max_hp`, `ac`, `spell_slots`, `saving_throws` | The sheet's numbers; `fill` replaces them. |
|
|
65
|
+
|
|
66
|
+
### Issues
|
|
67
|
+
|
|
68
|
+
Each `Issue` has a stable `code`, a `severity` (`error` breaks the rules, `warning` is likely wrong, `info` is a
|
|
69
|
+
note), the sheet `field`, a `message` and a `detail` dict.
|
|
70
|
+
|
|
71
|
+
| Code | Severity | Meaning |
|
|
72
|
+
|---|---|---|
|
|
73
|
+
| `build.rejected` | error | nat20 cannot construct the build; its reason is in `detail.reason` |
|
|
74
|
+
| `class.unknown`, `class.multiclass` | error | No class the content knows, or more than one (multiclass is not supported yet) |
|
|
75
|
+
| `species.unknown`, `species.missing` | error | |
|
|
76
|
+
| `subclass.unknown`, `subclass.missing` | error | Not in the content, or none chosen at the subclass level |
|
|
77
|
+
| `feat.unknown` | error | Not in the content |
|
|
78
|
+
| `feat.no_slot` | error | Nothing gives this feat: no Ability Score Improvement at that level, the slot is taken, or an origin feat beyond the background's and species' |
|
|
79
|
+
| `feat.level_missing` | info | A feat in an Ability Score Improvement without the level it was taken at; it is checked against the first free one |
|
|
80
|
+
| `feat.slot_empty` | warning | An Ability Score Improvement level with no feat recorded |
|
|
81
|
+
| `feat.missing` | warning | The background's feat (a custom background's chosen origin feat) is not on the sheet |
|
|
82
|
+
| `abilities.not_buildable` | warning | Not the standard array or a 27-point buy plus the background's increase and the Ability Score Improvements taken |
|
|
83
|
+
| `abilities.over_cap` | error | Above 20 (30 with an Epic Boon) |
|
|
84
|
+
| `saves.mismatch` | error / warning | Not the class's saving throws (warning when the sheet lists none) |
|
|
85
|
+
| `skills.unknown` | error | Not a skill |
|
|
86
|
+
| `skills.too_many`, `skills.too_few` | warning | More or fewer than background, class, species, subclass and feats give |
|
|
87
|
+
| `skills.granted_missing` | warning | A skill given outright is not on the sheet |
|
|
88
|
+
| `expertise.too_many`, `expertise.not_proficient` | error | |
|
|
89
|
+
| `choice.unknown`, `choice.too_many` | error | Not an option this build has, or more picks than its level allows |
|
|
90
|
+
| `choice.missing` | warning | Picks still due |
|
|
91
|
+
| `spell.unknown` | error | Not in the content |
|
|
92
|
+
| `spell.not_on_list` | error | Not on the class list and nothing else grants it |
|
|
93
|
+
| `spell.too_many_cantrips`, `spell.too_many_prepared` | error | Over the class table; granted spells do not count |
|
|
94
|
+
| `spell.above_slot` | error | Prepared above the highest spell slot |
|
|
95
|
+
| `spellbook.missing`, `spellbook.not_wizard_spell` | error | Prepared but not in the book; a book entry that is not a level 1+ wizard spell |
|
|
96
|
+
| `spellbook.too_few` | warning | Fewer than 6 + 2 per level after the first |
|
|
97
|
+
| `magic_initiate.not_on_list`, `magic_initiate.too_many` | error | |
|
|
98
|
+
| `magic_initiate.unrecorded` | info | A Magic Initiate whose picks are not recorded |
|
|
99
|
+
| `max_hp.mismatch`, `ac.mismatch`, `spell_slots.mismatch` | warning | The sheet's number differs from the derived one |
|
|
100
|
+
| `numbers.unchecked` | info | HP, AC and slots not compared because something the derivation needs is unknown |
|
|
101
|
+
|
|
102
|
+
Spells a character has without preparing them do not count against the class table: species and subclass grants,
|
|
103
|
+
spells a feature's own text grants ("You always have the Hunter's Mark spell prepared"), the Circle of the Land table
|
|
104
|
+
the sheet matches best, Mystic Arcanum, Magic Initiate, and the College of Lore's Magical Discoveries.
|
|
105
|
+
|
|
106
|
+
## Homebrew
|
|
107
|
+
|
|
108
|
+
`load_content(*paths)` layers YAML or JSON files (or folders of them) over the SRD; pass the result as
|
|
109
|
+
`content=` to any call. A file holds nat20-schema entries and this library's tables:
|
|
110
|
+
|
|
111
|
+
```yaml
|
|
112
|
+
subclasses: [...] # nat20 schema; also classes, species, backgrounds, feats, features, spells
|
|
113
|
+
spell_lists: # class -> spell level -> spells added to its list; a name with no entry is known by name and level
|
|
114
|
+
wizard: {1: [Chromatic Orb], 2: [Dragon's Breath]}
|
|
115
|
+
subclass_spells: # always-prepared spells by class level
|
|
116
|
+
oath-of-glory: {3: [Guiding Bolt, Heroism], 5: [Enhance Ability, Magic Weapon]}
|
|
117
|
+
subclass_spell_choices: # one table per option, as Circle of the Land
|
|
118
|
+
circle-of-the-reef: {shallows: {3: [Fog Cloud, Water Breathing]}}
|
|
119
|
+
class_tables: # cantrips and prepared per level, 20 each; a casting subclass also names progression and spell_list
|
|
120
|
+
eldritch-knight: {cantrips: [...], prepared: [...], progression: third, spell_list: wizard}
|
|
121
|
+
aliases:
|
|
122
|
+
subclasses: {glory: oath-of-glory}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
A path may also be a mapping of the same shape. `load_content(loader=...)` builds on your own nat20 `AssetLoader` instead of nat20's bundled corpus.
|
|
126
|
+
|
|
127
|
+
Loading refuses anything the library could not check, with a `ContentError` naming it: a spellcasting class without a
|
|
128
|
+
table or spell list, a subclass with a Spellcasting feature and no table, a subclass whose spells feature grants no
|
|
129
|
+
spells, a spell listed at the wrong level, an alias to nothing. Entries without nat20 provenance get a placeholder one
|
|
130
|
+
so they pass nat20's schema.
|
|
131
|
+
|
|
132
|
+
## Limits
|
|
133
|
+
|
|
134
|
+
Single class only; see [BACKLOG.md](BACKLOG.md) for the rest.
|
|
135
|
+
|
|
136
|
+
## Data and attribution
|
|
137
|
+
|
|
138
|
+
- Rules engine and SRD corpus: [nat20](https://github.com/tapestria/nat20) (`dnd5e-engine`, `dnd5e-srd-data`), MIT.
|
|
139
|
+
- Class spell lists and the Cantrips and Prepared Spells columns: `5e-SRD-Spells.json` and `5e-SRD-Levels.json` from
|
|
140
|
+
[5e-database](https://github.com/5e-bits/5e-database) (`src/2024/en`, commit `6f6299e`), MIT, vendored unchanged
|
|
141
|
+
in `src/dnd5e_character_builder/data/5e-database` with its licence. Two columns are corrected from the SRD in
|
|
142
|
+
`data/srd.yaml`.
|
|
143
|
+
- This work includes material from the System Reference Document 5.2.1 ("SRD 5.2.1") by Wizards of the Coast LLC,
|
|
144
|
+
available at <https://www.dndbeyond.com/srd>. The SRD 5.2.1 is licensed under the Creative Commons Attribution 4.0
|
|
145
|
+
International License, available at <https://creativecommons.org/licenses/by/4.0/legalcode>.
|
|
146
|
+
|
|
147
|
+
This library's code is MIT; see [LICENSE](LICENSE).
|
|
148
|
+
|
|
149
|
+
## Develop
|
|
150
|
+
|
|
151
|
+
```
|
|
152
|
+
uv sync
|
|
153
|
+
uv run ruff format --check src tests && uv run ruff check src tests && uv run mypy src tests && uv run pytest
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Release
|
|
157
|
+
|
|
158
|
+
Bump `version` in `pyproject.toml`, commit, then `git tag v<version> && git push origin v<version>`. The
|
|
159
|
+
`release.yml` workflow runs the checks and publishes to PyPI through Trusted Publishing.
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "dnd5e-character-builder"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Build, level up and check D&D 2024 (SRD 5.2.1) character sheets, on top of the nat20 rules engine."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
license-files = [
|
|
8
|
+
"LICENSE",
|
|
9
|
+
"src/dnd5e_character_builder/data/5e-database/LICENSE.md",
|
|
10
|
+
]
|
|
11
|
+
keywords = [
|
|
12
|
+
"dnd",
|
|
13
|
+
"5e",
|
|
14
|
+
"2024",
|
|
15
|
+
"srd",
|
|
16
|
+
"character-sheet",
|
|
17
|
+
"ttrpg",
|
|
18
|
+
]
|
|
19
|
+
classifiers = [
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.14",
|
|
22
|
+
"Topic :: Games/Entertainment :: Role-Playing",
|
|
23
|
+
"Typing :: Typed",
|
|
24
|
+
]
|
|
25
|
+
requires-python = ">=3.14"
|
|
26
|
+
dependencies = [
|
|
27
|
+
"dnd5e-engine>=0.7,<0.8",
|
|
28
|
+
"dnd5e-srd-data>=0.7,<0.8",
|
|
29
|
+
"pydantic>=2.14.2",
|
|
30
|
+
"pyyaml>=6.0.3",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[[project.authors]]
|
|
34
|
+
name = "Ben Strackany"
|
|
35
|
+
email = "ben@developmentnow.com"
|
|
36
|
+
|
|
37
|
+
[project.urls]
|
|
38
|
+
Repository = "https://github.com/bstrackany/dnd5e-character-builder"
|
|
39
|
+
|
|
40
|
+
[build-system]
|
|
41
|
+
requires = ["uv_build>=0.11.26,<0.12.0"]
|
|
42
|
+
build-backend = "uv_build"
|
|
43
|
+
|
|
44
|
+
[dependency-groups]
|
|
45
|
+
dev = [
|
|
46
|
+
"mypy>=2.4.1",
|
|
47
|
+
"pytest>=9.2.0",
|
|
48
|
+
"ruff>=0.17.3",
|
|
49
|
+
"types-pyyaml>=6.0.12.20261030",
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
[tool.ruff]
|
|
53
|
+
line-length = 140
|
|
54
|
+
target-version = "py314"
|
|
55
|
+
|
|
56
|
+
[tool.ruff.lint]
|
|
57
|
+
select = [
|
|
58
|
+
"E",
|
|
59
|
+
"F",
|
|
60
|
+
"W",
|
|
61
|
+
"I",
|
|
62
|
+
"B",
|
|
63
|
+
"UP",
|
|
64
|
+
"SIM",
|
|
65
|
+
"RUF",
|
|
66
|
+
]
|
|
67
|
+
ignore = [
|
|
68
|
+
"E501",
|
|
69
|
+
"RUF001",
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
[tool.ruff.format]
|
|
73
|
+
quote-style = "single"
|
|
74
|
+
|
|
75
|
+
[tool.mypy]
|
|
76
|
+
strict = true
|
|
77
|
+
warn_unused_ignores = true
|
|
78
|
+
|
|
79
|
+
[[tool.mypy.overrides]]
|
|
80
|
+
module = ["d20.*"]
|
|
81
|
+
ignore_missing_imports = true
|
|
82
|
+
|
|
83
|
+
[tool.pytest.ini_options]
|
|
84
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "dnd5e-character-builder"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Build, level up and check D&D 2024 (SRD 5.2.1) character sheets, on top of the nat20 rules engine."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
license-files = ["LICENSE", "src/dnd5e_character_builder/data/5e-database/LICENSE.md"]
|
|
8
|
+
keywords = ["dnd", "5e", "2024", "srd", "character-sheet", "ttrpg"]
|
|
9
|
+
classifiers = [
|
|
10
|
+
"Programming Language :: Python :: 3",
|
|
11
|
+
"Programming Language :: Python :: 3.14",
|
|
12
|
+
"Topic :: Games/Entertainment :: Role-Playing",
|
|
13
|
+
"Typing :: Typed",
|
|
14
|
+
]
|
|
15
|
+
authors = [
|
|
16
|
+
{ name = "Ben Strackany", email = "ben@developmentnow.com" }
|
|
17
|
+
]
|
|
18
|
+
requires-python = ">=3.14"
|
|
19
|
+
dependencies = [
|
|
20
|
+
"dnd5e-engine>=0.7,<0.8",
|
|
21
|
+
"dnd5e-srd-data>=0.7,<0.8",
|
|
22
|
+
"pydantic>=2.14.2",
|
|
23
|
+
"pyyaml>=6.0.3",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[project.urls]
|
|
27
|
+
Repository = "https://github.com/bstrackany/dnd5e-character-builder"
|
|
28
|
+
|
|
29
|
+
[build-system]
|
|
30
|
+
requires = ["uv_build>=0.11.26,<0.12.0"]
|
|
31
|
+
build-backend = "uv_build"
|
|
32
|
+
|
|
33
|
+
[dependency-groups]
|
|
34
|
+
dev = [
|
|
35
|
+
"mypy>=2.4.1",
|
|
36
|
+
"pytest>=9.2.0",
|
|
37
|
+
"ruff>=0.17.3",
|
|
38
|
+
"types-pyyaml>=6.0.12.20261030",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
[tool.ruff]
|
|
42
|
+
line-length = 140
|
|
43
|
+
target-version = "py314"
|
|
44
|
+
|
|
45
|
+
[tool.ruff.lint]
|
|
46
|
+
select = ["E", "F", "W", "I", "B", "UP", "SIM", "RUF"]
|
|
47
|
+
ignore = ["E501", "RUF001"] # the formatter wraps code; long rule quotes and typographic dashes are deliberate
|
|
48
|
+
|
|
49
|
+
[tool.ruff.format]
|
|
50
|
+
quote-style = "single"
|
|
51
|
+
|
|
52
|
+
[tool.mypy]
|
|
53
|
+
strict = true
|
|
54
|
+
warn_unused_ignores = true
|
|
55
|
+
|
|
56
|
+
[[tool.mypy.overrides]]
|
|
57
|
+
module = ["d20.*"]
|
|
58
|
+
ignore_missing_imports = true
|
|
59
|
+
|
|
60
|
+
[tool.pytest.ini_options]
|
|
61
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Build, level up and check 2024 (SRD 5.2.1) character sheets, on top of the nat20 rules engine.
|
|
2
|
+
|
|
3
|
+
``check`` lists every problem with a sheet; ``fill`` writes in the numbers nat20 derives; ``options`` says what a sheet
|
|
4
|
+
may still pick; ``level_up`` reports what a new level gives and which picks it makes due. ``load_content`` layers
|
|
5
|
+
homebrew over the SRD.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from dnd5e_character_builder.advance import Choice, FeatSlot, Gained, LevelUp, Options, SpellOption, SpellOptions, level_up, options
|
|
9
|
+
from dnd5e_character_builder.check import build_spec, check, derive, fill
|
|
10
|
+
from dnd5e_character_builder.content import Content, ContentError, load_content
|
|
11
|
+
from dnd5e_character_builder.sheet import Feat, Issue, MagicInitiate, Sheet
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
'Choice',
|
|
15
|
+
'Content',
|
|
16
|
+
'ContentError',
|
|
17
|
+
'Feat',
|
|
18
|
+
'FeatSlot',
|
|
19
|
+
'Gained',
|
|
20
|
+
'Issue',
|
|
21
|
+
'LevelUp',
|
|
22
|
+
'MagicInitiate',
|
|
23
|
+
'Options',
|
|
24
|
+
'Sheet',
|
|
25
|
+
'SpellOption',
|
|
26
|
+
'SpellOptions',
|
|
27
|
+
'build_spec',
|
|
28
|
+
'check',
|
|
29
|
+
'derive',
|
|
30
|
+
'fill',
|
|
31
|
+
'level_up',
|
|
32
|
+
'load_content',
|
|
33
|
+
'options',
|
|
34
|
+
]
|