Reverting datapacks stuff again, I am feeling like my original plan for a small dsl-like format was the correct way to go, but combining that with the datapack archive structure for embedded images and other resources.
10 KiB
Proposed Datapack system
A datapack is a collection of files that are user-generated and are used for loading data into SesssionZero. They store two types of data: Asset files like images, and custom data types called datasets and templates. There are two main template types: character_template and session_template.
Psuedocode notes
- Not the final syntax or file type, rather a rough outline of the data structure
- All paths are relative to the datapack root
- Icon paths are relative to
datapack_root/assets/images - All GUIDs are in the format
00000000-0000-0000-0000-000000000000to comply with SQL GUID standards - Quoted strings inside of brackets are assumed to be the
idof the object (eg.[entry "longsword"], longsword is the id of the entry) - Ids must be unique within their scope
- All objects under the datapack must have unique ids, entries must have unique ids within their dataset, groups must have unique ids within their entry, and fields must have unique ids within their group, etc
Dependencies
- A datapack can have dependencies on other datapacks. These dependencies are defined in the datapack metadata file with an id to access the dependency.
- SessionTemplates can also list datapack dependencies in the same manor as the root datapack for the sake of adding additional content outside of the core datapack that the session_template is loacated. This means there will be cases where a datapack and a session template have the same accessor name for the dependency, meaning the DependenyResolver will most likely fall back to the main datapack for the dependency.
Special Types
- The
listtype can be used to reference a list of values from dataset(s) either by referencing dataset(s) by id or by providing a list of allowed dataset types (eg. "item"). - The
formulatype can be used to calculate a value based on other fields and/or values from datasets or otherwise. (eg.damage = 10 + (level * 2) + current_weapon.stats.base_damage) - The
referencetype can be used to reference another dataset entry by id, and holds the full path to the referenced dataset entry. (The guid of the datapack that the dataset is in, the dataset id, and the entry id). This is used for formualas, etc.
Datapack definition
A datapack is defined in a metadata file in the pack's root. It also can hold dependencies to other datapacks. This file contains the following fields in psuedocode:
[datapack]
guid = "00000000-0000-0000-0000-000000000000"
name = "My datapack"
description = "My datapack description"
version = "1.0.0"
author = "My name"
creation_date = "2021-01-01"
icon = "icons/my_icon.png"
# example of a dependency to another datapack, the id given here (in the brackets) is the accessor name for the datapack but does not have to be the same as the actual datapack id (for the case where there are multiple datapacks with the same name)
[datapack_dependency "other_datapack"]
guid = "00000000-0000-0000-0000-000000000000"
version = "1.0.0"
Dataset definition
A dataset is a collection of user-defined data (like items, weapons, npcs, etc). Each entry in a dataset has fields that can be optionally grouped into categories. (ex. a damage field under the stats group on an entry for a longsword). Dataset fields have a type, which can be a text, textbox, number, boolean, list, or formula type. The structure in psuedocode looks like this:
# dataset definition
[dataset "core_items"]
description = "Core items dataset"
icon = "icons/core_items.png"
version = "1.0.0"
# the type of the dataset (can be any lowercase string, this type is arbitrary)
dataset_type = "item"
[entry "longsword"]
description = "A longsword"
icon = "icons/items/longsword.png"
[group "stats"]
icon = "icons/stats.png"
description = "Stats group"
[field "damage"]
type = "number"
description = "Damage number"
value = 10
# ... other field types to be defined later ...
Templates
Templates are used to define the structure and provide fill-in-the-blank functionality for various types of data that will be instanced. Currently, there are two types of templates: character_template and session_template. Templates define the structure of the data, and provide a way to fill in the data. All templates will follow a similar structure of sections with fields and nested groups with fields inside of them. Fields have no stored value (unlike a dataset), but a default value can be provided. Examples of templates are shown below.
Character template definition
A character template is a collection of user-defined fields that are used to create a character (like a character sheet for Dungeons and Dragons). Psuedocode:
[character_template "core_character"]
description = "Core character template"
icon = "icons/core_character_template.png"
version = "1.0.0"
# example of a dataset dependency, the id given here (in the brackets) is the accessor name for the dataset but does not have to be the same as the actual dataset id.
[dataset_dependency "core_items"]
datapack_guid = "00000000-0000-0000-0000-000000000000"
dataset_id = "core_items"
version = "1.0.0"
[section "character_info"]
description = "Character info section"
icon = "icons/character_info.png"
[field "name"]
type = "text"
description = "Character name"
value = "My character"
[field "description"]
type = "textbox"
description = "Character description"
value = "My character description"
# --- Only one of the following attributes can be present in a list field ---
# Preset list of values
allowed_values = ["fighter", "rogue", "wizard"]
# List of datasets that can be used to fill in the list (ids must be listed in the dataset dependency section)
allowed_datasets = ["core_classes", "other_classes"]
# List of allowed dataset types (will pull entries from all datasets that have the given type from any source of dependency (datapack level, dataset level, session level))
allowed_dataset_types = ["classes"]
# --- End of list field attributes ---
[group "attributes"]
description = "Attributes group"
icon = "icons/attributes.png"
[field "level"]
type = "number"
description = "Character level"
value = 1
[field "class"]
type = "list"
description = "Character class"
value = "fighter"
[field "damage"]
type = "formula"
description = "Damage"
# this formula assumes that the `class` field has been filled with an entry from a dataset that has a `stats` group with a `base_damage` field, and the `current_weapon` field has been filled with an entry from a dataset that has a `stats` group with a `base_damage` field.
# the formula is evaluated using the `strength`, `level`, `current_weapon.stats.base_damage`, and `class.stats.base_damage` fields as variables
# if the formula is invalid the the field will default to 0 OR the default value if provided
formula = "strength + (level * 2) + class.stats.damage + current_weapon.stats.base_damage"
# optional default value for the field
default_value = 10
[field "armor_class"]
type = "number"
default_value = 10
description = "Armor class"
[field "strength"]
type = "number"
default_value = 10
description = "Strength"
[section "inventory]
description = "Inventory section"
icon = "icons/inventory.png"
[field "current_weapon"]
type = "list"
description = "Equipped weapon"
allowed_dataset_types = ["weapons"]
Session template definition
A session template is a collection of user-defined fields that are used to create a session (the actual game state). It can hold any data that is needed for the session (such as extra datapack dependencies), and can be used to create a session from a template. A session template also holds a direct reference to a character_template to use for character creation within a session, meaning it will need to be listed as a dependency. A session_template can also hold a list of external datapack dependendencies that will get injected at runtime to allow for extra content. Psuedocode:
[session_template "core_session"]
description = "Core session template"
icon = "icons/core_session_template.png"
version = "1.0.0"
[character_template_dependency "core_character"]
datapack_guid = "00000000-0000-0000-0000-000000000000"
template_id = "core_character"
[datapack_dependency "other_datapack"]
datapack_guid = "00000000-0000-0000-0000-000000000000"
version = "1.0.0"
Possible directory structure
Filenames and types are not final, just an example of the possible directory structure.
- my_datapack
- assets
- images
- icons
- my_icon.png
- ...
- ...
- datasets
- ...
- character_templates
- ...
- session_templates
- ...
- my_datapack.szpack
Possible implementations
Godot 4.5 Resources
Using Godot 4.5's resource system to store all data, along with the assets, packed into an archive.
Pros:
- Theoretically Easy to implement in Godot
- Can be made into a binary format (.res) for faster load times and storage size
- Godot resources are deeply integrated with the engine, so it understands them well with built-in loading and saving
Cons:
- Not a standard format
- Not as easily human-readable
- Not portable to other development environments outside Godot
- Not as extensible for an open-source project like SessionZero
JSON format
Using JSON to store all data, along with the assets, packed into an archive.
Pros:
- Standard format
- Human-readable
- Understood by most systems/languages
- Portable to other development environments
Cons:
- Not as easy to work with in Godot
- Not as fast to load
- Not as compact
- Could get complex to implement the custom features like dependencies and formulas
Custom text format / DSL
Using a custom text format or DSL to store all data, along with the assets, packed into an archive.
Pros:
- Extremely Human-readable
- Portable to other development environments
- Can be as specific as needed for the complexity of the data
- Can create data just by writing text instead of relying on the SesssionZero godot client application
- Very valuable for an open-source project like SessionZero as it can be well documented and understood by anyone
- Can more easily create datapack generators in other languages/frameworks as its a simple text format/dsl
Cons:
- More complex to implement (All features need to be implemented like parsing, etc)
- Not a standard format
- Will have to document everything as it is very specific to SessionZero