Character template szf object
This commit is contained in:
parent
66dd01020c
commit
d12474c01e
175
SessionZero/Data/CharacterTemplate.cs
Normal file
175
SessionZero/Data/CharacterTemplate.cs
Normal file
@ -0,0 +1,175 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace SessionZero.Data;
|
||||
|
||||
public class CharacterTemplate : SzfObject
|
||||
{
|
||||
public string GameSystem { get; set; } = string.Empty;
|
||||
public List<RequiredDatasetReference> RequiredDatasets { get; set; } = new();
|
||||
public List<TemplateSection> Sections { get; set; } = new(); // Top-level sections
|
||||
|
||||
public override SzfObjectType ObjectType => SzfObjectType.CharacterTemplate;
|
||||
|
||||
public override void PopulateFromMetadata(Dictionary<string, SzfField> metadata)
|
||||
{
|
||||
base.PopulateFromMetadata(metadata);
|
||||
|
||||
if (metadata.TryGetValue("GameSystem", out var gameSystemField))
|
||||
GameSystem = gameSystemField.Value?.ToString() ?? string.Empty;
|
||||
}
|
||||
|
||||
public override void ParseSections(List<SzfSection> sections)
|
||||
{
|
||||
// Parse "Required Datasets" section
|
||||
var requiredDatasetsSection =
|
||||
sections.FirstOrDefault(s => s.Name.Equals("Required Datasets", StringComparison.OrdinalIgnoreCase));
|
||||
if (requiredDatasetsSection != null)
|
||||
{
|
||||
foreach (var field in requiredDatasetsSection.Fields)
|
||||
{
|
||||
var datasetReference = DatasetReference.Parse(field.Value?.ToString() ?? string.Empty);
|
||||
if (datasetReference != null)
|
||||
{
|
||||
RequiredDatasets.Add(new RequiredDatasetReference
|
||||
{
|
||||
Alias = field.Name,
|
||||
Reference = datasetReference
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse and build the hierarchical template sections
|
||||
// Use a map to quickly find existing sections by their full path for nesting
|
||||
var sectionPathMap = new Dictionary<string, TemplateSection>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var szfSection in sections)
|
||||
{
|
||||
// Only process sections that start with "Section:"
|
||||
if (!szfSection.Name.StartsWith("Section:", StringComparison.OrdinalIgnoreCase)) continue;
|
||||
|
||||
// Normalize the section name (e.g., "Section:Ability Scores.Modifiers" to "Ability Scores.Modifiers")
|
||||
var rawSectionPath = szfSection.Name.Substring("Section:".Length).Trim();
|
||||
var nameParts = rawSectionPath.Split('.');
|
||||
|
||||
TemplateSection? currentParentSection = null;
|
||||
List<TemplateSection> currentSectionList = Sections; // Start from the top-level Sections list
|
||||
|
||||
string accumulatedPath = "Section"; // Used for dictionary key to ensure uniqueness and hierarchy
|
||||
|
||||
for (int i = 0; i < nameParts.Length; i++)
|
||||
{
|
||||
var sectionNamePart = nameParts[i];
|
||||
accumulatedPath += "." + sectionNamePart; // Build the full path for the map key
|
||||
|
||||
TemplateSection? targetSection = null;
|
||||
|
||||
// Check if this specific section (at this level of hierarchy) already exists
|
||||
if (sectionPathMap.TryGetValue(accumulatedPath, out var existingSection))
|
||||
{
|
||||
targetSection = existingSection;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create a new section if it doesn't exist
|
||||
var newSection = new TemplateSection { Name = sectionNamePart };
|
||||
sectionPathMap[accumulatedPath] = newSection;
|
||||
|
||||
if (currentParentSection == null)
|
||||
{
|
||||
// This is a top-level section
|
||||
currentSectionList.Add(newSection);
|
||||
}
|
||||
else
|
||||
{
|
||||
// This is a subsection of the current parent
|
||||
currentParentSection.Subsections.Add(newSection);
|
||||
}
|
||||
|
||||
targetSection = newSection;
|
||||
}
|
||||
|
||||
currentParentSection = targetSection; // Move down the hierarchy
|
||||
currentSectionList =
|
||||
targetSection.Subsections; // Next iteration will look in subsections of this section
|
||||
}
|
||||
|
||||
// Once we've traversed/created the full path, currentParentSection points to the innermost section.
|
||||
// Now populate its fields from the SzfSection.
|
||||
if (currentParentSection != null)
|
||||
{
|
||||
foreach (var field in szfSection.Fields)
|
||||
{
|
||||
currentParentSection.Fields.Add(ConvertToTemplateField(field));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private TemplateField ConvertToTemplateField(SzfField szfField)
|
||||
{
|
||||
return new TemplateField
|
||||
{
|
||||
Name = szfField.Name,
|
||||
Type = ParseDatasetFieldType(szfField.Type), // Reusing DatasetFieldType for consistency
|
||||
Value = szfField.Value,
|
||||
};
|
||||
}
|
||||
|
||||
// Helper method to parse DatasetFieldType, can be shared or defined here.
|
||||
// Copying from Dataset.cs for now, consider a common utility.
|
||||
private DatasetFieldType ParseDatasetFieldType(string typeString)
|
||||
{
|
||||
return typeString.ToLower() switch
|
||||
{
|
||||
"text" => DatasetFieldType.Text,
|
||||
"text-field" => DatasetFieldType.TextField,
|
||||
"number" => DatasetFieldType.Number,
|
||||
"bool" => DatasetFieldType.Boolean,
|
||||
"group" => DatasetFieldType.Group,
|
||||
"calculated" => DatasetFieldType.Calculated,
|
||||
"system" => DatasetFieldType.System,
|
||||
"dataset-reference" => DatasetFieldType.DatasetReference,
|
||||
"dataset-type" => DatasetFieldType.DatasetType,
|
||||
"dataset-reference-multiple" => DatasetFieldType.DatasetReferenceMultiple,
|
||||
"dataset-type-multiple" => DatasetFieldType.DatasetTypeMultiple,
|
||||
_ => DatasetFieldType.Text
|
||||
};
|
||||
}
|
||||
|
||||
public override SzfValidationResult Validate()
|
||||
{
|
||||
var result = base.Validate();
|
||||
|
||||
// if (string.IsNullOrWhiteSpace(GameSystem))
|
||||
// result.AddError("GameSystem is required for CharacterTemplate");
|
||||
|
||||
// Further validation for required datasets and template sections can be added here
|
||||
// e.g., checking for duplicate section names or missing required fields.
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public class RequiredDatasetReference
|
||||
{
|
||||
public string Alias { get; set; } = string.Empty;
|
||||
public DatasetReference? Reference { get; set; }
|
||||
}
|
||||
|
||||
public class TemplateSection
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public List<TemplateField> Fields { get; set; } = new();
|
||||
public List<TemplateSection> Subsections { get; set; } = new();
|
||||
}
|
||||
|
||||
public class TemplateField
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public DatasetFieldType Type { get; set; }
|
||||
public object? Value { get; set; }
|
||||
}
|
@ -222,10 +222,10 @@ public enum DatasetFieldType
|
||||
public class DatasetReference
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public Guid DatasetId { get; set; }
|
||||
public Guid Guid { get; set; }
|
||||
public string Version { get; set; } = string.Empty;
|
||||
|
||||
public override string ToString() => $"{Name}|{DatasetId}|{Version}";
|
||||
public override string ToString() => $"{Name}|{Guid}|{Version}";
|
||||
|
||||
public static DatasetReference? Parse(string reference)
|
||||
{
|
||||
@ -236,7 +236,7 @@ public class DatasetReference
|
||||
return new DatasetReference
|
||||
{
|
||||
Name = parts[0],
|
||||
DatasetId = id,
|
||||
Guid = id,
|
||||
Version = parts[2]
|
||||
};
|
||||
}
|
||||
|
@ -73,8 +73,7 @@ public class SzfParser
|
||||
return objectType switch
|
||||
{
|
||||
SzfObjectType.Dataset => new Dataset(),
|
||||
// Add other types as they're implemented
|
||||
// SzfObjectType.CharacterTemplate => new CharacterTemplate(),
|
||||
SzfObjectType.CharacterTemplate => new CharacterTemplate(), // UNCOMMENTED THIS LINE
|
||||
// SzfObjectType.Character => new Character(),
|
||||
// SzfObjectType.Session => new Session(),
|
||||
_ => throw new SzfParseException($"Unsupported object type: {objectType}")
|
||||
@ -254,7 +253,7 @@ public class SzfParser
|
||||
var expectedType = typeof(T).Name switch
|
||||
{
|
||||
nameof(Dataset) => SzfObjectType.Dataset,
|
||||
// Add other types as they're implemented
|
||||
nameof(CharacterTemplate) => SzfObjectType.CharacterTemplate,
|
||||
_ => throw new SzfParseException($"Unsupported SzfObject type: {typeof(T).Name}")
|
||||
};
|
||||
|
||||
@ -289,7 +288,7 @@ public class SzfParser
|
||||
/// <summary>
|
||||
/// Internal class to hold parsed SZF structure
|
||||
/// </summary>
|
||||
internal class SzfParseResult
|
||||
public class SzfParseResult // Changed from internal to public for broader accessibility if needed
|
||||
{
|
||||
public SzfObjectType ObjectType { get; set; }
|
||||
public string SchemaVersion { get; set; } = "1.0.0";
|
||||
@ -300,7 +299,7 @@ internal class SzfParseResult
|
||||
/// <summary>
|
||||
/// Internal class representing a parsed section
|
||||
/// </summary>
|
||||
public class SzfSection
|
||||
public class SzfSection // Changed from internal to public for broader accessibility if needed
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public List<SzfField> Fields { get; set; } = new();
|
||||
|
@ -4,20 +4,20 @@
|
||||
@inject SzfParser SzfParser
|
||||
@inject ILogger<SzfParser> Logger
|
||||
|
||||
<h1 class="page-title">SZF Parser Test Page</h1>
|
||||
<h1>SZF Parser Test Page</h1>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<div class="form-group">
|
||||
<textarea id="szf-input" class="form-control" @bind="szfCode" rows="20"></textarea>
|
||||
<div>
|
||||
<div>
|
||||
<div>
|
||||
<textarea id="szf-input" @bind="szfCode" rows="20"></textarea>
|
||||
</div>
|
||||
<button class="btn btn-primary mt-2" @onclick="ParseSzf">Parse SZF</button>
|
||||
<button @onclick="ParseSzf">Parse SZF</button>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<div>
|
||||
@if (parsedObject != null)
|
||||
{
|
||||
<h4>Parsed Object Metadata</h4>
|
||||
<table class="table">
|
||||
<h2>Parsed Object Data</h2>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr><th>Object Type</th><td>@parsedObject.ObjectType</td></tr>
|
||||
<tr><th>Schema Version</th><td>@parsedObject.SchemaVersion</td></tr>
|
||||
@ -31,8 +31,8 @@
|
||||
|
||||
@if (parsedObject.ExtendedMetadata.Any())
|
||||
{
|
||||
<h5>Extended Metadata</h5>
|
||||
<table class="table">
|
||||
<h3>Extended Metadata</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Key</th>
|
||||
@ -51,24 +51,24 @@
|
||||
</table>
|
||||
}
|
||||
|
||||
@if (parsedObject is Dataset dataset && dataset.Entries.Any())
|
||||
@* Display Dataset specific data *@
|
||||
@if (parsedObject is Dataset dataset)
|
||||
{
|
||||
<h4 class="mt-4">Entries</h4>
|
||||
@foreach (var entry in dataset.Entries)
|
||||
@if (dataset.Entries.Any())
|
||||
{
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">
|
||||
<h5>@entry.Name</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h3>Dataset Entries</h3>
|
||||
@foreach (var entry in dataset.Entries)
|
||||
{
|
||||
<div>
|
||||
<h4>Entry: @entry.Name</h4>
|
||||
@if (entry.Fields.Any())
|
||||
{
|
||||
<table class="table table-bordered table-sm">
|
||||
<table>
|
||||
<tbody>
|
||||
@foreach (var field in entry.Fields)
|
||||
{
|
||||
<tr>
|
||||
<td style="width: 30%;"><strong>@field.Name</strong></td>
|
||||
<td><strong>@field.Name</strong></td>
|
||||
<td>@field.Value</td>
|
||||
</tr>
|
||||
}
|
||||
@ -77,16 +77,55 @@
|
||||
}
|
||||
@foreach (var section in entry.Sections)
|
||||
{
|
||||
@RenderSection(section)
|
||||
@RenderDatasetSection(section)
|
||||
}
|
||||
</div>
|
||||
</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> @* Changed from DatasetId to Guid *@
|
||||
<td>@reqDataset.Reference?.Version</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
|
||||
@if (characterTemplate.Sections.Any())
|
||||
{
|
||||
<h3>Template Sections</h3>
|
||||
@foreach (var section in characterTemplate.Sections)
|
||||
{
|
||||
@RenderTemplateSection(section)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@if (errorMessages.Any())
|
||||
{
|
||||
<div class="alert alert-danger mt-3">
|
||||
<div>
|
||||
<h4>Errors</h4>
|
||||
<ul>
|
||||
@foreach (var error in errorMessages)
|
||||
@ -99,11 +138,9 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mt-4">
|
||||
<div class="col-12">
|
||||
<h4>Logs</h4>
|
||||
<pre class="bg-light p-3">@string.Join("\n", logMessages)</pre>
|
||||
</div>
|
||||
<div>
|
||||
<h2>Logs</h2>
|
||||
<pre>@string.Join("\n", logMessages)</pre>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
@ -155,29 +192,49 @@
|
||||
logMessages.Add($"[ERROR] {message}{(ex is not null ? $": {ex.Message}" : "")}");
|
||||
}
|
||||
|
||||
private RenderFragment RenderSection(DatasetSection dataSection) =>@<div class="card mt-2" style="margin-left: 20px;">
|
||||
<div class="card-header py-1">
|
||||
<strong>@dataSection.Name</strong>
|
||||
</div>
|
||||
<div class="card-body py-2">
|
||||
@if (dataSection.Fields.Any())
|
||||
{
|
||||
<table class="table table-bordered table-sm mb-0">
|
||||
<tbody>
|
||||
@foreach (var field in dataSection.Fields)
|
||||
{
|
||||
<tr>
|
||||
<td style="width: 30%;"><strong>@field.Name</strong></td>
|
||||
<td>@field.Value</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
@foreach (var subSection in dataSection.Subsections)
|
||||
{
|
||||
@RenderSection(subSection)
|
||||
}
|
||||
</div>
|
||||
// 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>;
|
||||
|
||||
// New 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>;
|
||||
}
|
@ -10,23 +10,23 @@ Author (text) = Template Master
|
||||
GameSystem (text) = Fantasy RPG Universal
|
||||
|
||||
[Required Datasets]
|
||||
PlayerCharacterClasses (text) = Player Character Classes|c3d4e5f6-g7h8-9012-cdef-123456789012|1.5.3
|
||||
PlayableRaces (text) = Playable Races|d4e5f6g7-h8i9-0123-def0-234567890123|1.3.0
|
||||
CoreSkillsList (text) = Core Skills List|e5f6g7h8-i9j0-1234-ef01-345678901234|1.0.2
|
||||
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) =
|
||||
CharacterName (text) =
|
||||
PlayerName (text) =
|
||||
Race (dataset-reference) = PlayableRaces
|
||||
Class (dataset-reference) = PlayerCharacterClasses
|
||||
Level (number) = 1
|
||||
Experience (number) = 0
|
||||
Alignment (text) = Neutral
|
||||
Background (text) =
|
||||
Background (text) =
|
||||
Age (number) = 25
|
||||
Height (text) =
|
||||
Weight (text) =
|
||||
Gender (text) =
|
||||
Height (text) =
|
||||
Weight (text) =
|
||||
Gender (text) =
|
||||
|
||||
[Section: Ability Scores]
|
||||
Strength (number) = 10
|
||||
@ -177,32 +177,32 @@ Feats (group) = true
|
||||
DarkvisionRange (number) = 0
|
||||
Size (text) = Medium
|
||||
Languages (text-field) = Common
|
||||
SpecialAbilities (text-field) =
|
||||
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) =
|
||||
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) =
|
||||
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) =
|
||||
Conditions (text-field) =
|
||||
ActionEconomy (group) = true
|
||||
|
||||
[Section.Combat Tracker.Death Saves]
|
||||
|
Loading…
Reference in New Issue
Block a user