312 lines
9.4 KiB
Markdown
312 lines
9.4 KiB
Markdown
# SessionZero Data System
|
|
|
|
The formula system is not implemented yet. The `formula` type is reserved in the type system, but it does nothing right now. Formula fields resolve to `0` until the formula system exists.
|
|
|
|
## Layout
|
|
|
|
Data is separated into 3 main chunks: systems, games, and datasets. Each of these work together but hold varying information.
|
|
|
|
A system owns its datasets. A dataset always belongs to a system, even if the dataset folder gets pulled out, archived, and installed somewhere else later.
|
|
|
|
A game installs exactly one system. That system's datasets are what get installed into the game.
|
|
|
|
The flow goes as such:
|
|
|
|
1. Create a System
|
|
- Define a character template
|
|
- Define data templates
|
|
2. Create a Dataset (owned by a System)
|
|
- Reference the owning system for available templates
|
|
- Create data instances based on the available templates
|
|
3. Create a Game
|
|
- Install a System
|
|
- Install the datasets that belong to that system
|
|
- Install plugins (future feature, not covered in this spec)
|
|
- Create a character from the system's character template
|
|
|
|
## File Specifications
|
|
|
|
### Namespaces and IDs
|
|
|
|
There are 3 "namespaces" in the sz data pipeline: system, dataset, and data group in that order.
|
|
|
|
Author is just metadata, so it's not part of the ID chain. Author lives as a field inside `system.sz` and `dataset.sz`.
|
|
|
|
Namespaces are accessed with a trailing `:`, ex. the weapon data group inside the `szcore_data` dataset inside the `szcore` system is `szcore:szcore_data:weapon`.
|
|
|
|
IDs for top level files (`system.sz`, `dataset.sz`) come from the `id` field declared inside that file, not the folder name. Folder naming (`author.system_name/`) is a human-readable convention for organizing files on disk, it has no effect on ID resolution.
|
|
|
|
`.szt` and `.szo` files are identified by their file name.
|
|
|
|
Scope rules:
|
|
- A `system_id` is unique across everything installed locally.
|
|
- A `dataset_id` is unique within its owning system. Two different systems can each have a dataset called `homebrew` with no conflict, since the system segment disambiguates them.
|
|
- A `data group` (folder name/template name) is unique within its system (for templates) or within its dataset (for data, since it mirrors the data group it belongs to).
|
|
|
|
Full resolution path example:
|
|
|
|
A data instance in the `szcore_data` dataset (owned by the `szcore` system), inside the `weapon/` folder, named `sword.szo`, resolves as:
|
|
|
|
`szcore:szcore_data:weapon:sword`
|
|
|
|
`dataset.sz` carries a `system_version` field. It pins the dataset to a specific system version. It's not enforced yet, but it's there so datasets authored now don't end up missing a version pin once versioning is enforced.
|
|
|
|
Systems and datasets only exist inside a game right now. A local library that holds systems and datasets outside of any specific game (so they don't need to be duplicated across games) is planned, but not part of this spec yet. When it's added, it will keep the same `system_id:dataset_id` shape, so nothing needs to be re-identified when it moves from library to installed-in-a-game.
|
|
|
|
### Fields
|
|
|
|
A field is found within a data object, defined by a template.
|
|
|
|
Types: `text`, `number`, `boolean`, `option`, `list`, `formula`.
|
|
|
|
Every type has an enforced default value if one isn't set on the template. If the template author doesn't set `default_value`, it falls back to the type's zero-value:
|
|
- `text` -> `""`
|
|
- `number` -> `0`
|
|
- `boolean` -> `false`
|
|
- `option` -> unset / no selection
|
|
- `list` -> empty list
|
|
|
|
`formula` fields cannot have a `default_value`, since they're computed, not stored. They resolve to `0` until the formula system is implemented.
|
|
|
|
### Resolution & Validation Behavior
|
|
|
|
Rules for what happens when a dataset's data doesn't cleanly match the currently installed system's templates.
|
|
|
|
- **data group has no matching template in the system** (ex. a dataset has an `armor/` folder but the installed system has no `armor.szt`): a warning is logged, and that group's data is skipped.
|
|
- **Instance has a field the template no longer defines**: the field is stripped on load.
|
|
- **Template has a required field the instance doesn't define** (ex. a field got added to the template after the instance was authored): the default value is loaded in at runtime. This does not write back into the `.szo` file, the file on disk stays as-is until something explicitly saves it.
|
|
|
|
## System
|
|
|
|
Holds:
|
|
- Character Template
|
|
- Data templates
|
|
|
|
The character template is different from a regular data template. There's only ever one per system, it lives at the system root (not inside `templates/`), and datasets cannot use it. It's only ever used to create a character inside a game.
|
|
|
|
Example Directory Structure:
|
|
```text
|
|
author.system_name/
|
|
- system.sz
|
|
- icon.png
|
|
- character_template.szt
|
|
- templates/
|
|
- weapon.szt
|
|
```
|
|
|
|
Example `system.sz` file
|
|
```toml
|
|
id = "szcore"
|
|
display_name = "SZ Core System"
|
|
version = "1.0"
|
|
author = "sz"
|
|
description = """
|
|
A simple core system to base others off of
|
|
"""
|
|
```
|
|
|
|
Example `character_template.szt` file
|
|
```toml
|
|
template_type = "character"
|
|
author = "sz"
|
|
|
|
[fields.name]
|
|
type = "text"
|
|
required = true
|
|
default_value = "Character 1"
|
|
|
|
[fields.player_name]
|
|
type = "text"
|
|
required = false
|
|
|
|
[fields.level]
|
|
type = "number"
|
|
required = true
|
|
default_value = 1
|
|
|
|
[fields.race]
|
|
type = "option"
|
|
required = true
|
|
option_type = "data"
|
|
data_source = ["races"]
|
|
|
|
[fields.class]
|
|
type = "option"
|
|
option_type = "text"
|
|
data_source = ["ranger", "fighter", "wizard"]
|
|
required = true
|
|
|
|
[fields.status.is_dead]
|
|
type = "boolean"
|
|
required = true
|
|
default_value = false
|
|
|
|
[fields.stats.str]
|
|
type = "number"
|
|
required = true
|
|
default_value = 0
|
|
|
|
[fields.stats.modifiers.str]
|
|
type = "formula"
|
|
formula = '(stats.str - 10) / 2'
|
|
|
|
[fields.inventory]
|
|
type = "list"
|
|
list_type = "data" # if a list type is `data`, data_source becomes a list of data groups the list can pull from. otherwise use `number` or `text` for literal values
|
|
data_source = ["items", "weapons"]
|
|
|
|
[fields.spells]
|
|
type = "list"
|
|
list_type = "text"
|
|
data_source = ["fireball", "mage_hand"]
|
|
|
|
[fields.equipped_weapon]
|
|
type = "option"
|
|
option_type = "data"
|
|
data_source = ["weapons"]
|
|
|
|
[fields.stats.damage] # NOTE: Formulas are not implemented yet so this will resolve to a 0
|
|
type = "formula"
|
|
formula = 'equipped_weapon.calculated_damage'
|
|
```
|
|
|
|
Example data template file `weapon.szt`
|
|
```toml
|
|
# The data type eg. "item" is pulled from the file name, and can be anything
|
|
template_type = "data"
|
|
author = "sz"
|
|
|
|
[fields.id]
|
|
type = "text"
|
|
required = true
|
|
|
|
[fields.display_name]
|
|
type = "text"
|
|
required = true
|
|
|
|
[fields.value]
|
|
type = "number"
|
|
required = true
|
|
default_value = 1
|
|
|
|
[fields.base_damage]
|
|
type = "number"
|
|
required = true
|
|
default_value = 1
|
|
|
|
[fields.skill]
|
|
type = "text"
|
|
required = true
|
|
default_value = "str"
|
|
|
|
[fields.calculated_damage] # NOTE: Formulas are not implemented yet so this will resolve to a 0
|
|
type = "formula"
|
|
formula = 'base_damage + $PLAYER.stats.modifiers.{skill}'
|
|
# TODO: syntax for this still needs to be figured out.
|
|
# $PLAYER is a special variable that gets the current character of the owning player within a session.
|
|
# the local `skill` variable needs to be evaluated as a string value (so it looks for `$PLAYER.stats.modifiers.str` as a number).
|
|
# not sure about the {} syntax, may need to rework the formula type entirely.
|
|
```
|
|
|
|
## Datasets
|
|
|
|
Holds:
|
|
- Reference to the owning System and its templates
|
|
- Data Instances
|
|
|
|
Example Directory structure
|
|
```text
|
|
author.dataset_name/
|
|
- dataset.sz
|
|
- icons/
|
|
- dataset.png
|
|
- weapon/
|
|
- sword.png
|
|
- data/
|
|
- weapon/
|
|
- sword.szo
|
|
```
|
|
|
|
Example `dataset.sz` file
|
|
```toml
|
|
id = "szcore_data"
|
|
system_id = "szcore"
|
|
system_version = "1.0"
|
|
author = "sz"
|
|
```
|
|
|
|
Example data instance file `sword.szo` (uses the `weapon` template from the owning system)
|
|
```toml
|
|
author = "sz"
|
|
|
|
[fields.id]
|
|
value = "sword"
|
|
|
|
[fields.display_name]
|
|
value = "Sword"
|
|
|
|
[fields.value]
|
|
value = 100
|
|
|
|
[fields.base_damage]
|
|
value = 10
|
|
|
|
[fields.skill]
|
|
value = "str"
|
|
|
|
# this field is calculated based on the template's formula. resolves to 0 until the formula system exists.
|
|
[fields.calculated_damage]
|
|
value = 0
|
|
```
|
|
|
|
## Games
|
|
|
|
The Game type glues everything together. A game installs exactly one system. Datasets live nested under that system, since they can't exist independent of it.
|
|
|
|
Holds:
|
|
- System
|
|
- Datasets
|
|
- Plugins
|
|
- Player Data
|
|
- Character Instances
|
|
- Gamestate/Data
|
|
|
|
Example directory structure:
|
|
```text
|
|
game_name/
|
|
- game.sz
|
|
- system/
|
|
- system.sz
|
|
- datasets/
|
|
- dataset_a/
|
|
- dataset_b/
|
|
- plugins/
|
|
- (plugins will be defined in the future)
|
|
- player_data/
|
|
- player_name/
|
|
- player.sz
|
|
- characters/
|
|
- character1.szo // An actual Character instance using the System's template
|
|
- game_data/
|
|
- (various game related data...)
|
|
```
|
|
|
|
Example `game.sz` file
|
|
```toml
|
|
id = "my_game"
|
|
owner = "my_username" # The username that owns this game instance, used for online games in the future but does nothing yet.
|
|
system_id = "szcore"
|
|
system_version = "1.0" # Does nothing yet.
|
|
game_type = "local" # Does nothing yet, but the options are: local, online.
|
|
```
|
|
|
|
## Formula System
|
|
|
|
Not implemented yet. The `formula` type exists in the type system and templates can be authored with formula fields, but nothing evaluates them right now, they resolve to `0`.
|
|
|
|
Todo list for formulas:
|
|
- Scope model: what variables are reachable inside a formula (`self`, `$PLAYER`, others?)
|
|
- Grammar: real expression parser vs simple dot-path substitution
|
|
- Cycle detection between dependent formula fields
|
|
- The `$PLAYER.stats.modifiers.{skill}` interpolation syntax used in `weapon.szt` above
|