Updating szf spec.
This commit is contained in:
parent
1713c38301
commit
224e2a3d9b
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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>
|
||||
|
@ -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>;
|
||||
}
|
@ -24,8 +24,6 @@
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Components\Datasets\" />
|
||||
<Folder Include="Models\" />
|
||||
<Folder Include="Services\" />
|
||||
<Folder Include="wwwroot\res\images\" />
|
||||
</ItemGroup>
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
@ -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
|
@ -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.
|
Loading…
Reference in New Issue
Block a user