Updating szf spec.

This commit is contained in:
Chris Bell 2025-07-01 17:58:46 -05:00
parent 1713c38301
commit 224e2a3d9b
10 changed files with 265 additions and 877 deletions

View File

@ -8,7 +8,7 @@ namespace SessionZero.Data;
public class CharacterField
{
public string Name { get; set; } = string.Empty;
public string Type { get; set; } = string.Empty; // Store as string for writing (e.g., "text", "number")
public string Type { get; set; } = string.Empty; // Reverted to string
public object? Value { get; set; }
}
@ -107,7 +107,7 @@ public class Character : SzfObject
characterSection.Fields.Add(new CharacterField
{
Name = field.Name,
Type = field.Type, // SzfField.Type is expected to be a string (e.g., "text", "number")
Type = field.Type, // Assign directly as string
Value = field.Value
});
}
@ -147,7 +147,7 @@ public class Character : SzfObject
subSection.Fields.Add(new CharacterField
{
Name = field.Name,
Type = field.Type,
Type = field.Type, // Assign directly as string
Value = field.Value
});
}
@ -168,7 +168,7 @@ public class Character : SzfObject
characterSection.Fields.Add(new CharacterField
{
Name = field.Name,
Type = field.Type,
Type = field.Type, // Assign directly as string
Value = field.Value
});
}
@ -199,6 +199,9 @@ public class Character : SzfObject
return currentSection;
}
// Removed ParseDatasetFieldType and GetDatasetFieldTypeString as they are no longer needed here
public override void GenerateMetadata(SzfFieldWriter writer)
{
// Ensure common SzfObject metadata (Name, Version, Description, Author) is written first
@ -241,8 +244,8 @@ public class Character : SzfObject
// Write all fields within the current section
foreach (var field in section.Fields)
{
// Use the string representation of the type as required by SzfFieldWriter
writer.WriteField(field.Name, field.Type, field.Value);
// Use the new WriteField overload that does NOT include the type specifier
writer.WriteField(field.Name, field.Value);
}
// Recursively generate all subsections

View File

@ -79,8 +79,6 @@ public class Dataset : SzfObject
private DatasetField ConvertToDatasetField(SzfField szfField)
{
// You would typically parse more properties here like Description, IsRequired etc.
// For simplicity, only Name, Type and Value are mapped for now.
return new DatasetField
{
Name = szfField.Name,

View File

@ -1,5 +1,3 @@
// SzfFieldWriter.cs
using System.Text;
namespace SessionZero.Data;
@ -41,6 +39,30 @@ public class SzfFieldWriter
}
}
/// <summary>
/// Writes a field without a type specifier, as required for templated SZF files like Character.
/// </summary>
public void WriteField(string name, object? value)
{
if (!Options.IncludeEmptyFields && (value == null || string.IsNullOrEmpty(value.ToString())))
{
return; // Skip empty fields if option is set
}
// For templated files, the type is not explicitly written.
// We'll treat the value as text for formatting purposes if no type is provided.
var formattedValue = FormatFieldValue(value, "text");
if (Options.IndentFields)
{
Builder.AppendLine($"{Options.Indent}{name} = {formattedValue}");
}
else
{
Builder.AppendLine($"{name} = {formattedValue}");
}
}
/// <summary>
/// Writes a field with a DatasetFieldType.
/// </summary>

View File

@ -1,382 +1,270 @@
@page "/szf-parser-test"
@page "/szf"
@using SessionZero.Data
@using Microsoft.Extensions.Logging
@using SessionZero.Services
@using SessionZero.Models
@inject SzfParser SzfParser
@inject ILogger<SzfParser> Logger
@inject ILogger<SzfParseTest> Logger
@inject SzfGenerator SzfGenerator
@inject ISzfStorageService SzfStorageService
@inject ILocalStorageService LocalStorageService
<h1>SZF Parser and Generator Test Page</h1>
<h1 class="page-title">SZF Testing Page</h1>
<div class="test-container">
<div class="input-section">
<h2>SZF Input</h2>
<div>
<textarea id="szf-input" @bind="szfCode" rows="20"></textarea>
<div class="row">
<div class="col-md-6">
<h3>Input SZF</h3>
<textarea @bind="szfInputText" rows="15" class="form-control" placeholder="Paste your SZF content here..."></textarea>
<div class="mt-2">
<button class="btn btn-primary" @onclick="ParseSzf">Parse SZF</button>
<button class="btn btn-success ms-2" @onclick="SaveSzf">Save Parsed SZF</button>
</div>
<button @onclick="ParseSzf">Parse SZF</button>
@if (parsedObject != null)
{
<button @onclick="GenerateSzf">Generate SZF from Parsed Object</button>
}
</div>
<div class="output-section">
<h2>Parsed Object Data</h2>
@if (parsedObject != null)
{
<table>
<tbody>
<tr><th>Object Type</th><td>@parsedObject.TypeIdentifier</td></tr>
@if (!string.IsNullOrEmpty(parsedSchemaVersion))
{
<tr><th>Schema Version</th><td>@parsedSchemaVersion</td></tr>
}
<tr><th>Name</th><td>@parsedObject.Metadata.Name</td></tr>
<tr><th>Version</th><td>@parsedObject.Metadata.Version</td></tr>
<tr><th>ID</th><td>@parsedObject.Metadata.Guid</td></tr>
<tr><th>Author</th><td>@parsedObject.Metadata.Author</td></tr>
<tr><th>Description</th><td>@parsedObject.Metadata.Description</td></tr>
</tbody>
</table>
<div class="col-md-6">
<h3>Generated SZF</h3>
<textarea @bind="szfOutputText" rows="15" class="form-control" readonly></textarea>
<div class="mt-2">
<button class="btn btn-info" @onclick="GenerateSzf">Generate SZF from Object</button>
</div>
</div>
</div>
@* Display Dataset specific data *@
@if (parsedObject is Dataset dataset)
{
@if (dataset.Metadata.Any()) // Display Dataset's specific metadata
{
<h3>Dataset Specific Metadata</h3>
<table>
<thead>
<tr>
<th>Key</th>
<th>Value</th>
</tr>
</thead>
<tbody>
@foreach (var meta in dataset.Metadata)
{
<tr>
<td>@meta.Key</td>
<td>@meta.Value</td>
</tr>
}
</tbody>
</table>
}
@if (dataset.Entries.Any())
{
<h3>Dataset Entries</h3>
@foreach (var entry in dataset.Entries)
{
<div>
<h4>Entry: @entry.Name</h4>
@if (entry.Fields.Any())
{
<table>
<tbody>
@foreach (var field in entry.Fields)
{
<tr>
<td><strong>@field.Name</strong></td>
<td>@field.Value</td>
</tr>
}
</tbody>
</table>
}
@foreach (var section in entry.Sections)
{
@RenderDatasetSection(section)
}
</div>
}
}
}
@* Display CharacterTemplate specific data *@
@if (parsedObject is CharacterTemplate characterTemplate)
{
@if (characterTemplate.RequiredDatasets.Any())
{
<h3>Required Datasets</h3>
<table>
<thead>
<tr>
<th>Alias</th>
<th>Name</th>
<th>GUID</th>
<th>Version</th>
</tr>
</thead>
<tbody>
@foreach (var reqDataset in characterTemplate.RequiredDatasets)
{
<tr>
<td>@reqDataset.Alias</td>
<td>@reqDataset.Reference?.Name</td>
<td>@reqDataset.Reference?.Guid</td>
<td>@reqDataset.Reference?.Version</td>
</tr>
}
</tbody>
</table>
}
@if (characterTemplate.Sections.Any())
{
<h3>Template Sections</h3>
@foreach (var section in characterTemplate.Sections)
{
@RenderTemplateSection(section)
}
}
}
@* Display Character specific data *@
@if (parsedObject is Character character)
<div class="mt-4">
<h3>Load SZF by GUID</h3>
<div class="input-group mb-3">
<input type="text" class="form-control" placeholder="Enter GUID to load" @bind="loadGuid" />
<button class="btn btn-secondary" @onclick="LoadSzf">Load SZF</button>
</div>
</div>
<div class="mt-4">
<h3>Parsed Object Details</h3>
@if (!string.IsNullOrEmpty(errorMessage))
{
<div class="alert alert-danger" role="alert">
<strong>Error:</strong> @errorMessage
</div>
}
@if (!string.IsNullOrEmpty(statusMessage))
{
<div class="alert alert-info" role="alert">
<strong>Status:</strong> @statusMessage
</div>
}
@if (currentSzfObject != null)
{
<div class="card card-body bg-light">
<h5>Metadata</h5>
<p><strong>Type:</strong> @currentSzfObject.TypeIdentifier</p>
<p><strong>Name:</strong> @currentSzfObject.Metadata.Name</p>
<p><strong>GUID:</strong> @currentSzfObject.Metadata.Guid</p>
<p><strong>Version:</strong> @currentSzfObject.Metadata.Version</p>
@* For a more detailed display of sections, you might need a recursive component or more complex rendering logic.
For now, we'll just indicate if sections are present. *@
<h5>Sections:</h5>
@if (currentSzfObject is Character character)
{
<p><strong>Character Sections Count:</strong> @character.CharacterSections.Count</p>
@if (character.CharacterSections.Any())
{
<h3>Character Sections</h3>
@foreach (var section in character.CharacterSections)
{
@RenderCharacterSection(section)
}
<h6>Top-Level Sections:</h6>
<ul>
@foreach (var section in character.CharacterSections)
{
<li>@(section.Name) (@(section.Fields.Count) fields, @(section.Subsections.Count) subsections)</li>
}
</ul>
}
}
}
@if (errorMessages.Any())
{
<div>
<h4>Errors</h4>
<ul>
@foreach (var error in errorMessages)
{
<li>@error</li>
}
</ul>
</div>
}
</div>
<div class="generated-output-section">
<h2>Generated SZF Output</h2>
@if (!string.IsNullOrEmpty(generatedSzfOutput))
{
<textarea id="generated-szf-output" @bind="generatedSzfOutput" rows="20" readonly></textarea>
}
else
{
<p>No SZF has been generated yet.</p>
}
</div>
else if (currentSzfObject is CharacterTemplate characterTemplate)
{
<p><strong>Template Sections Count:</strong> @characterTemplate.Sections.Count</p>
@if (characterTemplate.Sections.Any())
{
<h6>Top-Level Template Sections:</h6>
<ul>
@foreach (var section in characterTemplate.Sections)
{
<li>@(section.Name) (@(section.Fields.Count) fields, @(section.Subsections.Count) subsections)</li>
}
</ul>
}
<p><strong>Required Datasets Count:</strong> @characterTemplate.RequiredDatasets.Count</p>
}
else if (currentSzfObject is Dataset dataset)
{
<p><strong>Dataset Entries Count:</strong> @dataset.Entries.Count</p>
@if (dataset.Entries.Any())
{
<h6>First 5 Dataset Entries:</h6>
<ul>
@foreach (var entry in dataset.Entries.Take(5))
{
<li>@(entry.Name) (@(entry.Fields.Count) fields, @(entry.Sections.Count) subsections)</li>
}
</ul>
}
}
else
{
<p>No specific display logic for this SzfObject type.</p>
}
</div>
}
else
{
<p>No SZF object parsed yet.</p>
}
</div>
<div class="logs-section">
<h2>Logs</h2>
<pre>@string.Join("\n", logMessages)</pre>
</div>
<style>
.test-container {
display: grid;
grid-template-columns: 1fr 1fr 1fr; /* Three columns for input, parsed data, generated output */
gap: 20px;
}
.input-section, .output-section, .generated-output-section {
border: 1px solid #ccc;
padding: 15px;
border-radius: 5px;
}
textarea {
width: 100%;
box-sizing: border-box; /* Include padding and border in the element's total width and height */
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 10px;
}
th, td {
border: 1px solid #ddd;
padding: 8px;
text-align: left;
}
th {
background-color: #f2f2f2;
}
.logs-section {
margin-top: 20px;
border: 1px solid #ccc;
padding: 15px;
border-radius: 5px;
}
pre {
white-space: pre-wrap; /* Ensures logs wrap */
word-wrap: break-word;
}
</style>
@code {
private string szfCode = string.Empty;
private SzfObject? parsedObject;
private string? parsedSchemaVersion; // To store the schema version from parsing
private string generatedSzfOutput = string.Empty; // New field for generated output
private List<string> logMessages = new List<string>();
private List<string> errorMessages = new List<string>();
private string szfInputText = string.Empty;
private string szfOutputText = string.Empty;
private SzfObject? currentSzfObject;
private string loadGuid = string.Empty;
private string statusMessage = string.Empty;
private string errorMessage = string.Empty;
private void ParseSzf()
// Helper to clear messages
private void ClearMessages()
{
ClearState(); // Clear previous states
Log("Starting SZF parsing...");
if (string.IsNullOrWhiteSpace(szfCode))
{
const string message = "SZF code input is empty.";
LogError(message);
errorMessages.Add(message);
return;
}
statusMessage = string.Empty;
errorMessage = string.Empty;
}
private void ParseSzf() // Changed from async Task to void
{
ClearMessages();
try
{
var parseResult = SzfParser.ParseSzfStructure(szfCode.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries).ToList());
if (string.IsNullOrWhiteSpace(szfInputText))
{
errorMessage = "Input SZF text cannot be empty.";
return;
}
// Create the SzfObject instance from the parseResult
parsedObject = SzfParser.CreateInstanceFromTypeIdentifier(parseResult.ObjectTypeIdentifier);
SzfParser.PopulateSzfObject(parsedObject, parseResult);
parsedSchemaVersion = parseResult.SchemaVersion; // Store the parsed schema version
Log("SZF content parsed successfully.");
Log($"Object Type: {parsedObject.TypeIdentifier}");
Log($"Schema Version: {parsedSchemaVersion}");
currentSzfObject = SzfParser.Parse(szfInputText);
statusMessage = $"SZF successfully parsed. Type: {currentSzfObject.TypeIdentifier}, Name: {currentSzfObject.Metadata.Name}";
szfOutputText = string.Empty; // Clear generated output on new parse
}
catch (SzfParseException ex)
{
errorMessage = $"Parsing failed: {ex.Message}";
Logger.LogError(ex, "SZF parsing error.");
}
catch (Exception ex)
{
const string message = "An error occurred during parsing.";
LogError(message, ex);
errorMessages.Add($"{message} See logs for details.");
errorMessage = $"An unexpected error occurred during parsing: {ex.Message}";
Logger.LogError(ex, "Unexpected error during SZF parsing.");
}
}
private void GenerateSzf()
private void GenerateSzf() // Changed from async Task to void
{
generatedSzfOutput = string.Empty; // Clear previous generated output
if (parsedObject == null)
{
Log("No object to generate from. Please parse an SZF first.");
return;
}
Log("Starting SZF generation...");
ClearMessages();
try
{
// Use the SzfGenerator to generate the string
generatedSzfOutput = SzfGenerator.Generate(parsedObject);
Log("SZF content generated successfully.");
if (currentSzfObject == null)
{
errorMessage = "No SZF object parsed to generate from. Please parse an SZF first.";
return;
}
szfOutputText = SzfGenerator.Generate(currentSzfObject);
statusMessage = "SZF successfully generated from the parsed object.";
}
catch (Exception ex)
{
const string message = "An error occurred during generation.";
LogError(message, ex);
errorMessages.Add($"{message} See logs for details.");
errorMessage = $"An error occurred during SZF generation: {ex.Message}";
Logger.LogError(ex, "SZF generation error.");
}
}
private void ClearState()
private async Task SaveSzf()
{
logMessages.Clear();
errorMessages.Clear();
parsedObject = null;
parsedSchemaVersion = null; // Clear parsed schema version
generatedSzfOutput = string.Empty;
ClearMessages();
try
{
if (currentSzfObject == null)
{
errorMessage = "No SZF object parsed to save. Please parse an SZF first.";
return;
}
// Generate the raw SZF content from the current object
var rawContent = SzfGenerator.Generate(currentSzfObject);
// Create an SzfFile DTO from SessionZero.Models
var szfFileDto = new SzfFile
{
Guid = currentSzfObject.Metadata.Guid.ToString(),
Name = currentSzfObject.Metadata.Name,
Type = currentSzfObject.TypeIdentifier, // The type identifier (e.g., "character", "dataset")
Version = currentSzfObject.Metadata.Version.ToString(),
Content = rawContent // Storing the raw SZF string
};
await SzfStorageService.SaveSzfFileAsync(szfFileDto);
statusMessage = $"SZF '{currentSzfObject.Metadata.Name}' (GUID: {currentSzfObject.Metadata.Guid}) successfully saved to storage.";
}
catch (ArgumentException ex)
{
errorMessage = $"Save failed: {ex.Message}";
Logger.LogError(ex, "Error saving SZF due to invalid argument.");
}
catch (Exception ex)
{
errorMessage = $"An unexpected error occurred during saving: {ex.Message}";
Logger.LogError(ex, "Unexpected error during SZF saving.");
}
}
private void Log(string message)
private async Task LoadSzf()
{
Logger.LogInformation(message);
logMessages.Add($"[INFO] {message}");
ClearMessages();
try
{
if (string.IsNullOrWhiteSpace(loadGuid))
{
errorMessage = "Please enter a GUID to load.";
return;
}
var loadedSzfFileDto = await SzfStorageService.GetSzfFileByGuidAsync(loadGuid);
if (loadedSzfFileDto == null)
{
statusMessage = $"No SZF file found for GUID: {loadGuid}";
currentSzfObject = null;
szfInputText = string.Empty;
szfOutputText = string.Empty;
return;
}
if (string.IsNullOrWhiteSpace(loadedSzfFileDto.Content))
{
errorMessage = "Loaded SZF file has no content. Cannot re-parse.";
currentSzfObject = null;
szfInputText = string.Empty;
szfOutputText = string.Empty;
return;
}
// Parse the content from the loaded DTO back into an SzfObject
currentSzfObject = SzfParser.Parse(loadedSzfFileDto.Content!); // Using null-forgiving operator
szfInputText = loadedSzfFileDto.Content!; // Using null-forgiving operator
szfOutputText = string.Empty; // Clear previous generated output
statusMessage = $"SZF '{currentSzfObject.Metadata.Name}' (GUID: {currentSzfObject.Metadata.Guid}) successfully loaded and parsed.";
}
catch (SzfParseException ex)
{
errorMessage = $"Loading and parsing failed: {ex.Message}";
Logger.LogError(ex, "Error re-parsing loaded SZF content.");
}
catch (Exception ex)
{
errorMessage = $"An unexpected error occurred during loading: {ex.Message}";
Logger.LogError(ex, "Unexpected error during SZF loading.");
}
}
private void LogError(string message, Exception? ex = null)
{
Logger.LogError(ex, message);
logMessages.Add($"[ERROR] {message}{(ex is not null ? $": {ex.Message}" : "")}");
}
// Helper for rendering Dataset sections (retained from original, adapted for basic HTML)
private RenderFragment RenderDatasetSection(DatasetSection dataSection) =>@<div>
<strong>@dataSection.Name</strong>
@if (dataSection.Fields.Any())
{
<table>
<tbody>
@foreach (var field in dataSection.Fields)
{
<tr>
<td><strong>@field.Name</strong></td>
<td>@field.Value</td>
</tr>
}
</tbody>
</table>
}
@foreach (var subSection in dataSection.Subsections)
{
@RenderDatasetSection(subSection)
}
</div>;
// Helper for rendering CharacterTemplate sections
private RenderFragment RenderTemplateSection(TemplateSection templateSection) =>@<div>
<strong>@templateSection.Name</strong>
@if (templateSection.Fields.Any())
{
<table>
<tbody>
@foreach (var field in templateSection.Fields)
{
<tr>
<td><strong>@field.Name</strong></td>
<td>@field.Value</td>
</tr>
}
</tbody>
</table>
}
@foreach (var subSection in templateSection.Subsections)
{
@RenderTemplateSection(subSection)
}
</div>;
// New helper for rendering Character sections
private RenderFragment RenderCharacterSection(CharacterSection characterSection) =>@<div>
<h4>@characterSection.Name</h4>
@if (characterSection.Fields.Any())
{
<table>
<tbody>
@foreach (var field in characterSection.Fields)
{
<tr>
<td><strong>@field.Name</strong></td>
<td>@field.Value</td>
</tr>
}
</tbody>
</table>
}
@foreach (var subSection in characterSection.Subsections)
{
@RenderCharacterSection(subSection)
}
</div>;
}

