adding sources readme
This commit is contained in:
parent
92a493c131
commit
f8395e7c92
165
dnd_item/sources/README.md
Normal file
165
dnd_item/sources/README.md
Normal file
|
@ -0,0 +1,165 @@
|
||||||
|
# Data Sources
|
||||||
|
|
||||||
|
This directory contains a series of data source files for item generators. The
|
||||||
|
format is used by the
|
||||||
|
[random_sets](https://github.com/evilchili/random-sets/tree/main) package to
|
||||||
|
create DataSource objects that make it easy to generate and combine randomized
|
||||||
|
sets with tunable frequency distributions.
|
||||||
|
|
||||||
|
* `magic_damage_types.yaml`: types of magical damage that can be applied to weapons
|
||||||
|
* `properties_base.yaml`: The base properties of mundane items ("light", "thrown," etc)
|
||||||
|
* `rarity.yaml`: The levels of rarity in D&D, with frequency distributions by challenge rating
|
||||||
|
* `weapons.yaml`: The basic weapon types (maul, shortsword, halbred, etc)
|
||||||
|
|
||||||
|
Besides `properties_base.yaml`, there are several files named `properties_[rarity].yaml`; these
|
||||||
|
sources define what properties can be applied to items of each rarity. For example, in `properties_rare.yaml` you will find a definition for +2 weapons, but +3 weapons are only in `properties_very_rare.yaml`.
|
||||||
|
|
||||||
|
## Data Source File Schema
|
||||||
|
|
||||||
|
Let's look at a simple example, from `rarity.yaml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
metadata:
|
||||||
|
headers:
|
||||||
|
- rarity
|
||||||
|
- sort_order
|
||||||
|
frequencies:
|
||||||
|
default:
|
||||||
|
common: 1.0
|
||||||
|
uncommon: 1.0
|
||||||
|
rare: 1.0
|
||||||
|
very rare: 1.0
|
||||||
|
legendary: 1.0
|
||||||
|
common:
|
||||||
|
- 0
|
||||||
|
uncommon:
|
||||||
|
- 1
|
||||||
|
rare:
|
||||||
|
- 2
|
||||||
|
very rare:
|
||||||
|
- 3
|
||||||
|
legendary:
|
||||||
|
- 4
|
||||||
|
```
|
||||||
|
|
||||||
|
The first block of the DataSource, `metadata`, contains information about the data. It may have two members, `headers` and `frequencies`. Everything after the `metadata` block is the *data set*.
|
||||||
|
|
||||||
|
### Headers
|
||||||
|
|
||||||
|
Headers are applied sequentially to the members of the data when the data set is interpreted as a
|
||||||
|
table, a list of dictionaries, and so on. A DataSource instance populated with this example would yield the following data structure:
|
||||||
|
|
||||||
|
```python
|
||||||
|
[
|
||||||
|
{'rarity': 'common', 'sort_order': 0}},
|
||||||
|
{'rarity': 'uncommon, 'sort_order': 1}},
|
||||||
|
{'rarity': 'rare, 'sort_order': 2}},
|
||||||
|
{'rarity': 'very rare, 'sort_order': 3}},
|
||||||
|
{'rarity': 'legendary, 'sort_order': 4}},
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that each member of the data set creates its own entry in the list.
|
||||||
|
|
||||||
|
This mapping is recursive, such that a member in the data set may contain an arbitrarily-deep structure of arrays and collections; they will be flattened into a serial list and headers applied to each, although this level of complexity is not generally needed for Item properties in this package.
|
||||||
|
|
||||||
|
### Frequencies
|
||||||
|
|
||||||
|
The frequencies collection defines one or more mappings of frequency distrubitions, from 0.0 to 1.0, for each member in the data set. The `default` data set in our example applies an equal weighting to all members in the set, meaning that by default all rarities defined here are equally likely to be
|
||||||
|
chosen when a random item is generated.
|
||||||
|
|
||||||
|
The full `rarity.yaml` defines several distrubtions organized by challenge rating, making it
|
||||||
|
impossible to generate legendary items for low-level parties; at higher levels, common items become paradoxically rare.
|
||||||
|
|
||||||
|
### Data Set
|
||||||
|
|
||||||
|
Everything after the metadata block is the data set; each collection represents a single entry in the set of possible selections. When `DataSource.random()` is called, the return value will be one of these collections, mediated by the specified frequencey distribution.
|
||||||
|
|
||||||
|
## Template Strings
|
||||||
|
|
||||||
|
Members of the data set can include python-style template strings. These strings are processed when
|
||||||
|
an item is generated, and can refer to other properties on the item, the base item properties, or even
|
||||||
|
the member's own properies. Here's an example, from `properties_rare.yaml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
enchanted:
|
||||||
|
- '{enchantment.nouns}'
|
||||||
|
- '{enchantment.adjectives}'
|
||||||
|
- 'Attacks made with this magical weapon do an extra {this.damage} {this.damage_type} damage.'
|
||||||
|
- '{enchantment.damage_type}'
|
||||||
|
- 1d6
|
||||||
|
- 0
|
||||||
|
- weapon
|
||||||
|
```
|
||||||
|
|
||||||
|
The `enchanted` property adds magical damage to a weapon. Enchantments are not defined in this file,
|
||||||
|
though; they are defined in `magic_damage_types.yaml` and look like this:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
fire:
|
||||||
|
- 'flames, fire'
|
||||||
|
- 'flaming, burning'
|
||||||
|
```
|
||||||
|
|
||||||
|
When an item is assigned the `enchanted` property, an `enchantment` property is added automatically. The template strings in the data are then resolved using the values from that property. So the nouns and adjectives of the `enchanted` property may resolve to `"flames, fire"` and '`"flames, burning"`, respectively.
|
||||||
|
|
||||||
|
Remember that the keyword attributes in the template strings are defined by the headers of the data source; this all works because `magic_damage_types.yaml` defines includes the "nouns" and "adjectives" headeres.
|
||||||
|
|
||||||
|
### Base Properties
|
||||||
|
|
||||||
|
A template string without a dotted attribute will refer to an item's basic properties. These basic properties are defined by each ItemGenerator class; for Weapons, basic properties are defined in `weapons.yaml` and include *name*, *category*, *type*, *weight*, *damage_type*, *damage*, *range*, *reload*, *value*, and *properties*. Here's an excerpt:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
metadata:
|
||||||
|
headers:
|
||||||
|
- name
|
||||||
|
- category
|
||||||
|
- type
|
||||||
|
- weight
|
||||||
|
- damage_type
|
||||||
|
- damage
|
||||||
|
- range
|
||||||
|
- reload
|
||||||
|
- value
|
||||||
|
- properties
|
||||||
|
Battleaxe:
|
||||||
|
- martial
|
||||||
|
- Martial
|
||||||
|
- '4'
|
||||||
|
- Slashing
|
||||||
|
- 1d8
|
||||||
|
- 5
|
||||||
|
- ''
|
||||||
|
- '1000'
|
||||||
|
- versatile
|
||||||
|
```
|
||||||
|
|
||||||
|
Thus, `{damage_type}` will resolve to "Slashing" in any template string on any property of a Battleaxe.
|
||||||
|
|
||||||
|
### The "this" Keyword
|
||||||
|
|
||||||
|
The `enchanted` member also uses the keyword `this`. When this property is applied to an item, `this`
|
||||||
|
will refer to members of the `enchanted` collection. so `{this.damage}` becomes `1d6`.
|
||||||
|
|
||||||
|
`this` can also reference other template strings! In our example, `{this.damage_type}` will resolve first to `{enchantment.damage_type}`, which will in turn resolve to `fire` (because `magic_damage_types.yaml` defines the "damage_type" header).
|
||||||
|
|
||||||
|
Putting it all together, a full substitution of values will yield a data set member that looks like this:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
enchanted:
|
||||||
|
- 'flames, fire'
|
||||||
|
- 'flaming, burning'
|
||||||
|
- 'Attacks made with this magical weapon do an extra 1d6 fire damage.'
|
||||||
|
- 'fire'
|
||||||
|
- 1d6
|
||||||
|
- 0
|
||||||
|
- weapon
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Reserved Keywords
|
||||||
|
|
||||||
|
The following keywords are reserved:
|
||||||
|
|
||||||
|
`this`: Refers to the current data set member
|
||||||
|
`enchantment`: Refers to a random member of the `magic_damage_types.yaml` data set
|
|
@ -344,14 +344,14 @@ class ItemGenerator:
|
||||||
len(self.properties_by_rarity[rarity].members)
|
len(self.properties_by_rarity[rarity].members)
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_requirements(self, item) -> set:
|
def _get_requirements(self, item) -> set:
|
||||||
"""
|
"""
|
||||||
Step through all attributes of an object looking for template strings,
|
Step through all attributes of an object looking for template strings,
|
||||||
and return the unique set of attributes referenced in those template
|
and return the unique set of attributes referenced in those template
|
||||||
strings.
|
strings.
|
||||||
|
|
||||||
>>> props = dict(foo="{one}", bar=dict(baz="{one}", boz="{two.three}"))
|
>>> props = dict(foo="{one}", bar=dict(baz="{one}", boz="{two.three}"))
|
||||||
>>> ItemGenerator().get_requirements(props)
|
>>> ItemGenerator()._get_requirements(props)
|
||||||
{'one', 'two'}
|
{'one', 'two'}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -432,7 +432,7 @@ class ItemGenerator:
|
||||||
# Look for template strings that reference item attributes which do not yet exist.
|
# Look for template strings that reference item attributes which do not yet exist.
|
||||||
# Add anything that is missing via a callback.
|
# Add anything that is missing via a callback.
|
||||||
predefined = list(item.keys()) + ["this", "_name"]
|
predefined = list(item.keys()) + ["this", "_name"]
|
||||||
for requirement in [r for r in self.get_requirements(item) if r not in predefined]:
|
for requirement in [r for r in self._get_requirements(item) if r not in predefined]:
|
||||||
try:
|
try:
|
||||||
item[requirement] = getattr(self, f"get_{requirement}")(**item)
|
item[requirement] = getattr(self, f"get_{requirement}")(**item)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
|
|
Loading…
Reference in New Issue
Block a user