diff --git a/viewmodels/mainwindowviewmodel.cs b/viewmodels/mainwindowviewmodel.cs
index d9461c2..a3e520f 100644
--- a/viewmodels/mainwindowviewmodel.cs
+++ b/viewmodels/mainwindowviewmodel.cs
@@ -9,6 +9,9 @@ using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using finder2e_foundry_converter.Models;
using finder2e_foundry_converter.Services;
+using System.Text.Json.Nodes;
+using System.Text.Json;
+using finder2e_foundry_converter.Converters;
namespace finder2e_foundry_converter.ViewModels
{
@@ -132,18 +135,128 @@ namespace finder2e_foundry_converter.ViewModels
}
}
+ [ObservableProperty]
+ private string _generatedNpcJson = string.Empty;
+
+ [ObservableProperty]
+ private string _temporaryNpcPath = string.Empty;
+
[RelayCommand]
private async Task Generate()
{
+ // Minimal validation
if (string.IsNullOrEmpty(SelectedModel))
{
StatusMessage = "Please select a model first.";
return;
}
- StatusMessage = "Generating...";
- var result = await CurrentService.GenerateResponseAsync(CurrentAddress, SelectedModel, Description, ImageItems.Select(i => i.Path).ToList());
- StatusMessage = result;
+ StatusMessage = "Generating NPC from template...";
+
+ try
+ {
+ // Load template JSON
+ var cwd = Directory.GetCurrentDirectory();
+ var templatePath = Path.Combine(cwd, "foundry_templates", "npc_template.json");
+ if (!File.Exists(templatePath))
+ {
+ StatusMessage = $"Template not found at {templatePath}";
+ return;
+ }
+
+ var templateText = await File.ReadAllTextAsync(templatePath);
+ var node = JsonNode.Parse(templateText)!.AsObject();
+
+ // Fill basic fields
+ node["name"] = string.IsNullOrWhiteSpace(Name) ? "NPC" : Name;
+ node["img"] = node["img"] ?? "systems/pf2e/icons/default-icons/npc.svg";
+
+ // system defaults
+ var systemNode = node["system"] as JsonObject ?? new JsonObject();
+ node["system"] = systemNode;
+
+ // Set description/blurb and level
+ if (!systemNode.TryGetPropertyValue("details", out var detailsNode) || detailsNode is null)
+ {
+ detailsNode = new JsonObject();
+ systemNode["details"] = detailsNode;
+ }
+ var details = detailsNode.AsObject();
+ details["blurb"] = Description ?? string.Empty;
+ if (!details.TryGetPropertyValue("level", out var levelNode) || levelNode is null)
+ {
+ details["level"] = new JsonObject();
+ }
+ details["level"].AsObject()["value"] = SelectedLevel;
+
+ // Default to pf2e for now
+ string gameSystem = "pf2e";
+
+ // Compute numeric stats using NpcConverter helper functions with conservative tiers
+ var abilityTier = "moderate";
+ var perceptionTier = "moderate";
+ var acTier = "moderate";
+ var savingTier = "moderate";
+ var hpTier = "moderate";
+
+ var abilities = new JsonObject();
+ string[] abilityNames = new[] { "str", "dex", "con", "int", "wis", "cha" };
+ foreach (var ab in abilityNames)
+ {
+ var modRes = NpcConverter.AbilityScoreModifier(SelectedLevel, abilityTier, gameSystem);
+ abilities[ab] = new JsonObject { ["mod"] = modRes.IsSuccess ? modRes.Value : 0 };
+ }
+ systemNode["abilities"] = abilities;
+
+ // Perception
+ var percRes = NpcConverter.PerceptionModifier(SelectedLevel, perceptionTier, gameSystem);
+ systemNode["perception"] = new JsonObject { ["mod"] = percRes.IsSuccess ? percRes.Value : 0, ["details"] = string.Empty };
+
+ // Saves
+ var fort = NpcConverter.SavingThrow(SelectedLevel, savingTier, gameSystem);
+ var reflex = NpcConverter.SavingThrow(SelectedLevel, savingTier, gameSystem);
+ var will = NpcConverter.SavingThrow(SelectedLevel, savingTier, gameSystem);
+ systemNode["saves"] = new JsonObject
+ {
+ ["fortitude"] = new JsonObject { ["value"] = fort.IsSuccess ? fort.Value : 0, ["saveDetail"] = string.Empty },
+ ["reflex"] = new JsonObject { ["value"] = reflex.IsSuccess ? reflex.Value : 0, ["saveDetail"] = string.Empty },
+ ["will"] = new JsonObject { ["value"] = will.IsSuccess ? will.Value : 0, ["saveDetail"] = string.Empty }
+ };
+
+ // AC
+ var acRes = NpcConverter.Ac(SelectedLevel, acTier, gameSystem);
+ if (!systemNode.TryGetPropertyValue("attributes", out var attributesNode) || attributesNode is null)
+ {
+ attributesNode = new JsonObject();
+ systemNode["attributes"] = attributesNode;
+ }
+ var attributes = attributesNode.AsObject();
+ attributes["ac"] = new JsonObject { ["value"] = acRes.IsSuccess ? acRes.Value : 10, ["details"] = string.Empty };
+
+ // HP
+ var hpRes = NpcConverter.Hp(SelectedLevel, hpTier, gameSystem);
+ attributes["hp"] = new JsonObject { ["value"] = hpRes.IsSuccess ? hpRes.Value : 10, ["temp"] = 0, ["max"] = hpRes.IsSuccess ? hpRes.Value : 10, ["details"] = string.Empty };
+
+ // Skills (leave empty for now)
+ systemNode["skills"] = new JsonObject();
+
+ // Finalize JSON text
+ GeneratedNpcJson = node.ToJsonString(new JsonSerializerOptions { WriteIndented = true });
+
+ // Save to a temporary file while generating
+ var tempDir = Path.Combine(Path.GetTempPath(), "Finder2eFoundryConverter");
+ if (!Directory.Exists(tempDir)) Directory.CreateDirectory(tempDir);
+ var safeName = string.IsNullOrWhiteSpace(Name) ? "npc" : string.Concat(Name.Select(ch => Path.GetInvalidFileNameChars().Contains(ch) ? '_' : ch));
+ var tempPath = Path.Combine(tempDir, $"{safeName}_{DateTime.Now:yyyyMMddHHmmss}.json");
+ await File.WriteAllTextAsync(tempPath, GeneratedNpcJson);
+ TemporaryNpcPath = tempPath;
+
+ StatusMessage = $"NPC generated and saved to temporary path: {tempPath}";
+ }
+ catch (Exception ex)
+ {
+ StatusMessage = $"Error generating NPC: {ex.Message}";
+ }
}
[RelayCommand]
diff --git a/views/mainwindow.axaml b/views/mainwindow.axaml
index 356b360..2fa5188 100644
--- a/views/mainwindow.axaml
+++ b/views/mainwindow.axaml
@@ -63,8 +63,17 @@
-
+
+
+
+
+
+
+
+
+
+
diff --git a/views/mainwindow.axaml.cs b/views/mainwindow.axaml.cs
index 9a068dd..774b59a 100644
--- a/views/mainwindow.axaml.cs
+++ b/views/mainwindow.axaml.cs
@@ -10,6 +10,7 @@ using Avalonia.Interactivity;
using Avalonia.Platform.Storage;
using Avalonia.Media.Imaging;
using finder2e_foundry_converter.ViewModels;
+using System.Diagnostics;
namespace finder2e_foundry_converter.Views
{
@@ -175,5 +176,77 @@ namespace finder2e_foundry_converter.Views
}
}
+ private async void SaveNpc_Click(object? sender, RoutedEventArgs e)
+ {
+ var vm = DataContext as MainWindowViewModel;
+ if (vm == null)
+ return;
+
+ if (string.IsNullOrWhiteSpace(vm.GeneratedNpcJson))
+ {
+ vm.StatusMessage = "No generated NPC to save.";
+ return;
+ }
+
+ var topLevel = GetTopLevel(this);
+ if (topLevel == null) return;
+
+ var save = await topLevel.StorageProvider.SaveFilePickerAsync(new FilePickerSaveOptions
+ {
+ Title = "Save NPC JSON",
+ SuggestedFileName = string.IsNullOrWhiteSpace(vm.Name) ? "npc.json" : vm.Name + ".json",
+ FileTypeChoices = new[]
+ {
+ new FilePickerFileType("JSON") { Patterns = new[] { "*.json" } }
+ }
+ });
+
+ if (save != null)
+ {
+ try
+ {
+ using var stream = await save.OpenWriteAsync();
+ using var writer = new StreamWriter(stream);
+ await writer.WriteAsync(vm.GeneratedNpcJson);
+ vm.StatusMessage = $"Saved NPC to {save.Path?.LocalPath ?? "(unknown)"}";
+ }
+ catch (Exception ex)
+ {
+ vm.StatusMessage = $"Error saving NPC: {ex.Message}";
+ }
+ }
+ }
+
+ private void OpenTempFolder_Click(object? sender, RoutedEventArgs e)
+ {
+ var vm = DataContext as MainWindowViewModel;
+ if (vm == null || string.IsNullOrWhiteSpace(vm.TemporaryNpcPath))
+ {
+ if (vm != null) vm.StatusMessage = "No temporary NPC file available.";
+ return;
+ }
+
+ try
+ {
+ var folder = Path.GetDirectoryName(vm.TemporaryNpcPath);
+ if (!string.IsNullOrWhiteSpace(folder) && Directory.Exists(folder))
+ {
+ Process.Start(new ProcessStartInfo
+ {
+ FileName = folder,
+ UseShellExecute = true
+ });
+ }
+ else
+ {
+ vm.StatusMessage = "Temporary folder not found.";
+ }
+ }
+ catch (Exception ex)
+ {
+ vm.StatusMessage = $"Error opening folder: {ex.Message}";
+ }
+ }
+
}
}