View File

@ -24,8 +24,6 @@
<ItemGroup>
<Folder Include="Components\Datasets\" />
<Folder Include="Models\" />
<Folder Include="Services\" />
<Folder Include="wwwroot\res\images\" />
</ItemGroup>

View File

@ -199,27 +199,28 @@ A character file is an instance of a character_template filled with specific dat
[Metadata]
Name = Elara the Brave
Guid = a1b2c3d4-e5f6-7890-abcd-ef1234567890
Version = 1.0.0
TemplateRef = Standard Fantasy Character|f9e8d7c6-b5a4-3210-9876-543210fedcba|2.1.0
[Info]
[Section: Info]
CharacterName = Elara
# The value 'Ranger' is an entry from the 'ClassData' dataset,
# as specified by the 'Class' field in the template.
Class = Ranger
[AbilityScores]
[Section: AbilityScores]
Strength = 16
Dexterity = 14
# Calculated values are not stored in the character file.
[Equipment]
[Section: Equipment]
# 'PrimaryWeapon' is a user-defined field name for the item.
# The value 'ItemData.Longsword' refers to the 'Longsword' entry in the 'ItemData' dataset.
PrimaryWeapon = ItemData.Longsword
PrimaryWeapon.Quantity = 1
[Inventory]
[Section: Inventory]
HealingPotion = ItemData.HealingPotion
HealingPotion.Quantity = 5
HempRope = ItemData.HempRope

