Rework game path selection a bit.

This commit is contained in:
Ottermandias 2024-01-05 19:02:50 +01:00
parent 55f38865e3
commit c33545acdf
2 changed files with 93 additions and 49 deletions

View file

@ -12,27 +12,29 @@ public partial class ModEditWindow
{
private ModEditWindow _edit;
public readonly MdlFile Mdl;
public readonly MdlFile Mdl;
private readonly List<string>[] _attributes;
public List<Utf8GamePath>? GamePaths { get; private set ;}
public int GamePathIndex;
public bool PendingIo { get; private set; } = false;
public List<Utf8GamePath>? GamePaths { get; private set; }
public int GamePathIndex;
public bool PendingIo { get; private set; } = false;
public string? IoException { get; private set; } = null;
[GeneratedRegex(@"chara/(?:equipment|accessory)/(?'Set'[a-z]\d{4})/model/(?'Race'c\d{4})\k'Set'_[^/]+\.mdl", RegexOptions.Compiled)]
private static partial Regex CharaEquipmentRegex();
[GeneratedRegex(@"chara/human/(?'Race'c\d{4})/obj/(?'Type'[^/]+)/(?'Set'[^/]\d{4})/model/(?'Race'c\d{4})\k'Set'_[^/]+\.mdl", RegexOptions.Compiled)]
[GeneratedRegex(@"chara/human/(?'Race'c\d{4})/obj/(?'Type'[^/]+)/(?'Set'[^/]\d{4})/model/(?'Race'c\d{4})\k'Set'_[^/]+\.mdl",
RegexOptions.Compiled)]
private static partial Regex CharaHumanRegex();
[GeneratedRegex(@"chara/(?'SubCategory'demihuman|monster|weapon)/(?'Set'w\d{4})/obj/body/(?'Body'b\d{4})/model/\k'Set'\k'Body'.mdl", RegexOptions.Compiled)]
[GeneratedRegex(@"chara/(?'SubCategory'demihuman|monster|weapon)/(?'Set'w\d{4})/obj/body/(?'Body'b\d{4})/model/\k'Set'\k'Body'.mdl",
RegexOptions.Compiled)]
private static partial Regex CharaBodyRegex();
public MdlTab(ModEditWindow edit, byte[] bytes, string path, Mod? mod)
{
_edit = edit;
_edit = edit;
Mdl = new MdlFile(bytes);
_attributes = CreateAttributes(Mdl);
@ -54,21 +56,29 @@ public partial class ModEditWindow
/// <param name="mod"> Mod within which the .mdl is resolved. </param>
private void FindGamePaths(string path, Mod mod)
{
if (!Path.IsPathRooted(path) && Utf8GamePath.FromString(path, out var p))
{
GamePaths = [p];
return;
}
PendingIo = true;
var task = Task.Run(() => {
var task = Task.Run(() =>
{
// TODO: Is it worth trying to order results based on option priorities for cases where more than one match is found?
// NOTE: We're using case insensitive comparisons, as option group paths in mods are stored in lower case, but the mod editor uses paths directly from the file system, which may be mixed case.
// NOTE: We're using case-insensitive comparisons, as option group paths in mods are stored in lower case, but the mod editor uses paths directly from the file system, which may be mixed case.
return mod.AllSubMods
.SelectMany(submod => submod.Files.Concat(submod.FileSwaps))
.SelectMany(m => m.Files.Concat(m.FileSwaps))
.Where(kv => kv.Value.FullName.Equals(path, StringComparison.OrdinalIgnoreCase))
.Select(kv => kv.Key)
.ToList();
});
task.ContinueWith(task => {
IoException = task.Exception?.ToString();
PendingIo = false;
GamePaths = task.Result;
task.ContinueWith(t =>
{
IoException = t.Exception?.ToString();
PendingIo = false;
GamePaths = t.Result;
});
}
@ -77,19 +87,23 @@ public partial class ModEditWindow
public void Export(string outputPath, Utf8GamePath mdlPath)
{
SklbFile? sklb = null;
try {
try
{
var sklbPath = GetSklbPath(mdlPath.ToString());
sklb = sklbPath != null ? ReadSklb(sklbPath) : null;
} catch (Exception exception) {
}
catch (Exception exception)
{
IoException = exception?.ToString();
return;
}
PendingIo = true;
_edit._models.ExportToGltf(Mdl, sklb, outputPath)
.ContinueWith(task => {
.ContinueWith(task =>
{
IoException = task.Exception?.ToString();
PendingIo = false;
PendingIo = false;
});
}
@ -114,7 +128,7 @@ public partial class ModEditWindow
return type switch
{
"body" or "tail" => $"chara/human/{race}/skeleton/base/b0001/skl_{race}b0001.sklb",
_ => throw new Exception($"Currently unsupported human model type \"{type}\"."),
_ => throw new Exception($"Currently unsupported human model type \"{type}\"."),
};
}
@ -123,7 +137,7 @@ public partial class ModEditWindow
if (match.Success)
{
var subCategory = match.Groups["SubCategory"].Value;
var set = match.Groups["Set"].Value;
var set = match.Groups["Set"].Value;
return $"chara/{subCategory}/{set}/skeleton/base/b0001/skl_{set}b0001.sklb";
}
@ -137,16 +151,17 @@ public partial class ModEditWindow
// TODO: if cross-collection lookups are turned off, this conversion can be skipped
if (!Utf8GamePath.FromString(sklbPath, out var utf8SklbPath, true))
throw new Exception($"Resolved skeleton path {sklbPath} could not be converted to a game path.");
var resolvedPath = _edit._activeCollections.Current.ResolvePath(utf8SklbPath);
// TODO: is it worth trying to use streams for these instead? i'll need to do this for mtrl/tex too, so might be a good idea. that said, the mtrl reader doesn't accept streams, so...
var bytes = resolvedPath switch
{
null => _edit._gameData.GetFile(sklbPath)?.Data,
null => _edit._gameData.GetFile(sklbPath)?.Data,
FullPath path => File.ReadAllBytes(path.ToPath()),
};
if (bytes == null)
throw new Exception($"Resolved skeleton path {sklbPath} could not be found. If modded, is it enabled in the current collection?");
throw new Exception(
$"Resolved skeleton path {sklbPath} could not be found. If modded, is it enabled in the current collection?");
return new SklbFile(bytes);
}

View file

@ -20,6 +20,8 @@ public partial class ModEditWindow
private string _modelNewMaterial = string.Empty;
private readonly List<TagButtons> _subMeshAttributeTagWidgets = [];
private string _customPath = string.Empty;
private Utf8GamePath _customGamePath = Utf8GamePath.Empty;
private bool DrawModelPanel(MdlTab tab, bool disabled)
{
@ -51,10 +53,6 @@ public partial class ModEditWindow
private void DrawExport(MdlTab tab, bool disabled)
{
// IO on a disabled panel doesn't really make sense.
if (disabled)
return;
if (!ImGui.CollapsingHeader("Export"))
return;
@ -70,16 +68,14 @@ public partial class ModEditWindow
DrawGamePathCombo(tab);
if (ImGuiUtil.DrawDisabledButton("Export to glTF", Vector2.Zero, "Exports this mdl file to glTF, for use in 3D authoring applications.", tab.PendingIo))
{
var gamePath = tab.GamePaths[tab.GamePathIndex];
_fileDialog.OpenSavePicker(
"Save model as glTF.",
".gltf",
Path.GetFileNameWithoutExtension(gamePath.Filename().ToString()),
".gltf",
(valid, path) => {
var gamePath = tab.GamePathIndex >= 0 && tab.GamePathIndex < tab.GamePaths.Count
? tab.GamePaths[tab.GamePathIndex]
: _customGamePath;
if (ImGuiUtil.DrawDisabledButton("Export to glTF", Vector2.Zero, "Exports this mdl file to glTF, for use in 3D authoring applications.",
tab.PendingIo || gamePath.IsEmpty))
_fileDialog.OpenSavePicker("Save model as glTF.", ".gltf", Path.GetFileNameWithoutExtension(gamePath.Filename().ToString()),
".gltf", (valid, path) =>
{
if (!valid)
return;
@ -88,27 +84,60 @@ public partial class ModEditWindow
_mod!.ModPath.FullName,
false
);
}
if (tab.IoException != null)
ImGuiUtil.TextWrapped(tab.IoException);
return;
}
private void DrawGamePathCombo(MdlTab tab)
{
using var combo = ImRaii.Combo("Game Path", tab.GamePaths![tab.GamePathIndex].ToString());
if (!combo)
return;
foreach (var (path, index) in tab.GamePaths.WithIndex())
if (tab.GamePaths!.Count == 0)
{
if (!ImGui.Selectable(path.ToString(), index == tab.GamePathIndex))
continue;
ImGui.TextUnformatted("No associated game path detected. Valid game paths are currently necessary for exporting.");
if (ImGui.InputTextWithHint("##customInput", "Enter custom game path...", ref _customPath, 256))
if (!Utf8GamePath.FromString(_customPath, out _customGamePath, false))
_customGamePath = Utf8GamePath.Empty;
tab.GamePathIndex = index;
return;
}
DrawComboButton(tab);
}
private static void DrawComboButton(MdlTab tab)
{
const string label = "Game Path";
var preview = tab.GamePaths![tab.GamePathIndex].ToString();
var labelWidth = ImGui.CalcTextSize(label).X + ImGui.GetStyle().ItemInnerSpacing.X;
var buttonWidth = ImGui.GetContentRegionAvail().X - labelWidth;
if (tab.GamePaths!.Count == 1)
{
using var style = ImRaii.PushStyle(ImGuiStyleVar.ButtonTextAlign, new Vector2(0, 0.5f));
using var color = ImRaii.PushColor(ImGuiCol.Button, ImGui.GetColorU32(ImGuiCol.FrameBg))
.Push(ImGuiCol.ButtonHovered, ImGui.GetColorU32(ImGuiCol.FrameBgHovered))
.Push(ImGuiCol.ButtonActive, ImGui.GetColorU32(ImGuiCol.FrameBgActive));
using var group = ImRaii.Group();
ImGui.Button(preview, new Vector2(buttonWidth, 0));
ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X);
ImGui.TextUnformatted("Game Path");
}
else
{
ImGui.SetNextItemWidth(buttonWidth);
using var combo = ImRaii.Combo("Game Path", preview);
if (combo.Success)
foreach (var (path, index) in tab.GamePaths.WithIndex())
{
if (!ImGui.Selectable(path.ToString(), index == tab.GamePathIndex))
continue;
tab.GamePathIndex = index;
}
}
if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
ImGui.SetClipboardText(preview);
ImGuiUtil.HoverTooltip("Right-Click to copy to clipboard.", ImGuiHoveredFlags.AllowWhenDisabled);
}
private bool DrawModelMaterialDetails(MdlTab tab, bool disabled)