From 0349ebf2bc850e5411744b77da04ed424efc4d7e Mon Sep 17 00:00:00 2001 From: Jonathan McCaffrey Date: Tue, 29 Sep 2026 18:34:44 +0000 Subject: [PATCH] =?UTF-8?q?AI:=20Planetary=20items=20page=20=E2=80=94=20Ze?= =?UTF-8?q?roSpace/ZeroSpace.Build/Program.cs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ZeroSpace/ZeroSpace.Build/Program.cs | 138 +++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) diff --git a/ZeroSpace/ZeroSpace.Build/Program.cs b/ZeroSpace/ZeroSpace.Build/Program.cs index 5845c72..db75d24 100644 --- a/ZeroSpace/ZeroSpace.Build/Program.cs +++ b/ZeroSpace/ZeroSpace.Build/Program.cs @@ -71,6 +71,7 @@ if (skipped.Count > 0) var starSystems = new List(); var systemMasteries = new List(); +var planetaryMasteryItems = new List(); var skippedDocs = new List(); var usedIds = new HashSet(StringComparer.Ordinal); @@ -104,19 +105,66 @@ foreach (var path in Directory.EnumerateFiles(docsRoot, "*.md", SearchOption.All skippedDocs.Add($"{Path.GetFileName(path)} ({reason})"); } } + else if (string.Equals(categoryValue.Scalar, "PlanetaryMasteryItem", StringComparison.OrdinalIgnoreCase)) + { + if (TryReadPlanetaryMasteryItem(path, frontMatter, body, usedIds, out var item, out var reason)) + { + planetaryMasteryItems.Add(item); + } + else + { + skippedDocs.Add($"{Path.GetFileName(path)} ({reason})"); + } + } } starSystems.Sort((left, right) => string.Compare(left.Name, right.Name, StringComparison.OrdinalIgnoreCase)); systemMasteries.Sort((left, right) => string.Compare(left.Name, right.Name, StringComparison.OrdinalIgnoreCase)); +planetaryMasteryItems.Sort((left, right) => string.Compare(left.Name, right.Name, StringComparison.OrdinalIgnoreCase)); var starSystemsPath = Path.Combine(modelRoot, "StarSystems.g.cs"); var systemMasteriesPath = Path.Combine(modelRoot, "SystemMasteries.g.cs"); +var planetaryMasteryItemsPath = Path.Combine(modelRoot, "PlanetaryMasteryItems.g.cs"); File.WriteAllText(starSystemsPath, GenerateStarSystems(starSystems), Encoding.UTF8); File.WriteAllText(systemMasteriesPath, GenerateSystemMasteries(systemMasteries), Encoding.UTF8); +File.WriteAllText(planetaryMasteryItemsPath, GeneratePlanetaryMasteryItems(planetaryMasteryItems), Encoding.UTF8); Console.WriteLine($"Generated {starSystems.Count} star systems: {Path.GetRelativePath(solutionRoot, starSystemsPath)}"); Console.WriteLine($"Generated {systemMasteries.Count} system masteries: {Path.GetRelativePath(solutionRoot, systemMasteriesPath)}"); +Console.WriteLine($"Generated {planetaryMasteryItems.Count} planetary mastery items: {Path.GetRelativePath(solutionRoot, planetaryMasteryItemsPath)}"); + +// Copy item icons next to the markdown docs into the web app's wwwroot so the +// items page can reference them. Images are copied, never moved: the docs folder +// stays the source of truth. +var itemImagesDir = Path.Combine(solutionRoot, "ZeroSpace.Web", "wwwroot", "images", "planetary-mastery-items"); +Directory.CreateDirectory(itemImagesDir); + +var expectedImages = new HashSet(StringComparer.OrdinalIgnoreCase); +var copiedImages = 0; +foreach (var item in planetaryMasteryItems) +{ + if (item.ImageFileName is null || item.ImageSourcePath is null) + { + continue; + } + + expectedImages.Add(item.ImageFileName); + var destination = Path.Combine(itemImagesDir, item.ImageFileName); + File.Copy(item.ImageSourcePath, destination, overwrite: true); + copiedImages++; +} + +foreach (var stale in Directory.EnumerateFiles(itemImagesDir, "*.png")) +{ + if (!expectedImages.Contains(Path.GetFileName(stale))) + { + File.Delete(stale); + Console.WriteLine($"Removed stale item image: {Path.GetRelativePath(solutionRoot, stale)}"); + } +} + +Console.WriteLine($"Copied {copiedImages} planetary mastery item images to {Path.GetRelativePath(solutionRoot, itemImagesDir)}"); if (skippedDocs.Count > 0) { Console.WriteLine($"Skipped {skippedDocs.Count} incomplete docs:"); @@ -661,6 +709,51 @@ static bool TryReadSystemMastery( return true; } +static bool TryReadPlanetaryMasteryItem( + string path, + IReadOnlyDictionary frontMatter, + string body, + HashSet usedIds, + out PlanetaryMasteryItemSource item, + out string reason) +{ + item = default!; + + var levels = new string[5]; + for (var level = 1; level <= 5; level++) + { + if (!TryGetRequiredString(frontMatter, $"level_{level}", out var text, out reason)) + { + return false; + } + + levels[level - 1] = text; + } + + var name = Path.GetFileNameWithoutExtension(path); + + // An icon with the same file name as the doc (e.g. "Battery.png" next to + // "Battery.md") is treated as this item's image when present. + string? imageFileName = null; + string? imageSourcePath = null; + var candidateImagePath = Path.ChangeExtension(path, ".png"); + if (File.Exists(candidateImagePath)) + { + imageFileName = Path.GetFileName(candidateImagePath); + imageSourcePath = candidateImagePath; + } + + item = new PlanetaryMasteryItemSource( + ToUniqueIdentifier(name, usedIds), + name, + levels, + imageFileName, + imageSourcePath, + body); + reason = string.Empty; + return true; +} + static bool IsMasteryField(string key) { if (string.Equals(key, "category", StringComparison.OrdinalIgnoreCase) || @@ -766,6 +859,43 @@ static string GenerateSystemMasteries(IReadOnlyList masteri return builder.ToString(); } +static string GeneratePlanetaryMasteryItems(IReadOnlyList items) +{ + var builder = new StringBuilder(); + builder.AppendLine("// "); + builder.AppendLine("namespace ZeroSpace.Model;"); + builder.AppendLine(); + builder.AppendLine("public static class PlanetaryMasteryItems"); + builder.AppendLine("{"); + + foreach (var item in items) + { + builder.AppendLine($" public static readonly PlanetaryMasteryItemData {item.Id} = new("); + builder.AppendLine($" Id: {Literal(item.Id)},"); + builder.AppendLine($" Name: {Literal(item.Name)},"); + builder.AppendLine($" Levels: [{string.Join(", ", item.Levels.Select(Literal))}],"); + builder.AppendLine($" ImageFileName: {Literal(item.ImageFileName)},"); + builder.AppendLine($" Body: {Literal(item.Body)});"); + builder.AppendLine(); + } + + builder.AppendLine(" public static IReadOnlyList All { get; } ="); + builder.AppendLine(" ["); + + foreach (var item in items) + { + builder.AppendLine($" {item.Id},"); + } + + builder.AppendLine(" ];"); + builder.AppendLine(); + builder.AppendLine(" public static IReadOnlyDictionary ByName { get; } ="); + builder.AppendLine(" All.ToDictionary(static item => item.Name, StringComparer.OrdinalIgnoreCase);"); + builder.AppendLine("}"); + + return builder.ToString(); +} + static string BoolLiteral(bool value) { return value ? "true" : "false"; @@ -833,3 +963,11 @@ internal sealed record SystemMasterySource( IReadOnlyList AvailableSystems, IReadOnlyList<(string Key, double Value)> Stats, string Body); + +internal sealed record PlanetaryMasteryItemSource( + string Id, + string Name, + IReadOnlyList Levels, + string? ImageFileName, + string? ImageSourcePath, + string Body);