View File

@ -1,235 +0,0 @@
# Session Zero Format (.szf) Documentation
## Overview
The Session Zero Format (.szf) is a human-readable, structured data format designed specifically for the Session Zero tabletop RPG management application. It provides a standardized way to represent game data, templates, and character sheets for offline storage, interchange, and sharing.
## Format Principles
- **Human-Readable**: Content is stored in plain text with a clear, consistent structure
- **Self-Documenting**: Field types and metadata are explicitly defined
- **Extensible**: Format supports future expansion without breaking compatibility
- **Portable**: Files can be easily shared and imported across devices
- **Compact**: Efficient representation without unnecessary verbosity
## File Structure
All .szf files follow a common structure:
1. **Header**: Identifies the file type and schema version
2. **Metadata**: Contains descriptive information about the content
3. **Content Sections**: Contains the actual data organized in logical sections
### Header Format
Files begin with type declaration and schema version:
```
!type: [file_type]
!schema: [schema_version]
```
Where `[file_type]` can be:
- `dataset` - Collection of related game elements
- `character_template` - Character sheet definition
- `character` - Actual character data
- `session` - Game session data
And `[schema_version]` follows semantic versioning (e.g., `1.0.0`).
### Section Format
Data is organized into named sections with optional nesting:
```
[Section: Name]
FieldName (field_type) = value
[Section.SubsectionName]
FieldName (field_type) = value
```
For entries in datasets, the format is:
```
[Entry: EntryName]
FieldName (field_type) = value
[Entry.EntryName.SubsectionName]
FieldName (field_type) = value
```
## Field Types
The .szf format supports the following field types:
| Type | Description | Example |
|------|-------------|--------|
| `text` | Single-line string | `Name (text) = Longsword` |
| `text-field` | Multi-line text | `Description (text-field) = A long sword...` |
| `number` | Integer or floating-point | `Weight (number) = 3` |
| `bool` | Boolean true/false | `IsMartial (bool) = true` |
| `group` | Container for related fields | `Currency (group) = true` |
| `calculated` | Formula-based computed value | `StrengthMod (calculated) = (Strength - 10) / 2` |
| `system` | Special application-handled field | `DatasetType (system) = items` |
| `dataset-reference` | Reference to specific dataset | `Race (dataset-reference) = PlayableRaces` |
| `dataset-type` | Reference to any dataset of type | `PrimaryWeapon (dataset-type) = items` |
| `dataset-reference-multiple` | Multiple references to specific dataset | `Feats (dataset-reference-multiple) = CoreFeats` |
| `dataset-type-multiple` | Multiple references to dataset type | `Level1Spells (dataset-type-multiple) = spells` |
## Dataset Format
Datasets contain collections of related game elements like items, spells, or character classes.
### Example
```szf
!type: dataset
!schema: 1.0.0
[Metadata]
Name (text) = Core Fantasy Items
Type (text) = items
Version (text) = 1.0.0
Description (text-field) = Basic fantasy items for any campaign
[Entry: Longsword]
Name (text) = Longsword
Description (text-field) = A standard sword with a long blade
Category (text) = Weapon
[Entry.Longsword.Stats]
Damage (text) = 1d8
Weight (number) = 3
IsMartial (bool) = true
```
## Character Template Format
Templates define the structure of character sheets, including fields, calculations, and dataset references.
### Example
```szf
!type: character_template
!schema: 1.0.0
[Metadata]
Name (text) = Fantasy Character Sheet
Version (text) = 2.0.0
GUID (text) = f9e8d7c6-b5a4-3210-9876-543210fedcba
Description (text-field) = Advanced character sheet for fantasy RPGs
Author (text) = Template Master
GameSystem (text) = Fantasy RPG Universal
[Required Datasets]
ClassesDataset (text) = Core Classes|c3d4e5f6-g7h8-9012-cdef-123456789012|1.5.0
RacesDataset (text) = Playable Races|d4e5f6g7-h8i9-0123-def0-234567890123|1.3.0
[Section: Ability Scores]
Strength (number) = 10
Dexterity (number) = 10
Constitution (number) = 10
[Section.Ability Scores.Modifiers]
StrengthMod (calculated) = (Strength - 10) / 2
DexterityMod (calculated) = (Dexterity - 10) / 2
```
## Special Features
### Dataset Dependencies
Character templates can specify required datasets using the `Name|GUID|Version` triple format:
```
[Required Datasets]
ClassesDataset (text) = Core Classes|c3d4e5f6-g7h8-9012-cdef-123456789012|1.5.0
```
This ensures that character sheets created from the template have access to the correct datasets.
### Calculated Fields
Formulas can reference other fields to create dynamic values:
```
StrengthMod (calculated) = (Strength - 10) / 2
SpellSaveDC (calculated) = 8 + ProficiencyBonus + IntelligenceMod
```
Supported operations include basic arithmetic, comparisons, and functions like SUM, MIN, MAX.
### Nested Sections
Sections can be nested to create a logical hierarchy:
```
[Section: Equipment]
DatasetType (system) = items
[Section.Equipment.Weapons]
PrimaryWeapon (dataset-type) = items
```
### System Properties
Special fields that control application behavior:
```
DatasetType (system) = items
SelectionMode (system) = multiple
```
## Best Practices
1. **Use Descriptive Names**: Choose clear, meaningful section and field names
2. **Include Metadata**: Always provide version, description, and author information
3. **Organize Logically**: Group related fields into appropriate sections
4. **Set Defaults**: Provide sensible default values where appropriate
5. **Document Dependencies**: List all required datasets with their correct identifiers
6. **Validate Before Sharing**: Ensure all references are valid and values are appropriate
7. **Follow Naming Conventions**: Use consistent capitalization and naming patterns
## Import/Export Guidelines
### Exporting
When exporting .szf files:
1. Include all required metadata (especially GUID and version)
2. List all dependencies explicitly
3. Keep file sizes reasonable (prefer multiple focused files over single massive ones)
4. Set appropriate default values
### Importing
When importing .szf files:
1. Validate the file structure before processing
2. Check for missing dependencies
3. Handle version conflicts appropriately
4. Validate all field values against their declared types
## Character Sheet Data
When a character is created from a template, the resulting .szf file contains:
1. Reference to the source template (GUID and version)
2. All field values populated by the user
3. References to selected dataset entries
4. Character-specific notes and customizations
## Versioning and Compatibility
- **GUID**: Unique identifier that remains constant across versions
- **Version**: Semantic version number (MAJOR.MINOR.PATCH)
- **Compatibility**: Minor versions should maintain backward compatibility
- **Breaking Changes**: Major version increases indicate potentially incompatible changes
## Common Use Cases
1. **Sharing Game Systems**: Distribute templates and datasets for specific RPG systems
2. **Character Backups**: Export characters for safekeeping or transfer between devices
3. **Community Content**: Share custom content with other Session Zero users
4. **Game Preparation**: Pre-build NPCs, items, and other game elements

