dndwright 0.2.0__py3-none-any.whl
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.
- dndwright/__init__.py +69 -0
- dndwright/content/__init__.py +56 -0
- dndwright/content/classes.json +147 -0
- dndwright/content/creatures.json +334 -0
- dndwright/content/generate.py +87 -0
- dndwright/content/magic_items.json +1915 -0
- dndwright/content/species.json +111 -0
- dndwright/ontology/__init__.py +25 -0
- dndwright/ontology/dnd.yaml +141 -0
- dndwright/ontology/loader.py +104 -0
- dndwright/rules/__init__.py +35 -0
- dndwright/rules/adapters.py +691 -0
- dndwright/rules/assembler.py +377 -0
- dndwright/rules/character_evaluator.py +248 -0
- dndwright/rules/components.py +654 -0
- dndwright/rules/dnd_5e_2024.py +606 -0
- dndwright/rules/evaluator.py +217 -0
- dndwright/rules/lookup_tables.py +501 -0
- dndwright/rules/operations.py +412 -0
- dndwright/rules/schema.py +69 -0
- dndwright/rules/theme_scaling.py +207 -0
- dndwright-0.2.0.dist-info/METADATA +101 -0
- dndwright-0.2.0.dist-info/RECORD +26 -0
- dndwright-0.2.0.dist-info/WHEEL +4 -0
- dndwright-0.2.0.dist-info/licenses/LICENSE +21 -0
- dndwright-0.2.0.dist-info/licenses/NOTICE +24 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""Generate original homebrew D&D content via a caller-provided LLM.
|
|
2
|
+
|
|
3
|
+
dndwright is LLM-agnostic: you pass a ``complete_json(prompt, system) -> dict``
|
|
4
|
+
callable that wraps *your* LLM (OpenAI, Anthropic, OpenRouter, a local model, …)
|
|
5
|
+
and returns the parsed JSON object. The prompts steer toward **original homebrew**
|
|
6
|
+
— novel names and mechanics, never official content — so the output is yours.
|
|
7
|
+
|
|
8
|
+
from dndwright.content import generate_library
|
|
9
|
+
|
|
10
|
+
def my_llm(prompt: str, system: str | None = None) -> dict:
|
|
11
|
+
# call your model in JSON mode, return the parsed dict
|
|
12
|
+
...
|
|
13
|
+
|
|
14
|
+
library = generate_library(my_llm, classes=6, species=6, creatures=12)
|
|
15
|
+
# -> {"classes": [...], "species": [...], "creatures": [...]}
|
|
16
|
+
|
|
17
|
+
Output matches the bundled-content schema (and the dndwright Class/Species/Creature
|
|
18
|
+
ontology), so you can drop it straight into your own graph or library.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
from typing import Any, Callable
|
|
24
|
+
|
|
25
|
+
# (prompt, system) -> parsed JSON object. Sync; wrap an async client if needed.
|
|
26
|
+
JsonLLM = Callable[[str, "str | None"], dict[str, Any]]
|
|
27
|
+
|
|
28
|
+
SYSTEM = (
|
|
29
|
+
"You are a tabletop RPG homebrew designer. You invent ORIGINAL fantasy game "
|
|
30
|
+
"content — wholly novel names and mechanics. You NEVER reproduce official "
|
|
31
|
+
"Dungeons & Dragons content (no Fighter/Wizard/Rogue, no Elf/Dwarf/Orc, no "
|
|
32
|
+
"Goblin/Beholder/Dragon, etc.). You answer only with the requested JSON."
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
_CLASSES_PROMPT = """Invent {n} ORIGINAL homebrew character classes for a 5e-style fantasy game — wholly novel concepts, not reskins of official classes. For each, output exactly:
|
|
36
|
+
{{"name": "<original class name>", "mechanics": {{"hit_die": "d6|d8|d10|d12", "primary_ability": "STR|DEX|CON|INT|WIS|CHA", "saving_throws": ["<ability>", "<ability>"], "spellcasting_type": "none|full|half|pact", "key_features": ["<3-5 signature features, each a short phrase>"]}}, "narrative": {{"description": "<2-3 sentence concept>", "role": "<party role>", "flavor": "<evocative one-liner>"}}}}
|
|
37
|
+
Respond with JSON: {{"classes": [ ... {n} items ... ]}}. Original names only — invent them."""
|
|
38
|
+
|
|
39
|
+
_SPECIES_PROMPT = """Invent {n} ORIGINAL homebrew playable ancestries/species for a 5e-style fantasy game — wholly novel, not elves/dwarves/orcs/halflings/etc. For each, output exactly:
|
|
40
|
+
{{"name": "<original species name>", "mechanics": {{"size": "Small|Medium", "speed": 30, "ability_bonuses": "<e.g. +2 to one, +1 to another, or 'flexible +2/+1'>", "traits": ["<3-4 species traits, short phrases>"]}}, "narrative": {{"description": "<2-3 sentence concept>", "flavor": "<one-liner>"}}}}
|
|
41
|
+
Respond with JSON: {{"species": [ ... {n} items ... ]}}. Original names only — invent them."""
|
|
42
|
+
|
|
43
|
+
_CREATURES_PROMPT = """Invent {n} ORIGINAL homebrew creatures for a 5e-style fantasy game — wholly novel, not goblins/dragons/beholders/owlbears/etc. Spread challenge ratings from 1/8 up to about 8. For each, output exactly:
|
|
44
|
+
{{"name": "<original creature name>", "size": "Tiny|Small|Medium|Large|Huge|Gargantuan", "creature_type": "aberration|beast|celestial|construct|dragon|elemental|fey|fiend|giant|humanoid|monstrosity|ooze|plant|undead", "alignment": "<e.g. chaotic evil>", "cr": "<1/8|1/4|1/2|1|2|3|...>", "hp": <integer>, "ac": <integer>, "speed": "<e.g. '30 ft.' or '30 ft., fly 60 ft.'>", "stat_block": {{"abilities": {{"str": <int>, "dex": <int>, "con": <int>, "int": <int>, "wis": <int>, "cha": <int>}}, "actions": ["<2-4 actions, short phrases>"], "traits": ["<0-3 trait phrases>"]}}}}
|
|
45
|
+
Respond with JSON: {{"creatures": [ ... {n} items ... ]}}. Original names only — invent them.{avoid}"""
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _gen(llm: JsonLLM, prompt: str, key: str) -> list[dict]:
|
|
49
|
+
result = llm(prompt, SYSTEM)
|
|
50
|
+
items = result.get(key, []) if isinstance(result, dict) else []
|
|
51
|
+
return [i for i in items if isinstance(i, dict)]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def generate_classes(llm: JsonLLM, n: int = 6) -> list[dict]:
|
|
55
|
+
"""Generate ``n`` original homebrew classes."""
|
|
56
|
+
return _gen(llm, _CLASSES_PROMPT.format(n=n), "classes")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def generate_species(llm: JsonLLM, n: int = 6) -> list[dict]:
|
|
60
|
+
"""Generate ``n`` original homebrew species."""
|
|
61
|
+
return _gen(llm, _SPECIES_PROMPT.format(n=n), "species")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def generate_creatures(llm: JsonLLM, n: int = 6) -> list[dict]:
|
|
65
|
+
"""Generate ``n`` original homebrew creatures (in batches, avoiding repeats)."""
|
|
66
|
+
creatures: list[dict] = []
|
|
67
|
+
remaining = n
|
|
68
|
+
while remaining > 0:
|
|
69
|
+
batch = min(6, remaining)
|
|
70
|
+
avoid = ""
|
|
71
|
+
if creatures:
|
|
72
|
+
names = ", ".join(c.get("name", "") for c in creatures)
|
|
73
|
+
avoid = f" Do NOT repeat any of these already-made creatures: {names}."
|
|
74
|
+
creatures += _gen(llm, _CREATURES_PROMPT.format(n=batch, avoid=avoid), "creatures")
|
|
75
|
+
remaining -= batch
|
|
76
|
+
return creatures
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def generate_library(
|
|
80
|
+
llm: JsonLLM, classes: int = 6, species: int = 6, creatures: int = 12
|
|
81
|
+
) -> dict[str, list[dict]]:
|
|
82
|
+
"""Generate a full starter library: classes + species + creatures."""
|
|
83
|
+
return {
|
|
84
|
+
"classes": generate_classes(llm, classes),
|
|
85
|
+
"species": generate_species(llm, species),
|
|
86
|
+
"creatures": generate_creatures(llm, creatures),
|
|
87
|
+
}
|