254 lines
10 KiB
C#
254 lines
10 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
|
|
namespace SessionZero.Data;
|
|
|
|
// Helper classes to represent the structured character 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 object? Value { get; set; }
|
|
}
|
|
|
|
public class CharacterSection
|
|
{
|
|
public string Name { get; set; } = string.Empty;
|
|
public List<CharacterField> Fields { get; set; } = new();
|
|
public List<CharacterSection> Subsections { get; set; } = new();
|
|
}
|
|
|
|
[SzfObject("character")]
|
|
public class Character : SzfObject
|
|
{
|
|
public Guid TemplateGuid { get; private set; }
|
|
public Version TemplateVersion { get; private set; } = new Version(1, 0, 0);
|
|
|
|
// This will hold the actual character data organized in sections,
|
|
// using our custom hierarchical CharacterSection objects.
|
|
public List<CharacterSection> CharacterSections { get; private set; } = new List<CharacterSection>();
|
|
|
|
public Character()
|
|
{
|
|
// Default constructor for deserialization/initialization.
|
|
// Base SzfObject constructor handles its own Metadata initialization.
|
|
}
|
|
|
|
public Character(
|
|
Guid templateGuid,
|
|
Version templateVersion,
|
|
string name,
|
|
string version,
|
|
string? description = null,
|
|
string? author = null
|
|
)
|
|
{
|
|
TemplateGuid = templateGuid;
|
|
TemplateVersion = templateVersion;
|
|
// Do NOT directly set Metadata here. The SzfObject base class manages its own Metadata.
|
|
// If constructing a new object, you would typically set its Name, Version, etc.
|
|
// via the SzfObject's own properties or methods after construction, or through a
|
|
// dedicated constructor in SzfObject that passes to its Metadata.
|
|
// For existing SzfObjects, PopulateFromMetadata handles reading.
|
|
}
|
|
|
|
public override void PopulateFromMetadata(Dictionary<string, SzfField> metadata)
|
|
{
|
|
// Call the base method to populate common SzfObject metadata (Name, Version, Description, Author)
|
|
base.PopulateFromMetadata(metadata);
|
|
|
|
// Populate Character-specific metadata from the "Metadata" section
|
|
if (metadata.TryGetValue("SourceTemplateGuid", out var guidField))
|
|
{
|
|
if (Guid.TryParse(guidField.Value?.ToString(), out var guid))
|
|
{
|
|
TemplateGuid = guid;
|
|
}
|
|
}
|
|
|
|
if (metadata.TryGetValue("SourceTemplateVersion", out var versionField))
|
|
{
|
|
// Ensure the value is converted to a string and explicitly call System.Version.TryParse
|
|
if (System.Version.TryParse(Convert.ToString(versionField.Value), out var ver))
|
|
{
|
|
TemplateVersion = ver;
|
|
}
|
|
}
|
|
}
|
|
|
|
public override void ParseSections(List<SzfSection> sections)
|
|
{
|
|
CharacterSections.Clear(); // Clear existing sections to ensure a fresh parse
|
|
|
|
// A temporary list to hold top-level CharacterSections as we parse
|
|
var tempTopLevelCharacterSections = new List<CharacterSection>();
|
|
|
|
// A map to quickly find parent sections by their name (simplified for lookup)
|
|
// e.g., "Character Information" -> CharacterSection object
|
|
var sectionNameMap = new Dictionary<string, CharacterSection>(StringComparer.OrdinalIgnoreCase);
|
|
|
|
foreach (var szfSection in sections)
|
|
{
|
|
if (szfSection.Name.Equals("Metadata", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
// Metadata is handled by PopulateFromMetadata, so skip here
|
|
continue;
|
|
}
|
|
|
|
if (szfSection.Name.StartsWith("Section:", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
// This is a top-level section (e.g., [Section: Character Information])
|
|
var sectionName = szfSection.Name.Replace("Section:", "").Trim();
|
|
var characterSection = new CharacterSection { Name = sectionName };
|
|
|
|
foreach (var field in szfSection.Fields)
|
|
{
|
|
characterSection.Fields.Add(new CharacterField
|
|
{
|
|
Name = field.Name,
|
|
Type = field.Type, // SzfField.Type is expected to be a string (e.g., "text", "number")
|
|
Value = field.Value
|
|
});
|
|
}
|
|
|
|
tempTopLevelCharacterSections.Add(characterSection);
|
|
sectionNameMap[sectionName] = characterSection; // Store by its simplified name for easy lookup
|
|
}
|
|
else if (szfSection.Name.StartsWith("Section.", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
// This is a subsection (e.g., [Section.Ability Scores.Modifiers])
|
|
var parts = szfSection.Name.Split('.');
|
|
if (parts.Length < 2) continue; // Malformed section name
|
|
|
|
// The path to the immediate parent (e.g., "Ability Scores" for "Ability Scores.Modifiers")
|
|
var parentPathSegments = new List<string>();
|
|
// Start from the first segment after "Section." and go up to the segment before the current section's name
|
|
for (int i = 1; i < parts.Length - 1; i++)
|
|
{
|
|
parentPathSegments.Add(parts[i]);
|
|
}
|
|
|
|
var parentLookupKey = string.Join(".", parentPathSegments);
|
|
|
|
// Find the direct parent section within the already processed hierarchy
|
|
CharacterSection? parentSection =
|
|
FindCharacterSectionByPath(tempTopLevelCharacterSections, parentLookupKey);
|
|
|
|
if (parentSection != null)
|
|
{
|
|
var subSection = new CharacterSection
|
|
{
|
|
Name = parts.Last().Trim() // The last part of the name is the subsection's own name
|
|
};
|
|
|
|
foreach (var field in szfSection.Fields)
|
|
{
|
|
subSection.Fields.Add(new CharacterField
|
|
{
|
|
Name = field.Name,
|
|
Type = field.Type,
|
|
Value = field.Value
|
|
});
|
|
}
|
|
|
|
parentSection.Subsections.Add(subSection);
|
|
}
|
|
// If parent not found, it implies an issue with SZF structure or order.
|
|
// For now, we silently skip it. In a robust system, you might log an error.
|
|
}
|
|
else
|
|
{
|
|
// Handle any other root-level sections that are not "Metadata"
|
|
// and don't conform to the "Section:..." or "Section...." patterns.
|
|
// These are treated as flat character sections.
|
|
var characterSection = new CharacterSection { Name = szfSection.Name.Trim() };
|
|
foreach (var field in szfSection.Fields)
|
|
{
|
|
characterSection.Fields.Add(new CharacterField
|
|
{
|
|
Name = field.Name,
|
|
Type = field.Type,
|
|
Value = field.Value
|
|
});
|
|
}
|
|
|
|
tempTopLevelCharacterSections.Add(characterSection);
|
|
}
|
|
}
|
|
|
|
CharacterSections.AddRange(tempTopLevelCharacterSections);
|
|
}
|
|
|
|
// Helper method to find a section by its hierarchical path (e.g., "ParentName.SubName")
|
|
private CharacterSection? FindCharacterSectionByPath(List<CharacterSection> sections, string path)
|
|
{
|
|
var segments = path.Split('.');
|
|
CharacterSection? currentSection = null;
|
|
List<CharacterSection> currentSearchList = sections;
|
|
|
|
foreach (var segment in segments)
|
|
{
|
|
currentSection =
|
|
currentSearchList.FirstOrDefault(s => s.Name.Equals(segment, StringComparison.OrdinalIgnoreCase));
|
|
if (currentSection == null) return null; // Segment not found in the current level
|
|
|
|
currentSearchList = currentSection.Subsections; // Move to subsections for the next segment
|
|
}
|
|
|
|
return currentSection;
|
|
}
|
|
|
|
public override void GenerateMetadata(SzfFieldWriter writer)
|
|
{
|
|
// Ensure common SzfObject metadata (Name, Version, Description, Author) is written first
|
|
base.GenerateMetadata(writer);
|
|
|
|
// Write Character-specific metadata fields
|
|
writer.WriteField("SourceTemplateGuid", "text", TemplateGuid.ToString());
|
|
writer.WriteField("SourceTemplateVersion", "text", TemplateVersion.ToString());
|
|
}
|
|
|
|
public override void GenerateContent(SzfFieldWriter writer)
|
|
{
|
|
// Generate all the character data sections
|
|
foreach (var section in CharacterSections)
|
|
{
|
|
// For top-level sections, the prefix is "Section". This will result in [Section: Name]
|
|
GenerateCharacterSection(writer, section, "Section");
|
|
writer.AppendLine(); // Add a blank line after each top-level section for readability
|
|
}
|
|
}
|
|
|
|
private void GenerateCharacterSection(SzfFieldWriter writer, CharacterSection section,
|
|
string parentSectionHeaderPrefix)
|
|
{
|
|
// Construct the full section header string based on the current nesting level
|
|
string currentSectionHeader;
|
|
if (parentSectionHeaderPrefix == "Section")
|
|
{
|
|
// Top-level section, e.g., [Section: Character Information]
|
|
currentSectionHeader = $"Section: {section.Name}";
|
|
}
|
|
else
|
|
{
|
|
// Nested section, e.g., [Section.Ability Scores.Modifiers]
|
|
currentSectionHeader = $"{parentSectionHeaderPrefix}.{section.Name}";
|
|
}
|
|
|
|
writer.AppendSectionHeader(currentSectionHeader);
|
|
|
|
// 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);
|
|
}
|
|
|
|
// Recursively generate all subsections
|
|
foreach (var subSection in section.Subsections)
|
|
{
|
|
GenerateCharacterSection(writer, subSection, currentSectionHeader);
|
|
}
|
|
}
|
|
} |