View File

@ -1,217 +0,0 @@
!type: character_template
!schema: 1.0.0
[Metadata]
Name (text) = Advanced Fantasy Character Sheet
Version (text) = 2.1.0
GUID (text) = f9e8d7c6-b5a4-3210-9876-543210fedcba
Description (text-field) = Comprehensive character template for fantasy RPGs with full dataset integration, calculated fields, and advanced features
Author (text) = Template Master
GameSystem (text) = Fantasy RPG Universal
[Required Datasets]
PlayerCharacterClasses (text) = Player Character Classes|c3d4e5f6-b5a4-40c7-97d8-30bca3bda294|1.5.3
PlayableRaces (text) = Playable Races|d4e5f6a7-b8c9-40d0-e1f2-345678901234|1.3.0
CoreSkillsList (text) = Core Skills List|e5f6a7b8-c9d0-40e1-f2g3-456789012345|1.0.2
[Section: Core Identity]
CharacterName (text) =
PlayerName (text) =
Race (dataset-reference) = PlayableRaces
Class (dataset-reference) = PlayerCharacterClasses
Level (number) = 1
Experience (number) = 0
Alignment (text) = Neutral
Background (text) =
Age (number) = 25
Height (text) =
Weight (text) =
Gender (text) =
[Section: Ability Scores]
Strength (number) = 10
Dexterity (number) = 10
Constitution (number) = 10
Intelligence (number) = 10
Wisdom (number) = 10
Charisma (number) = 10
[Section.Ability Scores.Modifiers]
StrengthMod (calculated) = (Strength - 10) / 2
DexterityMod (calculated) = (Dexterity - 10) / 2
ConstitutionMod (calculated) = (Constitution - 10) / 2
IntelligenceMod (calculated) = (Intelligence - 10) / 2
WisdomMod (calculated) = (Wisdom - 10) / 2
CharismaMod (calculated) = (Charisma - 10) / 2
[Section: Combat Statistics]
HitPoints (number) = 10
MaxHitPoints (calculated) = BaseHP + (ConstitutionMod * Level)
ArmorClass (calculated) = 10 + DexterityMod + ArmorBonus + ShieldBonus
Initiative (calculated) = DexterityMod + MiscInitiative
Speed (number) = 30
BaseAttackBonus (number) = 0
[Section.Combat Statistics.Saving Throws]
Fortitude (calculated) = BaseFortitude + ConstitutionMod
Reflex (calculated) = BaseReflex + DexterityMod
Will (calculated) = BaseWill + WisdomMod
BaseFortitude (number) = 0
BaseReflex (number) = 0
BaseWill (number) = 0
[Section: Skills]
DatasetReference (system) = CoreSkillsList
Acrobatics (number) = 0
Athletics (number) = 0
Deception (number) = 0
History (number) = 0
Insight (number) = 0
Intimidation (number) = 0
Investigation (number) = 0
Medicine (number) = 0
Nature (number) = 0
Perception (number) = 0
Performance (number) = 0
Persuasion (number) = 0
Religion (number) = 0
SleightOfHand (number) = 0
Stealth (number) = 0
Survival (number) = 0
[Section.Skills.Skill Modifiers]
AcrobaticsTotal (calculated) = Acrobatics + DexterityMod
AthleticsTotal (calculated) = Athletics + StrengthMod
DeceptionTotal (calculated) = Deception + CharismaMod
HistoryTotal (calculated) = History + IntelligenceMod
InsightTotal (calculated) = Insight + WisdomMod
IntimidationTotal (calculated) = Intimidation + CharismaMod
InvestigationTotal (calculated) = Investigation + IntelligenceMod
MedicineTotal (calculated) = Medicine + WisdomMod
NatureTotal (calculated) = Nature + IntelligenceMod
PerceptionTotal (calculated) = Perception + WisdomMod
PerformanceTotal (calculated) = Performance + CharismaMod
PersuasionTotal (calculated) = Persuasion + CharismaMod
ReligionTotal (calculated) = Religion + IntelligenceMod
SleightOfHandTotal (calculated) = SleightOfHand + DexterityMod
StealthTotal (calculated) = Stealth + DexterityMod
SurvivalTotal (calculated) = Survival + WisdomMod
[Section: Equipment]
DatasetType (system) = items
SelectionMode (system) = multiple
Currency (group) = true
[Section.Equipment.Weapons]
PrimaryWeapon (dataset-type) = items
SecondaryWeapon (dataset-type) = items
RangedWeapon (dataset-type) = items
Ammunition (dataset-type) = items
AmmoCount (number) = 0
[Section.Equipment.Armor]
Armor (dataset-type) = items
Shield (dataset-type) = items
ArmorBonus (calculated) = Armor.ArmorClass
ShieldBonus (calculated) = Shield.ArmorClass
[Section.Equipment.Currency]
PlatinumPieces (number) = 0
GoldPieces (number) = 0
SilverPieces (number) = 0
CopperPieces (number) = 0
TotalGoldValue (calculated) = (PlatinumPieces * 10) + GoldPieces + (SilverPieces / 10) + (CopperPieces / 100)
[Section: Inventory]
DatasetType (system) = items
SelectionMode (system) = multiple
MaxItems (number) = 50
CarryingCapacity (calculated) = Strength * 15
CurrentWeight (calculated) = SUM(SelectedItems.Weight)
EncumbranceLevel (calculated) = CurrentWeight / CarryingCapacity
[Section: Spellcasting]
DatasetType (system) = spells
SelectionMode (system) = multiple
SpellcastingAbility (text) = Intelligence
SpellSaveDC (calculated) = 8 + ProficiencyBonus + SpellcastingAbilityMod
SpellAttackBonus (calculated) = ProficiencyBonus + SpellcastingAbilityMod
SpellsKnown (number) = 0
SpellSlots (group) = true
[Section.Spellcasting.Spell Slots]
Level1Slots (number) = 0
Level2Slots (number) = 0
Level3Slots (number) = 0
Level4Slots (number) = 0
Level5Slots (number) = 0
Level6Slots (number) = 0
Level7Slots (number) = 0
Level8Slots (number) = 0
Level9Slots (number) = 0
[Section.Spellcasting.Known Spells]
Cantrips (dataset-type-multiple) = spells
Level1Spells (dataset-type-multiple) = spells
Level2Spells (dataset-type-multiple) = spells
Level3Spells (dataset-type-multiple) = spells
Level4Spells (dataset-type-multiple) = spells
Level5Spells (dataset-type-multiple) = spells
[Section: Companions]
DatasetType (system) = npcs
SelectionMode (system) = multiple
MaxCompanions (number) = 3
[Section.Companions.Active Companions]
Animal Companion (dataset-type) = npcs
Familiar (dataset-type) = npcs
Hireling (dataset-type) = npcs
[Section: Features and Traits]
RacialTraits (group) = true
ClassFeatures (group) = true
Feats (group) = true
[Section.Features and Traits.Racial Traits]
DarkvisionRange (number) = 0
Size (text) = Medium
Languages (text-field) = Common
SpecialAbilities (text-field) =
[Section.Features and Traits.Class Features]
HitDie (text) = d8
ProficiencyBonus (number) = 2
ArmorProficiencies (text-field) =
WeaponProficiencies (text-field) =
ToolProficiencies (text-field) =
SavingThrowProficiencies (text) =
[Section: Personal Journal]
CharacterDescription (text-field) =
Personality (text-field) =
Ideals (text-field) =
Bonds (text-field) =
Flaws (text-field) =
Backstory (text-field) =
Goals (text-field) =
Notes (text-field) =
[Section: Combat Tracker]
CurrentHitPoints (number) = 10
TemporaryHitPoints (number) = 0
HitDiceRemaining (text) = 1d8
DeathSaves (group) = true
Conditions (text-field) =
ActionEconomy (group) = true
[Section.Combat Tracker.Death Saves]
Successes (number) = 0
Failures (number) = 0
IsStabilized (bool) = false
[Section.Combat Tracker.Action Economy]
ActionUsed (bool) = false
BonusActionUsed (bool) = false
ReactionUsed (bool) = false
MovementUsed (number) = 0

