From 321c481c7dc12274bb3ea182c0f4999d8255d0ae Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 21 Nov 2023 17:40:06 +0100 Subject: [PATCH] Add option to import .chara files as design or onto actors/designs. --- Glamourer/Designs/DesignBase.cs | 10 + Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 22 +- Glamourer/Gui/Tabs/DebugTab.cs | 10 +- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 17 +- Glamourer/Gui/Tabs/DesignTab/DesignTab.cs | 25 +- Glamourer/Interop/CharaFile/CharaFile.cs | 295 ++++++++++++++++++ .../{DatFileService.cs => ImportService.cs} | 79 +++-- Glamourer/Services/ServiceManager.cs | 2 +- 8 files changed, 412 insertions(+), 48 deletions(-) create mode 100644 Glamourer/Interop/CharaFile/CharaFile.cs rename Glamourer/Interop/{DatFileService.cs => ImportService.cs} (59%) diff --git a/Glamourer/Designs/DesignBase.cs b/Glamourer/Designs/DesignBase.cs index 4ef567e..183ca99 100644 --- a/Glamourer/Designs/DesignBase.cs +++ b/Glamourer/Designs/DesignBase.cs @@ -32,6 +32,16 @@ public class DesignBase CustomizationSet = SetCustomizationSet(customize); } + internal DesignBase(CustomizationService customize, in DesignData designData, EquipFlag equipFlags, CustomizeFlag customizeFlags) + { + _designData = designData; + ApplyCustomize = customizeFlags & CustomizeFlagExtensions.AllRelevant; + ApplyEquip = equipFlags & EquipFlagExtensions.All; + _designFlags = 0; + CustomizationSet = SetCustomizationSet(customize); + + } + internal DesignBase(DesignBase clone) { _designData = clone._designData; diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index 6963053..d049eec 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -36,7 +36,7 @@ public class ActorPanel private readonly DesignConverter _converter; private readonly ObjectManager _objects; private readonly DesignManager _designManager; - private readonly DatFileService _datFileService; + private readonly ImportService _importService; private readonly ICondition _conditions; private ActorIdentifier _identifier; @@ -48,7 +48,7 @@ public class ActorPanel public ActorPanel(ActorSelector selector, StateManager stateManager, CustomizationDrawer customizationDrawer, EquipmentDrawer equipmentDrawer, IdentifierService identification, AutoDesignApplier autoDesignApplier, - Configuration config, DesignConverter converter, ObjectManager objects, DesignManager designManager, DatFileService datFileService, + Configuration config, DesignConverter converter, ObjectManager objects, DesignManager designManager, ImportService importService, ICondition conditions) { _selector = selector; @@ -61,7 +61,7 @@ public class ActorPanel _converter = converter; _objects = objects; _designManager = designManager; - _datFileService = datFileService; + _importService = importService; _conditions = conditions; } @@ -81,9 +81,21 @@ public class ActorPanel if (_state is not { IsLocked: false }) return; - if (_datFileService.CreateImGuiTarget(out var dat)) + if (_importService.CreateDatTarget(out var dat)) + { _stateManager.ChangeCustomize(_state!, dat.Customize, CustomizeApplicationFlags, StateChanged.Source.Manual); - _datFileService.CreateSource(); + Glamourer.Messager.NotificationMessage($"Applied games .dat file {dat.Description} customizations to {_state.Identifier}.", + NotificationType.Success, false); + } + else if (_importService.CreateCharaTarget(out var designBase, out var name)) + { + _stateManager.ApplyDesign(designBase, _state!, StateChanged.Source.Manual); + Glamourer.Messager.NotificationMessage($"Applied Anamnesis .chara file {name} to {_state.Identifier}.", NotificationType.Success, + false); + } + + _importService.CreateDatSource(); + _importService.CreateCharaSource(); } private void DrawHeader() diff --git a/Glamourer/Gui/Tabs/DebugTab.cs b/Glamourer/Gui/Tabs/DebugTab.cs index 3b07b33..7d946fb 100644 --- a/Glamourer/Gui/Tabs/DebugTab.cs +++ b/Glamourer/Gui/Tabs/DebugTab.cs @@ -50,7 +50,7 @@ public unsafe class DebugTab : ITab private readonly ObjectManager _objectManager; private readonly GlamourerIpc _ipc; private readonly CodeService _code; - private readonly DatFileService _datFileService; + private readonly ImportService _importService; private readonly ItemManager _items; private readonly ActorService _actors; @@ -81,7 +81,7 @@ public unsafe class DebugTab : ITab DesignFileSystem designFileSystem, DesignManager designManager, StateManager state, Configuration config, PenumbraChangedItemTooltip penumbraTooltip, MetaService metaService, GlamourerIpc ipc, DalamudPluginInterface pluginInterface, AutoDesignManager autoDesignManager, JobService jobs, CodeService code, CustomizeUnlockManager customizeUnlocks, - ItemUnlockManager itemUnlocks, DesignConverter designConverter, DatFileService datFileService, InventoryService inventoryService, + ItemUnlockManager itemUnlocks, DesignConverter designConverter, ImportService importService, InventoryService inventoryService, HumanModelList humans, FunModule funModule) { _changeCustomizeService = changeCustomizeService; @@ -107,7 +107,7 @@ public unsafe class DebugTab : ITab _customizeUnlocks = customizeUnlocks; _itemUnlocks = itemUnlocks; _designConverter = designConverter; - _datFileService = datFileService; + _importService = importService; _inventoryService = inventoryService; _humans = humans; _funModule = funModule; @@ -284,10 +284,10 @@ public unsafe class DebugTab : ITab ImGui.InputTextWithHint("##datFilePath", "Dat File Path...", ref _datFilePath, 256); var exists = _datFilePath.Length > 0 && File.Exists(_datFilePath); if (ImGuiUtil.DrawDisabledButton("Load##Dat", Vector2.Zero, string.Empty, !exists)) - _datFile = _datFileService.LoadDesign(_datFilePath, out var tmp) ? tmp : null; + _datFile = _importService.LoadDat(_datFilePath, out var tmp) ? tmp : null; if (ImGuiUtil.DrawDisabledButton("Save##Dat", Vector2.Zero, string.Empty, _datFilePath.Length == 0 || _datFile == null)) - _datFileService.SaveDesign(_datFilePath, _datFile!.Value.Customize, _datFile!.Value.Description); + _importService.SaveDesignAsDat(_datFilePath, _datFile!.Value.Customize, _datFile!.Value.Description); if (_datFile != null) { diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index c5e6943..bd6e197 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Numerics; +using System.Xml.Linq; using Dalamud.Interface; using Dalamud.Interface.ImGuiFileDialog; using Dalamud.Interface.Internal.Notifications; @@ -25,7 +26,7 @@ namespace Glamourer.Gui.Tabs.DesignTab; public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer _customizationDrawer, DesignManager _manager, ObjectManager _objects, StateManager _state, EquipmentDrawer _equipmentDrawer, ModAssociationsTab _modAssociations, - DesignDetailTab _designDetails, DesignConverter _converter, DatFileService _datFileService, MultiDesignPanel _multiDesignPanel) + DesignDetailTab _designDetails, DesignConverter _converter, ImportService _importService, MultiDesignPanel _multiDesignPanel) { private readonly FileDialogManager _fileDialog = new(); @@ -299,16 +300,22 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer if (_selector.Selected == null || _selector.Selected.WriteProtected()) return; - if (_datFileService.CreateImGuiTarget(out var dat)) + if (_importService.CreateDatTarget(out var dat)) { _manager.ChangeCustomize(_selector.Selected!, CustomizeIndex.Clan, dat.Customize[CustomizeIndex.Clan]); _manager.ChangeCustomize(_selector.Selected!, CustomizeIndex.Gender, dat.Customize[CustomizeIndex.Gender]); foreach (var idx in CustomizationExtensions.AllBasic) _manager.ChangeCustomize(_selector.Selected!, idx, dat.Customize[idx]); + Glamourer.Messager.NotificationMessage($"Applied games .dat file {dat.Description} customizations to {_selector.Selected.Name}.", NotificationType.Success, false); + } + else if (_importService.CreateCharaTarget(out var designBase, out var name)) + { + _manager.ApplyDesign(_selector.Selected!, designBase); + Glamourer.Messager.NotificationMessage($"Applied Anamnesis .chara file {name} to {_selector.Selected.Name}.", NotificationType.Success, false); } } - _datFileService.CreateSource(); + _importService.CreateDatSource(); } private void DrawPanel() @@ -417,7 +424,7 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer private void DrawSaveToDat() { - var verified = _datFileService.Verify(_selector.Selected!.DesignData.Customize, out _); + var verified = _importService.Verify(_selector.Selected!.DesignData.Customize, out _); var tt = verified ? "Export the currently configured customizations of this design to a character creation data file." : "The current design contains customizations that can not be applied during character creation."; @@ -428,7 +435,7 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer _fileDialog.SaveFileDialog("Save File...", ".dat", "FFXIV_CHARA_01.dat", ".dat", (v, path) => { if (v && _selector.Selected != null) - _datFileService.SaveDesign(path, _selector.Selected!.DesignData.Customize, _selector.Selected!.Name); + _importService.SaveDesignAsDat(path, _selector.Selected!.DesignData.Customize, _selector.Selected!.Name); }, startPath); _fileDialog.Draw(); diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignTab.cs b/Glamourer/Gui/Tabs/DesignTab/DesignTab.cs index 243fc9c..1c5a0b9 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignTab.cs @@ -1,29 +1,32 @@ using System; +using Dalamud.Interface.Internal.Notifications; using Dalamud.Interface.Utility; +using Glamourer.Designs; +using Glamourer.Interop; using ImGuiNET; +using OtterGui.Classes; using OtterGui.Widgets; namespace Glamourer.Gui.Tabs.DesignTab; -public class DesignTab : ITab +public class DesignTab(DesignFileSystemSelector _selector, DesignPanel _panel, ImportService _importService, DesignManager _manager) + : ITab { - public readonly DesignFileSystemSelector Selector; - private readonly DesignPanel _panel; - - public DesignTab(DesignFileSystemSelector selector, DesignPanel panel) - { - Selector = selector; - _panel = panel; - } - public ReadOnlySpan Label => "Designs"u8; public void DrawContent() { - Selector.Draw(GetDesignSelectorSize()); + _selector.Draw(GetDesignSelectorSize()); + if (_importService.CreateCharaTarget(out var designBase, out var name)) + { + var newDesign = _manager.CreateClone(designBase, name, true); + Glamourer.Messager.NotificationMessage($"Imported Anamnesis .chara file {name} as new design {newDesign.Name}", NotificationType.Success, false); + } + ImGui.SameLine(); _panel.Draw(); + _importService.CreateCharaSource(); } public float GetDesignSelectorSize() diff --git a/Glamourer/Interop/CharaFile/CharaFile.cs b/Glamourer/Interop/CharaFile/CharaFile.cs new file mode 100644 index 0000000..6f56acc --- /dev/null +++ b/Glamourer/Interop/CharaFile/CharaFile.cs @@ -0,0 +1,295 @@ +using System; +using Glamourer.Customization; +using Glamourer.Designs; +using Glamourer.Services; +using Glamourer.Structs; +using Newtonsoft.Json.Linq; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; +using Race = Penumbra.GameData.Enums.Race; + +namespace Glamourer.Interop.CharaFile; + +public sealed class CharaFile +{ + public string Name = string.Empty; + public DesignData Data = new(); + public CustomizeFlag ApplyCustomize; + public EquipFlag ApplyEquip; + + public static CharaFile? ParseData(ItemManager items, string data, string? name = null) + { + try + { + var jObj = JObject.Parse(data); + SanityCheck(jObj); + var ret = new CharaFile(); + ret.Data.SetDefaultEquipment(items); + ret.Data.ModelId = ParseModelId(jObj); + ret.Name = jObj["Nickname"]?.ToObject() ?? name ?? "New Design"; + ret.ApplyCustomize = ParseCustomize(jObj, ref ret.Data.Customize); + ret.ApplyEquip = ParseEquipment(items, jObj, ref ret.Data); + return ret; + } + catch (Exception ex) + { + return null; + } + } + + private static EquipFlag ParseEquipment(ItemManager items, JObject jObj, ref DesignData data) + { + EquipFlag ret = 0; + ParseWeapon(items, jObj, "MainHand", EquipSlot.MainHand, ref data, ref ret); + ParseWeapon(items, jObj, "OffHand", EquipSlot.OffHand, ref data, ref ret); + ParseGear(items, jObj, "HeadGear", EquipSlot.Head, ref data, ref ret); + ParseGear(items, jObj, "Body", EquipSlot.Body, ref data, ref ret); + ParseGear(items, jObj, "Hands", EquipSlot.Hands, ref data, ref ret); + ParseGear(items, jObj, "Legs", EquipSlot.Legs, ref data, ref ret); + ParseGear(items, jObj, "Feet", EquipSlot.Feet, ref data, ref ret); + ParseGear(items, jObj, "Ears", EquipSlot.Ears, ref data, ref ret); + ParseGear(items, jObj, "Neck", EquipSlot.Neck, ref data, ref ret); + ParseGear(items, jObj, "Wrists", EquipSlot.Wrists, ref data, ref ret); + ParseGear(items, jObj, "LeftRing", EquipSlot.LFinger, ref data, ref ret); + ParseGear(items, jObj, "RightRing", EquipSlot.RFinger, ref data, ref ret); + return ret; + } + + private static void ParseWeapon(ItemManager items, JObject jObj, string property, EquipSlot slot, ref DesignData data, ref EquipFlag flags) + { + var jTok = jObj[property]; + if (jTok == null) + return; + + var set = jTok["ModelSet"]?.ToObject() ?? 0; + var type = jTok["ModelBase"]?.ToObject() ?? 0; + var variant = jTok["ModelVariant"]?.ToObject() ?? 0; + var dye = jTok["DyeId"]?.ToObject() ?? 0; + var item = items.Identify(slot, set, type, variant, slot is EquipSlot.OffHand ? data.MainhandType : FullEquipType.Unknown); + if (!item.Valid) + return; + + data.SetItem(slot, item); + data.SetStain(slot, (StainId)dye); + flags |= slot.ToFlag(); + flags |= slot.ToStainFlag(); + } + + private static void ParseGear(ItemManager items, JObject jObj, string property, EquipSlot slot, ref DesignData data, ref EquipFlag flags) + { + var jTok = jObj[property]; + if (jTok == null) + return; + + var set = jTok["ModelBase"]?.ToObject() ?? 0; + var variant = jTok["ModelVariant"]?.ToObject() ?? 0; + var dye = jTok["DyeId"]?.ToObject() ?? 0; + var item = items.Identify(slot, set, variant); + if (!item.Valid) + return; + + data.SetItem(slot, item); + data.SetStain(slot, dye); + flags |= slot.ToFlag(); + flags |= slot.ToStainFlag(); + } + + private static CustomizeFlag ParseCustomize(JObject jObj, ref Customize customize) + { + CustomizeFlag ret = 0; + customize.Race = ParseRace(jObj, ref ret); + customize.Gender = ParseGender(jObj, ref ret); + customize.Clan = ParseTribe(jObj, ref ret); + ParseByte(jObj, "Height", CustomizeIndex.Height, ref customize, ref ret); + ParseByte(jObj, "Head", CustomizeIndex.Face, ref customize, ref ret); + ParseByte(jObj, "Hair", CustomizeIndex.Hairstyle, ref customize, ref ret); + ParseHighlights(jObj, ref customize, ref ret); + ParseByte(jObj, "Skintone", CustomizeIndex.SkinColor, ref customize, ref ret); + ParseByte(jObj, "REyeColor", CustomizeIndex.EyeColorRight, ref customize, ref ret); + ParseByte(jObj, "HairTone", CustomizeIndex.HairColor, ref customize, ref ret); + ParseByte(jObj, "Highlights", CustomizeIndex.HighlightsColor, ref customize, ref ret); + ParseFacial(jObj, ref customize, ref ret); + ParseByte(jObj, "LimbalEyes", CustomizeIndex.TattooColor, ref customize, ref ret); + ParseByte(jObj, "Eyebrows", CustomizeIndex.Eyebrows, ref customize, ref ret); + ParseByte(jObj, "LEyeColor", CustomizeIndex.EyeColorLeft, ref customize, ref ret); + ParseByte(jObj, "Eyes", CustomizeIndex.EyeShape, ref customize, ref ret); + ParseByte(jObj, "Nose", CustomizeIndex.Nose, ref customize, ref ret); + ParseByte(jObj, "Jaw", CustomizeIndex.Jaw, ref customize, ref ret); + ParseByte(jObj, "Mouth", CustomizeIndex.Mouth, ref customize, ref ret); + ParseByte(jObj, "LipsToneFurPattern", CustomizeIndex.LipColor, ref customize, ref ret); + ParseByte(jObj, "EarMuscleTailSize", CustomizeIndex.MuscleMass, ref customize, ref ret); + ParseByte(jObj, "TailEarsType", CustomizeIndex.TailShape, ref customize, ref ret); + ParseByte(jObj, "Bust", CustomizeIndex.BustSize, ref customize, ref ret); + ParseByte(jObj, "FacePaint", CustomizeIndex.FacePaint, ref customize, ref ret); + ParseByte(jObj, "FacePaintColor", CustomizeIndex.FacePaintColor, ref customize, ref ret); + ParseAge(jObj); + + if (ret.HasFlag(CustomizeFlag.EyeShape)) + ret |= CustomizeFlag.SmallIris; + + if (ret.HasFlag(CustomizeFlag.Mouth)) + ret |= CustomizeFlag.Lipstick; + + if (ret.HasFlag(CustomizeFlag.FacePaint)) + ret |= CustomizeFlag.FacePaintReversed; + + return ret; + } + + private static uint ParseModelId(JObject jObj) + { + var jTok = jObj["ModelType"]; + if (jTok == null) + throw new Exception("No Model ID given."); + + var id = jTok.ToObject(); + if (id != 0) + throw new Exception($"Model ID {id} != 0 not supported."); + + return id; + } + + private static void ParseFacial(JObject jObj, ref Customize customize, ref CustomizeFlag application) + { + var jTok = jObj["FacialFeatures"]; + if (jTok == null) + return; + + application |= CustomizeFlag.FacialFeature1 + | CustomizeFlag.FacialFeature2 + | CustomizeFlag.FacialFeature3 + | CustomizeFlag.FacialFeature4 + | CustomizeFlag.FacialFeature5 + | CustomizeFlag.FacialFeature6 + | CustomizeFlag.FacialFeature7 + | CustomizeFlag.LegacyTattoo; + + var value = jTok.ToObject()!; + if (value is "None") + return; + + if (value.Contains("First")) + customize[CustomizeIndex.FacialFeature1] = CustomizeValue.Max; + if (value.Contains("Second")) + customize[CustomizeIndex.FacialFeature2] = CustomizeValue.Max; + if (value.Contains("Third")) + customize[CustomizeIndex.FacialFeature3] = CustomizeValue.Max; + if (value.Contains("Fourth")) + customize[CustomizeIndex.FacialFeature4] = CustomizeValue.Max; + if (value.Contains("Fifth")) + customize[CustomizeIndex.FacialFeature5] = CustomizeValue.Max; + if (value.Contains("Sixth")) + customize[CustomizeIndex.FacialFeature6] = CustomizeValue.Max; + if (value.Contains("Seventh")) + customize[CustomizeIndex.FacialFeature7] = CustomizeValue.Max; + if (value.Contains("LegacyTattoo")) + customize[CustomizeIndex.LegacyTattoo] = CustomizeValue.Max; + } + + private static void ParseHighlights(JObject jObj, ref Customize customize, ref CustomizeFlag application) + { + var jTok = jObj["EnableHighlights"]; + if (jTok == null) + return; + + var value = jTok.ToObject(); + application |= CustomizeFlag.Highlights; + customize[CustomizeIndex.Highlights] = value ? CustomizeValue.Max : CustomizeValue.Zero; + } + + private static Race ParseRace(JObject jObj, ref CustomizeFlag application) + { + var race = jObj["Race"]?.ToObject() switch + { + null => Race.Unknown, + "Hyur" => Race.Hyur, + "Elezen" => Race.Elezen, + "Lalafel" => Race.Lalafell, + "Miqote" => Race.Miqote, + "Roegadyn" => Race.Roegadyn, + "AuRa" => Race.AuRa, + "Hrothgar" => Race.Hrothgar, + "Viera" => Race.Viera, + _ => throw new Exception($"Invalid Race value {jObj["Race"]?.ToObject()}."), + }; + if (race == Race.Unknown) + return Race.Hyur; + + application |= CustomizeFlag.Race; + return race; + } + + private static Gender ParseGender(JObject jObj, ref CustomizeFlag application) + { + var gender = jObj["Gender"]?.ToObject() switch + { + null => Gender.Unknown, + "Masculine" => Gender.Male, + "Feminine" => Gender.Female, + _ => throw new Exception($"Invalid Gender value {jObj["Gender"]?.ToObject()}."), + }; + if (gender == Gender.Unknown) + return Gender.Male; + + application |= CustomizeFlag.Gender; + return gender; + } + + private static void ParseAge(JObject jObj) + { + var age = jObj["Age"]?.ToObject(); + if (age is not null and not "Normal") + throw new Exception($"Age {age} != Normal is not supported."); + } + + private static unsafe void ParseByte(JObject jObj, string property, CustomizeIndex idx, ref Customize customize, + ref CustomizeFlag application) + { + var jTok = jObj[property]; + if (jTok == null) + return; + + customize.Data.Data[idx.ToByteAndMask().ByteIdx] = jTok.ToObject(); + application |= idx.ToFlag(); + } + + private static SubRace ParseTribe(JObject jObj, ref CustomizeFlag application) + { + var tribe = jObj["Tribe"]?.ToObject() switch + { + null => SubRace.Unknown, + "Midlander" => SubRace.Midlander, + "Highlander" => SubRace.Highlander, + "Wildwood" => SubRace.Wildwood, + "Duskwight" => SubRace.Duskwight, + "Plainsfolk" => SubRace.Plainsfolk, + "Dunesfolk" => SubRace.Dunesfolk, + "SeekerOfTheSun" => SubRace.SeekerOfTheSun, + "KeeperOfTheMoon" => SubRace.KeeperOfTheMoon, + "SeaWolf" => SubRace.Seawolf, + "Hellsguard" => SubRace.Hellsguard, + "Raen" => SubRace.Raen, + "Xaela" => SubRace.Xaela, + "Helions" => SubRace.Helion, + "TheLost" => SubRace.Lost, + "Rava" => SubRace.Rava, + "Veena" => SubRace.Veena, + _ => throw new Exception($"Invalid Tribe value {jObj["Tribe"]?.ToObject()}."), + }; + if (tribe == SubRace.Unknown) + return SubRace.Midlander; + + application |= CustomizeFlag.Clan; + return tribe; + } + + private static void SanityCheck(JObject jObj) + { + if (jObj["TypeName"]?.ToObject() is not "Anamnesis Character File") + throw new Exception("Wrong TypeName property set."); + + var type = jObj["ObjectKind"]?.ToObject(); + if (type is not "Player") + throw new Exception($"ObjectKind {type} != Player is not supported."); + } +} diff --git a/Glamourer/Interop/DatFileService.cs b/Glamourer/Interop/ImportService.cs similarity index 59% rename from Glamourer/Interop/DatFileService.cs rename to Glamourer/Interop/ImportService.cs index 0d27bcc..2681abb 100644 --- a/Glamourer/Interop/DatFileService.cs +++ b/Glamourer/Interop/ImportService.cs @@ -1,39 +1,34 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using Dalamud.Interface.DragDrop; using Dalamud.Interface.Internal.Notifications; using Glamourer.Customization; +using Glamourer.Designs; using Glamourer.Services; -using Glamourer.Unlocks; using ImGuiNET; using OtterGui.Classes; namespace Glamourer.Interop; -public class DatFileService +public class ImportService(CustomizationService _customizations, IDragDropManager _dragDropManager, ItemManager _items) { - private readonly CustomizationService _customizations; - private readonly CustomizeUnlockManager _unlocks; - private readonly IDragDropManager _dragDropManager; - - public DatFileService(CustomizationService customizations, CustomizeUnlockManager unlocks, IDragDropManager dragDropManager) - { - _customizations = customizations; - _unlocks = unlocks; - _dragDropManager = dragDropManager; - } - - public void CreateSource() - { - _dragDropManager.CreateImGuiSource("DatDragger", m => m.Files.Count == 1 && m.Extensions.Contains(".dat"), m => + public void CreateDatSource() + => _dragDropManager.CreateImGuiSource("DatDragger", m => m.Files.Count == 1 && m.Extensions.Contains(".dat"), m => { ImGui.TextUnformatted($"Dragging {Path.GetFileName(m.Files[0])} to import customizations for Glamourer..."); return true; }); - } - public bool CreateImGuiTarget(out DatCharacterFile file) + public void CreateCharaSource() + => _dragDropManager.CreateImGuiSource("CharaDragger", m => m.Files.Count == 1 && m.Extensions.Contains(".chara"), m => + { + ImGui.TextUnformatted($"Dragging {Path.GetFileName(m.Files[0])} to import Anamnesis data for Glamourer..."); + return true; + }); + + public bool CreateDatTarget(out DatCharacterFile file) { if (!_dragDropManager.CreateImGuiTarget("DatDragger", out var files, out _) || files.Count != 1) { @@ -41,10 +36,52 @@ public class DatFileService return false; } - return LoadDesign(files[0], out file); + return LoadDat(files[0], out file); } - public bool LoadDesign(string path, out DatCharacterFile file) + public bool CreateCharaTarget([NotNullWhen(true)] out DesignBase? design, out string name) + { + if (!_dragDropManager.CreateImGuiTarget("CharaDragger", out var files, out _) || files.Count != 1) + { + design = null; + name = string.Empty; + return false; + } + + return LoadChara(files[0], out design, out name); + } + + public bool LoadChara(string path, [NotNullWhen(true)] out DesignBase? design, out string name) + { + if (!File.Exists(path)) + { + design = null; + name = string.Empty; + return false; + } + + try + { + var text = File.ReadAllText(path); + var file = CharaFile.CharaFile.ParseData(_items, text, Path.GetFileNameWithoutExtension(path)); + if (file == null) + throw new Exception(); + + name = file.Name; + design = new DesignBase(_customizations, file.Data, file.ApplyEquip, file.ApplyCustomize); + } + catch (Exception ex) + { + Glamourer.Messager.NotificationMessage(ex, $"Could not read .chara file {path}.", NotificationType.Error); + design = null; + name = string.Empty; + return false; + } + + return true; + } + + public bool LoadDat(string path, out DatCharacterFile file) { if (!File.Exists(path)) { @@ -70,7 +107,7 @@ public class DatFileService return true; } - public bool SaveDesign(string path, in Customize input, string description) + public bool SaveDesignAsDat(string path, in Customize input, string description) { if (!Verify(input, out var voice)) return false; diff --git a/Glamourer/Services/ServiceManager.cs b/Glamourer/Services/ServiceManager.cs index 19ade32..6e9cffa 100644 --- a/Glamourer/Services/ServiceManager.cs +++ b/Glamourer/Services/ServiceManager.cs @@ -96,7 +96,7 @@ public static class ServiceManager .AddSingleton() .AddSingleton() .AddSingleton() - .AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton();