json handling
This commit is contained in:
@@ -9,6 +9,9 @@ using CommunityToolkit.Mvvm.ComponentModel;
|
|||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using finder2e_foundry_converter.Models;
|
using finder2e_foundry_converter.Models;
|
||||||
using finder2e_foundry_converter.Services;
|
using finder2e_foundry_converter.Services;
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
|
using System.Text.Json;
|
||||||
|
using finder2e_foundry_converter.Converters;
|
||||||
|
|
||||||
namespace finder2e_foundry_converter.ViewModels
|
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]
|
[RelayCommand]
|
||||||
private async Task Generate()
|
private async Task Generate()
|
||||||
{
|
{
|
||||||
|
// Minimal validation
|
||||||
if (string.IsNullOrEmpty(SelectedModel))
|
if (string.IsNullOrEmpty(SelectedModel))
|
||||||
{
|
{
|
||||||
StatusMessage = "Please select a model first.";
|
StatusMessage = "Please select a model first.";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
StatusMessage = "Generating...";
|
StatusMessage = "Generating NPC from template...";
|
||||||
var result = await CurrentService.GenerateResponseAsync(CurrentAddress, SelectedModel, Description, ImageItems.Select(i => i.Path).ToList());
|
|
||||||
StatusMessage = result;
|
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]
|
[RelayCommand]
|
||||||
|
|||||||
@@ -65,6 +65,15 @@
|
|||||||
Command="{Binding GenerateCommand}" FontWeight="Bold"/>
|
Command="{Binding GenerateCommand}" FontWeight="Bold"/>
|
||||||
|
|
||||||
<TextBlock Text="{Binding StatusMessage}" TextWrapping="Wrap" Margin="0,10,0,0"/>
|
<TextBlock Text="{Binding StatusMessage}" TextWrapping="Wrap" Margin="0,10,0,0"/>
|
||||||
|
|
||||||
|
<StackPanel Spacing="5" Margin="0,10,0,0" IsVisible="{Binding IsNpcSelected}">
|
||||||
|
<Label Content="Generated NPC JSON:"/>
|
||||||
|
<TextBox Text="{Binding GeneratedNpcJson}" AcceptsReturn="True" Height="200" IsReadOnly="True" />
|
||||||
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Spacing="5">
|
||||||
|
<Button Content="Save NPC..." Click="SaveNpc_Click"/>
|
||||||
|
<Button Content="Open Temp Folder" Click="OpenTempFolder_Click"/>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ using Avalonia.Interactivity;
|
|||||||
using Avalonia.Platform.Storage;
|
using Avalonia.Platform.Storage;
|
||||||
using Avalonia.Media.Imaging;
|
using Avalonia.Media.Imaging;
|
||||||
using finder2e_foundry_converter.ViewModels;
|
using finder2e_foundry_converter.ViewModels;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
namespace finder2e_foundry_converter.Views
|
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}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user