View File

@ -1,70 +0,0 @@
!type: character
!schema: 1.0.0
[Metadata]
Name (text) = Elara Moonwhisper
Version (text) = 1.0.0
GUID (text) = 1a2b3c4d-5e6f-7890-1234-567890abcdef
Description (text-field) = A young elven rogue with a mysterious past.
Author (text) = Player One
TemplateRef (dataset-reference) = Advanced Fantasy Character Sheet|f9e8d7c6-b5a4-3210-9876-543210fedcba|2.1.0
[Section: Core Identity]
CharacterName (text) = Elara Moonwhisper
PlayerName (text) = John Doe
Race (dataset-reference) = Human|abcd1234-5678-90ab-cdef-1234567890ab|1.0.0
Class (dataset-reference) = Rogue|00001111-2222-3333-4444-555566667777|1.0.0
Level (number) = 3
Experience (number) = 1500
Alignment (text) = Chaotic Neutral
Background (text) = Urchin
Age (number) = 28
Height (text) = 5'6"
Weight (text) = 120 lbs
Gender (text) = Female
[Section: Ability Scores]
Strength (number) = 10
Dexterity (number) = 16
Constitution (number) = 14
Intelligence (number) = 12
Wisdom (number) = 10
Charisma (number) = 8
[Section: Combat Statistics]
HitPoints (number) = 22
BaseAttackBonus (number) = 2
[Section: Skills]
Acrobatics (number) = 3
Athletics (number) = 0
Deception (number) = 0
History (number) = 1
Insight (number) = 0
Intimidation (number) = 0
Investigation (number) = 2
Medicine (number) = 0
Nature (number) = 0
Perception (number) = 2
Performance (number) = 0
Persuasion (number) = 0
Religion (number) = 0
SleightOfHand (number) = 3
Stealth (number) = 3
Survival (number) = 0
[Section.Equipment.Currency]
PlatinumPieces (number) = 0
GoldPieces (number) = 5
SilverPieces (number) = 12
CopperPieces (number) = 50
[Section: Personal Journal]
CharacterDescription (text-field) = Elara has sharp, emerald eyes and dark, flowing hair. She carries herself with a quiet confidence.
Personality (text-field) = Resourceful and independent, but distrustful of authority.
Ideals (text-field) = Freedom. Everyone should be free to live their own life.
Bonds (text-field) = She owes a debt to an old beggar who saved her life.
Flaws (text-field) = Impulsive and prone to taking unnecessary risks.
Backstory (text-field) = Orphaned at a young age, Elara survived on the streets, learning to pick pockets and locks.
Goals (text-field) = To uncover the truth about her parents' disappearance.
Notes (text-field) = Has a small, intricately carved wooden bird she keeps as a good luck charm.

View File