From 1a4672a901fbfb2e0276ea57a0cac99aa642b06d Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 6 Nov 2022 18:28:20 +0100 Subject: [PATCH] More stuff. --- .../Customization/CustomizationSet.cs | 21 +- Glamourer/Api/PenumbraAttach.cs | 2 +- Glamourer/Designs/FixedDesigns.cs | 7 +- Glamourer/Glamourer.cs | 8 +- Glamourer/Gui/ComboWithFilter.cs | 207 -------- .../Gui/Equipment/EquipmentDrawer.Items.cs | 3 +- .../Gui/Equipment/EquipmentDrawer.Main.cs | 8 +- .../Gui/Equipment/EquipmentDrawer.Misc.cs | 20 +- Glamourer/Gui/Interface.Actors.cs | 76 +-- Glamourer/Gui/Interface.DebugStateTab.cs | 15 +- Glamourer/Interop/Actor.Identifier.cs | 375 -------------- Glamourer/Interop/Actor.cs | 16 +- Glamourer/Interop/DrawObject.cs | 3 + Glamourer/Interop/IDesignable.cs | 1 + Glamourer/Interop/ObjectManager.cs | 67 +-- Glamourer/Saves/Design.cs | 479 +++++++++++------- Glamourer/Saves/DesignFlagsV1.cs | 15 + Glamourer/Saves/EquipmentDesign.cs | 115 +++++ Glamourer/Saves/HumanDesign.cs | 67 +++ Glamourer/State/ApplicationFlags.cs | 75 --- Glamourer/State/CharacterSave.cs | 27 +- Glamourer/State/CurrentManipulations.cs | 11 +- Glamourer/State/FixedDesigns.cs | 3 +- 23 files changed, 616 insertions(+), 1005 deletions(-) delete mode 100644 Glamourer/Gui/ComboWithFilter.cs delete mode 100644 Glamourer/Interop/Actor.Identifier.cs create mode 100644 Glamourer/Saves/DesignFlagsV1.cs create mode 100644 Glamourer/Saves/EquipmentDesign.cs create mode 100644 Glamourer/Saves/HumanDesign.cs delete mode 100644 Glamourer/State/ApplicationFlags.cs diff --git a/Glamourer.GameData/Customization/CustomizationSet.cs b/Glamourer.GameData/Customization/CustomizationSet.cs index d68aa09..c1160d7 100644 --- a/Glamourer.GameData/Customization/CustomizationSet.cs +++ b/Glamourer.GameData/Customization/CustomizationSet.cs @@ -14,23 +14,23 @@ public class CustomizationSet { internal CustomizationSet(SubRace clan, Gender gender) { - Gender = gender; - Clan = clan; - Race = clan.ToRace(); - _settingAvailable = 0; + Gender = gender; + Clan = clan; + Race = clan.ToRace(); + SettingAvailable = 0; } public Gender Gender { get; } public SubRace Clan { get; } public Race Race { get; } - private CustomizeFlag _settingAvailable; + public CustomizeFlag SettingAvailable { get; internal set; } internal void SetAvailable(CustomizeIndex index) - => _settingAvailable |= index.ToFlag(); + => SettingAvailable |= index.ToFlag(); public bool IsAvailable(CustomizeIndex index) - => _settingAvailable.HasFlag(index.ToFlag()); + => SettingAvailable.HasFlag(index.ToFlag()); // Meta public IReadOnlyList OptionName { get; internal set; } = null!; @@ -138,10 +138,11 @@ public class CustomizationSet CharaMakeParams.MenuType.ListSelector => GetInteger(out custom), CharaMakeParams.MenuType.IconSelector => index switch { - CustomizeIndex.Face => Get(Faces, HrothgarFaceHack(value), out custom), - CustomizeIndex.Hairstyle => Get((face = HrothgarFaceHack(face)).Value < HairByFace.Count ? HairByFace[face.Value] : HairStyles, value, out custom), + CustomizeIndex.Face => Get(Faces, HrothgarFaceHack(value), out custom), + CustomizeIndex.Hairstyle => Get((face = HrothgarFaceHack(face)).Value < HairByFace.Count ? HairByFace[face.Value] : HairStyles, + value, out custom), CustomizeIndex.TailShape => Get(TailEarShapes, value, out custom), - CustomizeIndex.FacePaint => Get(FacePaints, value, out custom), + CustomizeIndex.FacePaint => Get(FacePaints, value, out custom), CustomizeIndex.LipColor => Get(LipColorsDark, value, out custom), _ => Invalid(out custom), }, diff --git a/Glamourer/Api/PenumbraAttach.cs b/Glamourer/Api/PenumbraAttach.cs index 56b2d06..241cbad 100644 --- a/Glamourer/Api/PenumbraAttach.cs +++ b/Glamourer/Api/PenumbraAttach.cs @@ -108,7 +108,7 @@ public unsafe class PenumbraAttach : IDisposable if (button != MouseButton.Right || type != ChangedItemType.Item) return; - var item = (Lumina.Excel.GeneratedSheets.Item)type.GetObject(id)!; + var item = (Lumina.Excel.GeneratedSheets.Item)type.GetObject(Dalamud.GameData, id)!; var writeItem = new Item(item, string.Empty); UpdateItem(ObjectManager.GPosePlayer, writeItem); diff --git a/Glamourer/Designs/FixedDesigns.cs b/Glamourer/Designs/FixedDesigns.cs index 70aa629..fb8e15a 100644 --- a/Glamourer/Designs/FixedDesigns.cs +++ b/Glamourer/Designs/FixedDesigns.cs @@ -13,6 +13,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Penumbra.GameData.Structs; using Glamourer.Saves; +using Penumbra.GameData.Actors; namespace Glamourer.Designs; @@ -52,7 +53,7 @@ public class FixedDesign public string Name { get; private set; } public bool Enabled; - public List Actors; + public List Actors; public List<(FixedCondition, Design)> Customization; public List<(FixedCondition, Design)> Equipment; public List<(FixedCondition, Design)> Weapons; @@ -60,7 +61,7 @@ public class FixedDesign public FixedDesign(string name) { Name = name; - Actors = new List(); + Actors = new List(); Customization = new List<(FixedCondition, Design)>(); Equipment = new List<(FixedCondition, Design)>(); Weapons = new List<(FixedCondition, Design)>(); @@ -125,7 +126,7 @@ public class FixedDesign j.WritePropertyName(nameof(Actors)); j.WriteStartArray(); foreach (var actor in Actors) - actor.ToJson(j); + actor.ToJson().WriteTo(j); j.WriteEndArray(); j.WritePropertyName(nameof(Customization)); j.WriteStartArray(); diff --git a/Glamourer/Glamourer.cs b/Glamourer/Glamourer.cs index 6aa21e3..c5c7618 100644 --- a/Glamourer/Glamourer.cs +++ b/Glamourer/Glamourer.cs @@ -9,6 +9,7 @@ using Glamourer.Interop; using Glamourer.State; using OtterGui.Log; using Penumbra.GameData; +using Penumbra.GameData.Actors; namespace Glamourer; @@ -31,6 +32,7 @@ public class Glamourer : IDalamudPlugin public static Logger Log = null!; public static IObjectIdentifier Identifier = null!; + public static ActorManager Actors = null!; public static PenumbraAttach Penumbra = null!; public static ICustomizationManager Customization = null!; public static RestrictedGear RestrictedGear = null!; @@ -60,8 +62,10 @@ public class Glamourer : IDalamudPlugin Config = GlamourerConfig.Load(); - Identifier = global::Penumbra.GameData.GameData.GetIdentifier(Dalamud.GameData); + Identifier = global::Penumbra.GameData.GameData.GetIdentifier(Dalamud.PluginInterface, Dalamud.GameData); Penumbra = new PenumbraAttach(Config.AttachToPenumbra); + Actors = new ActorManager(Dalamud.PluginInterface, Dalamud.Objects, Dalamud.ClientState, Dalamud.GameData, + i => (short)Penumbra.CutsceneParent(i)); FixedDesigns = new FixedDesigns(); CurrentManipulations = new CurrentManipulations(); //Designs = new DesignManager(); @@ -93,6 +97,8 @@ public class Glamourer : IDalamudPlugin public void Dispose() { + + Dalamud.PluginInterface.RelinquishData("test1"); RedrawManager?.Dispose(); Penumbra?.Dispose(); if (_windowSystem != null) diff --git a/Glamourer/Gui/ComboWithFilter.cs b/Glamourer/Gui/ComboWithFilter.cs deleted file mode 100644 index c14d525..0000000 --- a/Glamourer/Gui/ComboWithFilter.cs +++ /dev/null @@ -1,207 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Numerics; -using ImGuiNET; - -namespace Glamourer.Gui; - -public class ComboWithFilter -{ - private readonly string _label; - private readonly string _filterLabel; - private readonly string _listLabel; - private string _currentFilter = string.Empty; - private string _currentFilterLower = string.Empty; - private bool _focus; - private readonly float _size; - private float _previewSize; - private readonly IReadOnlyList _items; - private readonly IReadOnlyList<(string, int)> _itemNamesLower; - private readonly Func _itemToName; - private IReadOnlyList<(string, int)> _currentItemNames; - private bool _needsClear; - - public Action? PrePreview; - public Action? PostPreview; - public Func? CreateSelectable; - public Action? PreList; - public Action? PostList; - public float? HeightPerItem; - - private float _heightPerItem; - - public ImGuiComboFlags Flags { get; set; } = ImGuiComboFlags.None; - public int ItemsAtOnce { get; set; } = 12; - - private void UpdateFilter(string newFilter) - { - if (newFilter == _currentFilter) - return; - - var lower = newFilter.ToLowerInvariant(); - if (_currentFilterLower.Any() && lower.Contains(_currentFilterLower)) - _currentItemNames = _currentItemNames.Where(p => p.Item1.Contains(lower)).ToArray(); - else if (lower.Any()) - _currentItemNames = _itemNamesLower.Where(p => p.Item1.Contains(lower)).ToArray(); - else - _currentItemNames = _itemNamesLower; - _currentFilter = newFilter; - _currentFilterLower = lower; - } - - public ComboWithFilter(string label, float size, float previewSize, IReadOnlyList items, Func itemToName) - { - _label = label; - _filterLabel = $"##_{label}_filter"; - _listLabel = $"##_{label}_list"; - _itemToName = itemToName; - _items = items; - _size = size; - _previewSize = previewSize; - - _itemNamesLower = _items.Select((i, idx) => (_itemToName(i).ToLowerInvariant(), idx)).ToArray(); - _currentItemNames = _itemNamesLower; - } - - public ComboWithFilter(string label, ComboWithFilter other) - { - _label = label; - _filterLabel = $"##_{label}_filter"; - _listLabel = $"##_{label}_list"; - _itemToName = other._itemToName; - _items = other._items; - _itemNamesLower = other._itemNamesLower; - _currentItemNames = other._currentItemNames; - _size = other._size; - _previewSize = other._previewSize; - PrePreview = other.PrePreview; - PostPreview = other.PostPreview; - CreateSelectable = other.CreateSelectable; - PreList = other.PreList; - PostList = other.PostList; - HeightPerItem = other.HeightPerItem; - Flags = other.Flags; - } - - private bool DrawList(string currentName, out int numItems, out int nodeIdx, ref T? value) - { - numItems = ItemsAtOnce; - nodeIdx = -1; - if (!ImGui.BeginChild(_listLabel, new Vector2(_size, ItemsAtOnce * _heightPerItem))) - { - ImGui.EndChild(); - return false; - } - - var ret = false; - try - { - if (!_focus) - { - ImGui.SetScrollY(0); - _focus = true; - } - - var scrollY = Math.Max((int)(ImGui.GetScrollY() / _heightPerItem) - 1, 0); - var restHeight = scrollY * _heightPerItem; - numItems = 0; - nodeIdx = 0; - - if (restHeight > 0) - ImGui.Dummy(Vector2.UnitY * restHeight); - - for (var i = scrollY; i < _currentItemNames.Count; ++i) - { - if (++numItems > ItemsAtOnce + 2) - continue; - - nodeIdx = _currentItemNames[i].Item2; - var item = _items[nodeIdx]!; - bool success; - if (CreateSelectable != null) - { - success = CreateSelectable(item); - } - else - { - var name = _itemToName(item); - success = ImGui.Selectable(name, name == currentName); - } - - if (success) - { - value = item; - ImGui.CloseCurrentPopup(); - ret = true; - } - } - - if (_currentItemNames.Count > ItemsAtOnce + 2) - ImGui.Dummy(Vector2.UnitY * (_currentItemNames.Count - ItemsAtOnce - 2 - scrollY) * _heightPerItem); - } - finally - { - ImGui.EndChild(); - } - - return ret; - } - - public bool Draw(string currentName, out T? value, float? size = null) - { - if (size.HasValue) - _previewSize = size.Value; - - value = default; - ImGui.SetNextItemWidth(_previewSize); - PrePreview?.Invoke(); - if (!ImGui.BeginCombo(_label, currentName, Flags)) - { - if (_needsClear) - { - _needsClear = false; - _focus = false; - UpdateFilter(string.Empty); - } - - PostPreview?.Invoke(); - return false; - } - - _needsClear = true; - PostPreview?.Invoke(); - - _heightPerItem = HeightPerItem ?? ImGui.GetTextLineHeightWithSpacing(); - - bool ret; - try - { - ImGui.SetNextItemWidth(-1); - var tmp = _currentFilter; - if (ImGui.InputTextWithHint(_filterLabel, "Filter...", ref tmp, 255)) - UpdateFilter(tmp); - - var isFocused = ImGui.IsItemActive(); - if (!_focus) - ImGui.SetKeyboardFocusHere(); - - PreList?.Invoke(); - ret = DrawList(currentName, out var numItems, out var nodeIdx, ref value); - PostList?.Invoke(); - - if (!isFocused && numItems <= 1 && nodeIdx >= 0) - { - value = _items[nodeIdx]; - ret = true; - ImGui.CloseCurrentPopup(); - } - } - finally - { - ImGui.EndCombo(); - } - - return ret; - } -} diff --git a/Glamourer/Gui/Equipment/EquipmentDrawer.Items.cs b/Glamourer/Gui/Equipment/EquipmentDrawer.Items.cs index 453632b..5e9bcf9 100644 --- a/Glamourer/Gui/Equipment/EquipmentDrawer.Items.cs +++ b/Glamourer/Gui/Equipment/EquipmentDrawer.Items.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using Dalamud.Interface; using ImGuiNET; using Lumina.Excel.GeneratedSheets; @@ -354,7 +355,7 @@ public partial class EquipmentDrawer { 0 => SmallClothes, 9903 => SmallClothesNpc, - _ => Identifier.Identify(set, weapon, variant, slot) ?? Unknown, + _ => Identifier.Identify(set, weapon, variant, slot).FirstOrDefault(Unknown), }; } } diff --git a/Glamourer/Gui/Equipment/EquipmentDrawer.Main.cs b/Glamourer/Gui/Equipment/EquipmentDrawer.Main.cs index 9f6837a..144c9dd 100644 --- a/Glamourer/Gui/Equipment/EquipmentDrawer.Main.cs +++ b/Glamourer/Gui/Equipment/EquipmentDrawer.Main.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; using Glamourer.Customization; using Glamourer.Interop; -using Glamourer.State; using ImGuiNET; using OtterGui.Raii; using Penumbra.GameData.Enums; @@ -11,6 +10,11 @@ using Penumbra.GameData.Structs; namespace Glamourer.Gui.Equipment; +public enum ApplicationFlags +{ + +} + public partial class EquipmentDrawer { private Race _race; @@ -26,7 +30,7 @@ public partial class EquipmentDrawer { Stains = GameData.Stains(Dalamud.GameData); StainCombo = new FilterStainCombo(140); - Identifier = Penumbra.GameData.GameData.GetIdentifier(Dalamud.GameData); + Identifier = Glamourer.Identifier; ItemCombos = EquipSlotExtensions.EqdpSlots.Select(s => new ItemCombo(s)).ToArray(); MainHandCombo = new WeaponCombo(EquipSlot.MainHand); OffHandCombo = new WeaponCombo(EquipSlot.OffHand); diff --git a/Glamourer/Gui/Equipment/EquipmentDrawer.Misc.cs b/Glamourer/Gui/Equipment/EquipmentDrawer.Misc.cs index 3139a71..a9372cb 100644 --- a/Glamourer/Gui/Equipment/EquipmentDrawer.Misc.cs +++ b/Glamourer/Gui/Equipment/EquipmentDrawer.Misc.cs @@ -20,15 +20,20 @@ public partial class EquipmentDrawer private sealed class FilterStainCombo : FilterComboBase { - private readonly float _comboWidth; - private Vector2 _buttonSize; + private readonly float _comboWidth; + private Vector2 _buttonSize; + public ImRaii.Color Color = new(); public FilterStainCombo(float comboWidth) : base(Stains.Values.ToArray(), false) => _comboWidth = comboWidth; protected override float GetFilterWidth() - => _buttonSize.X + ImGui.GetStyle().ScrollbarSize; + { + // Hack to not color the filter frame. + Color.Pop(); + return _buttonSize.X + ImGui.GetStyle().ScrollbarSize; + } protected override void DrawList(float width, float itemHeight) { @@ -58,11 +63,12 @@ public partial class EquipmentDrawer private void DrawStainSelector() { - var foundIdx = StainCombo.Items.IndexOf(s => s.RowIndex.Equals(_currentArmor.Stain)); - var stain = foundIdx >= 0 ? StainCombo.Items[foundIdx] : default; - using var color = ImRaii.PushColor(ImGuiCol.FrameBg, stain.RgbaColor, foundIdx >= 0); + var foundIdx = StainCombo.Items.IndexOf(s => s.RowIndex.Equals(_currentArmor.Stain)); + var stain = foundIdx >= 0 ? StainCombo.Items[foundIdx] : default; + StainCombo.Color.Push(ImGuiCol.FrameBg, stain.RgbaColor, foundIdx >= 0); var change = StainCombo.Draw("##stainSelector", string.Empty, ref foundIdx, ImGui.GetFrameHeight(), ImGui.GetFrameHeight(), ImGuiComboFlags.NoArrowButton); + StainCombo.Color.Pop(); if (!change && (byte)_currentArmor.Stain != 0) { ImGuiUtil.HoverTooltip($"{stain.Name}\nRight-click to clear."); @@ -82,7 +88,7 @@ public partial class EquipmentDrawer } private void DrawCheckbox(ref ApplicationFlags flags) - => DrawCheckbox("##checkbox", "Enable writing this slot in this save.", ref flags, _currentSlot.ToApplicationFlag()); + => DrawCheckbox("##checkbox", "Enable writing this slot in this save.", ref flags, 0); private static void DrawCheckbox(string label, string tooltip, ref ApplicationFlags flags, ApplicationFlags flag) { diff --git a/Glamourer/Gui/Interface.Actors.cs b/Glamourer/Gui/Interface.Actors.cs index 2c02e80..3b00eeb 100644 --- a/Glamourer/Gui/Interface.Actors.cs +++ b/Glamourer/Gui/Interface.Actors.cs @@ -12,6 +12,7 @@ using ImGuiNET; using OtterGui; using OtterGui.Classes; using OtterGui.Raii; +using Penumbra.GameData.Actors; using Penumbra.GameData.Enums; using ImGui = ImGuiNET.ImGui; @@ -26,7 +27,7 @@ internal partial class Interface public ActorTab(CurrentManipulations manipulations) => _manipulations = manipulations; - private Actor.IIdentifier _identifier = Actor.IIdentifier.Invalid; + private ActorIdentifier _identifier = ActorIdentifier.Invalid; private ObjectManager.ActorData _currentData = ObjectManager.ActorData.Invalid; private string _currentLabel = string.Empty; private CurrentDesign? _currentSave; @@ -50,7 +51,7 @@ internal partial class Interface private unsafe void DrawPanel() { - if (_identifier == Actor.IIdentifier.Invalid) + if (_identifier == ActorIdentifier.Invalid) return; @@ -65,15 +66,16 @@ internal partial class Interface RevertButton(); CustomizationDrawer.Draw(_currentSave.Data.Customize, _currentSave.Data.Equipment, _currentData.Objects, - _identifier is Actor.SpecialIdentifier); + _identifier.Type == IdentifierType.Special); - EquipmentDrawer.Draw(_currentSave.Data.Customize, _currentSave.Data.Equipment, ref _currentSave.Data.MainHand, ref _currentSave.Data.OffHand, _currentData.Objects, _identifier is Actor.SpecialIdentifier); + EquipmentDrawer.Draw(_currentSave.Data.Customize, _currentSave.Data.Equipment, ref _currentSave.Data.MainHand, + ref _currentSave.Data.OffHand, _currentData.Objects, _identifier.Type == IdentifierType.Special); } private const uint RedHeaderColor = 0xFF1818C0; private const uint GreenHeaderColor = 0xFF18C018; - private void RevertButton() + private unsafe void RevertButton() { if (ImGui.Button("Revert")) { @@ -84,40 +86,42 @@ internal partial class Interface if (_currentData.Objects.Count > 0) _currentSave = _manipulations.GetOrCreateSave(_currentData.Objects[0]); - + _currentSave!.Reset(); } - VisorBox(); + if (_currentData.Objects.Count > 0) + ImGui.TextUnformatted(_currentData.Objects[0].Pointer->GameObject.DataID.ToString()); + //VisorBox(); } - private unsafe void VisorBox() - { - var (flags, mask) = (_currentSave!.Data.Flags & (ApplicationFlags.SetVisor | ApplicationFlags.Visor)) switch - { - ApplicationFlags.SetVisor => (0u, 3u), - ApplicationFlags.Visor => (1u, 3u), - ApplicationFlags.SetVisor | ApplicationFlags.Visor => (3u, 3u), - _ => (2u, 3u), - }; - var tmp = flags; - if (ImGui.CheckboxFlags("Visor Toggled", ref tmp, mask)) - { - _currentSave.Data.Flags = flags switch - { - 0 => (_currentSave.Data.Flags | ApplicationFlags.Visor) & ~ApplicationFlags.SetVisor, - 1 => _currentSave.Data.Flags | ApplicationFlags.SetVisor, - 2 => _currentSave.Data.Flags | ApplicationFlags.SetVisor, - _ => _currentSave.Data.Flags & ~(ApplicationFlags.SetVisor | ApplicationFlags.Visor), - }; - if (_currentSave.Data.Flags.HasFlag(ApplicationFlags.SetVisor)) - { - var on = _currentSave.Data.Flags.HasFlag(ApplicationFlags.Visor); - foreach (var actor in _currentData.Objects.Where(a => a.IsHuman && a.DrawObject)) - RedrawManager.SetVisor(actor.DrawObject.Pointer, on); - } - } - } + //private unsafe void VisorBox() + //{ + // var (flags, mask) = (_currentSave!.Data.Flags & (ApplicationFlags.SetVisor | ApplicationFlags.Visor)) switch + // { + // ApplicationFlags.SetVisor => (0u, 3u), + // ApplicationFlags.Visor => (1u, 3u), + // ApplicationFlags.SetVisor | ApplicationFlags.Visor => (3u, 3u), + // _ => (2u, 3u), + // }; + // var tmp = flags; + // if (ImGui.CheckboxFlags("Visor Toggled", ref tmp, mask)) + // { + // _currentSave.Data.Flags = flags switch + // { + // 0 => (_currentSave.Data.Flags | ApplicationFlags.Visor) & ~ApplicationFlags.SetVisor, + // 1 => _currentSave.Data.Flags | ApplicationFlags.SetVisor, + // 2 => _currentSave.Data.Flags | ApplicationFlags.SetVisor, + // _ => _currentSave.Data.Flags & ~(ApplicationFlags.SetVisor | ApplicationFlags.Visor), + // }; + // if (_currentSave.Data.Flags.HasFlag(ApplicationFlags.SetVisor)) + // { + // var on = _currentSave.Data.Flags.HasFlag(ApplicationFlags.Visor); + // foreach (var actor in _currentData.Objects.Where(a => a.IsHuman && a.DrawObject)) + // RedrawManager.SetVisor(actor.DrawObject.Pointer, on); + // } + // } + //} private void DrawPanelHeader() { @@ -213,10 +217,10 @@ internal partial class Interface ImGuiClip.DrawEndDummy(remainder, ImGui.GetTextLineHeight()); } - private bool CheckFilter((Actor.IIdentifier, ObjectManager.ActorData) pair) + private bool CheckFilter((ActorIdentifier, ObjectManager.ActorData) pair) => _actorFilter.IsEmpty || pair.Item2.Label.Contains(_actorFilter.Lower, StringComparison.OrdinalIgnoreCase); - private void DrawSelectable((Actor.IIdentifier, ObjectManager.ActorData) pair) + private void DrawSelectable((ActorIdentifier, ObjectManager.ActorData) pair) { var equal = pair.Item1.Equals(_identifier); if (ImGui.Selectable(pair.Item2.Label, equal) && !equal) diff --git a/Glamourer/Gui/Interface.DebugStateTab.cs b/Glamourer/Gui/Interface.DebugStateTab.cs index 5fd7a7a..19eae46 100644 --- a/Glamourer/Gui/Interface.DebugStateTab.cs +++ b/Glamourer/Gui/Interface.DebugStateTab.cs @@ -9,6 +9,7 @@ using ImGuiNET; using OtterGui; using OtterGui.Classes; using OtterGui.Raii; +using Penumbra.GameData.Actors; namespace Glamourer.Gui; @@ -18,10 +19,10 @@ internal partial class Interface { private readonly CurrentManipulations _currentManipulations; - private LowerString _manipulationFilter = LowerString.Empty; - private Actor.IIdentifier _selection = Actor.IIdentifier.Invalid; - private CurrentDesign? _save = null; - private bool _delete = false; + private LowerString _manipulationFilter = LowerString.Empty; + private ActorIdentifier _selection = ActorIdentifier.Invalid; + private CurrentDesign? _save = null; + private bool _delete = false; public DebugStateTab(CurrentManipulations currentManipulations) => _currentManipulations = currentManipulations; @@ -43,7 +44,7 @@ internal partial class Interface { _delete = false; _currentManipulations.DeleteSave(_selection); - _selection = Actor.IIdentifier.Invalid; + _selection = ActorIdentifier.Invalid; } } @@ -72,14 +73,14 @@ internal partial class Interface DrawSelector(oldSpacing); } - private bool CheckFilter(KeyValuePair data) + private bool CheckFilter(KeyValuePair data) { if (data.Key.Equals(_selection)) _save = data.Value; return _manipulationFilter.Length == 0 || _manipulationFilter.IsContained(data.Key.ToString()!); } - private void DrawSelectable(KeyValuePair data) + private void DrawSelectable(KeyValuePair data) { var equal = data.Key.Equals(_selection); if (ImGui.Selectable(data.Key.ToString(), equal)) diff --git a/Glamourer/Interop/Actor.Identifier.cs b/Glamourer/Interop/Actor.Identifier.cs deleted file mode 100644 index b145c74..0000000 --- a/Glamourer/Interop/Actor.Identifier.cs +++ /dev/null @@ -1,375 +0,0 @@ -using System; -using Dalamud.Game.ClientState.Objects.Enums; -using Dalamud.Utility; -using Lumina.Excel.GeneratedSheets; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using Penumbra.GameData.ByteString; - -namespace Glamourer.Interop; - -public unsafe partial struct Actor -{ - public interface IIdentifier : IEquatable - { - Utf8String Name { get; } - public bool IsValid { get; } - - public IIdentifier CreatePermanent(); - - public static readonly InvalidIdentifier Invalid = new(); - - public void ToJson(JsonTextWriter j); - - public static IIdentifier? FromJson(JObject j) - { - switch (j["Type"]?.Value() ?? string.Empty) - { - case nameof(PlayerIdentifier): - { - var name = j[nameof(Name)]?.Value(); - if (name.IsNullOrEmpty()) - return null; - - var serverId = j[nameof(PlayerIdentifier.HomeWorld)]?.Value() ?? ushort.MaxValue; - return new PlayerIdentifier(Utf8String.FromStringUnsafe(name, false), serverId); - } - case nameof(SpecialIdentifier): - { - var index = j[nameof(SpecialIdentifier.Index)]?.Value() ?? ushort.MaxValue; - return new SpecialIdentifier(index); - } - case nameof(OwnedIdentifier): - { - var name = j[nameof(Name)]?.Value(); - if (name.IsNullOrEmpty()) - return null; - - var ownerName = j[nameof(OwnedIdentifier.OwnerName)]?.Value(); - if (ownerName.IsNullOrEmpty()) - return null; - - var ownerHomeWorld = j[nameof(OwnedIdentifier.OwnerHomeWorld)]?.Value() ?? ushort.MaxValue; - var dataId = j[nameof(OwnedIdentifier.DataId)]?.Value() ?? ushort.MaxValue; - var kind = j[nameof(OwnedIdentifier.Kind)]?.Value() ?? ObjectKind.Player; - - return new OwnedIdentifier(Utf8String.FromStringUnsafe(name, false), Utf8String.FromStringUnsafe(ownerName, false), - ownerHomeWorld, dataId, kind); - } - case nameof(NpcIdentifier): - { - var name = j[nameof(Name)]?.Value(); - if (name.IsNullOrEmpty()) - return null; - - var dataId = j[nameof(NpcIdentifier.DataId)]?.Value() ?? uint.MaxValue; - - return new NpcIdentifier(Utf8String.FromStringUnsafe(name, false), ushort.MaxValue, dataId); - } - default: return null; - } - } - } - - public class InvalidIdentifier : IIdentifier - { - public Utf8String Name - => Utf8String.Empty; - - public bool IsValid - => false; - - public bool Equals(IIdentifier? other) - => false; - - public override int GetHashCode() - => 0; - - public override string ToString() - => "Invalid"; - - public IIdentifier CreatePermanent() - => this; - - public void ToJson(JsonTextWriter j) - { } - } - - public class PlayerIdentifier : IIdentifier, IEquatable - { - public Utf8String Name { get; } - public readonly ushort HomeWorld; - - public bool IsValid - => true; - - public PlayerIdentifier(Utf8String name, ushort homeWorld) - { - Name = name; - HomeWorld = homeWorld; - } - - public bool Equals(IIdentifier? other) - => Equals(other as PlayerIdentifier); - - public bool Equals(PlayerIdentifier? other) - => other?.HomeWorld == HomeWorld && other.Name.Equals(Name); - - public override int GetHashCode() - => HashCode.Combine(Name.Crc32, HomeWorld); - - public override string ToString() - => $"{Name} ({HomeWorld})"; - - public IIdentifier CreatePermanent() - => new PlayerIdentifier(Name.Clone(), HomeWorld); - - public void ToJson(JsonTextWriter j) - { - j.WriteStartObject(); - j.WritePropertyName("Type"); - j.WriteValue(GetType().Name); - j.WritePropertyName(nameof(Name)); - j.WriteValue(Name); - j.WritePropertyName(nameof(HomeWorld)); - j.WriteValue(HomeWorld); - j.WriteEndObject(); - } - } - - public class SpecialIdentifier : IIdentifier, IEquatable - { - public Utf8String Name - => Utf8String.Empty; - - public readonly ushort Index; - - public bool IsValid - => true; - - public SpecialIdentifier(ushort index) - => Index = index; - - public bool Equals(IIdentifier? other) - => Equals(other as SpecialIdentifier); - - public bool Equals(SpecialIdentifier? other) - => other?.Index == Index; - - public override int GetHashCode() - => Index; - - public override string ToString() - => $"Special Actor {Index}"; - - public IIdentifier CreatePermanent() - => this; - - public void ToJson(JsonTextWriter j) - { - j.WriteStartObject(); - j.WritePropertyName("Type"); - j.WriteValue(GetType().Name); - j.WritePropertyName(nameof(Index)); - j.WriteValue(Index); - j.WriteEndObject(); - } - } - - - public class OwnedIdentifier : IIdentifier, IEquatable - { - public Utf8String Name { get; } - public readonly Utf8String OwnerName; - public readonly uint DataId; - public readonly ushort OwnerHomeWorld; - public readonly ObjectKind Kind; - - public bool IsValid - => true; - - public OwnedIdentifier(Utf8String actorName, Utf8String ownerName, ushort ownerHomeWorld, uint dataId, ObjectKind kind) - { - OwnerName = ownerName; - OwnerHomeWorld = ownerHomeWorld; - DataId = dataId; - Kind = kind; - Name = actorName; - switch (Kind) - { - case ObjectKind.MountType: - var mount = Dalamud.GameData.GetExcelSheet()!.GetRow(dataId); - if (mount != null) - Name = Utf8String.FromSpanUnsafe(mount.Singular.RawData, false).AsciiToMixed(); - break; - case ObjectKind.Companion: - var companion = Dalamud.GameData.GetExcelSheet()!.GetRow(dataId); - if (companion != null) - Name = Utf8String.FromSpanUnsafe(companion.Singular.RawData, false).AsciiToMixed(); - break; - } - } - - public bool Equals(IIdentifier? other) - => Equals(other as OwnedIdentifier); - - public bool Equals(OwnedIdentifier? other) - => other?.DataId == DataId - && other.OwnerHomeWorld == OwnerHomeWorld - && other.Kind == Kind - && other.OwnerName.Equals(OwnerName); - - - public override int GetHashCode() - => HashCode.Combine(OwnerName.Crc32, OwnerHomeWorld, DataId, Kind); - - public override string ToString() - => $"{OwnerName}s {Name}"; - - public IIdentifier CreatePermanent() - => new OwnedIdentifier(Name.Clone(), OwnerName.Clone(), OwnerHomeWorld, DataId, Kind); - - public void ToJson(JsonTextWriter j) - { - j.WriteStartObject(); - j.WritePropertyName("Type"); - j.WriteValue(GetType().Name); - j.WritePropertyName(nameof(Name)); - j.WriteValue(Name); - j.WritePropertyName(nameof(OwnerName)); - j.WriteValue(OwnerName); - j.WritePropertyName(nameof(OwnerHomeWorld)); - j.WriteValue(OwnerHomeWorld); - j.WritePropertyName(nameof(Kind)); - j.WriteValue(Kind); - j.WritePropertyName(nameof(DataId)); - j.WriteValue(DataId); - j.WriteEndObject(); - } - } - - public class NpcIdentifier : IIdentifier, IEquatable - { - public Utf8String Name { get; } - public readonly uint DataId; - public readonly ushort ObjectIndex; - - public bool IsValid - => true; - - public NpcIdentifier(Utf8String actorName, ushort objectIndex = ushort.MaxValue, uint dataId = uint.MaxValue) - { - Name = actorName; - ObjectIndex = objectIndex; - DataId = dataId; - } - - public bool Equals(IIdentifier? other) - => Equals(other as NpcIdentifier); - - public bool Equals(NpcIdentifier? other) - => (other?.Name.Equals(Name) ?? false) - && (other.DataId == uint.MaxValue || DataId == uint.MaxValue || other.DataId == DataId) - && (other.ObjectIndex == ushort.MaxValue || ObjectIndex == ushort.MaxValue || other.ObjectIndex == ObjectIndex); - - public override int GetHashCode() - => Name.Crc32; - - public override string ToString() - => DataId == uint.MaxValue ? ObjectIndex == ushort.MaxValue ? Name.ToString() : $"{Name} at {ObjectIndex}" : - ObjectIndex == ushort.MaxValue ? $"{Name} ({DataId})" : $"{Name} ({DataId}) at {ObjectIndex}"; - - public IIdentifier CreatePermanent() - => new NpcIdentifier(Name.Clone(), ObjectIndex, DataId); - - public void ToJson(JsonTextWriter j) - { - j.WriteStartObject(); - j.WritePropertyName("Type"); - j.WriteValue(GetType().Name); - j.WritePropertyName(nameof(Name)); - j.WriteValue(Name); - j.WritePropertyName(nameof(DataId)); - j.WriteValue(DataId); - j.WriteEndObject(); - } - } - - private static IIdentifier CreateIdentifier(Actor actor) - { - if (!actor.Valid) - return IIdentifier.Invalid; - - var objectIdx = actor.Pointer->GameObject.ObjectIndex; - if (objectIdx is >= 200 and < 240) - { - var parentIdx = Glamourer.Penumbra.CutsceneParent(objectIdx); - if (parentIdx >= 0) - { - var parent = (Actor)Dalamud.Objects.GetObjectAddress(parentIdx); - if (!parent) - return IIdentifier.Invalid; - - return CreateIdentifier(parent); - } - } - - switch (actor.ObjectKind) - { - case ObjectKind.Player: - { - var name = actor.Utf8Name; - if (name.Length > 0 && actor.Pointer->HomeWorld is > 0 and < ushort.MaxValue) - return new PlayerIdentifier(actor.Utf8Name, actor.Pointer->HomeWorld); - - return IIdentifier.Invalid; - } - case ObjectKind.BattleNpc: - { - var ownerId = actor.Pointer->GameObject.OwnerID; - if (ownerId != 0xE0000000) - { - var owner = (Actor)Dalamud.Objects.SearchById(ownerId)?.Address; - if (!owner) - return new InvalidIdentifier(); - - return new OwnedIdentifier(actor.Utf8Name, owner.Utf8Name, owner.Pointer->HomeWorld, - actor.Pointer->GameObject.DataID, ObjectKind.BattleNpc); - } - - return new NpcIdentifier(actor.Utf8Name, actor.Pointer->GameObject.ObjectIndex, - actor.Pointer->GameObject.DataID); - } - case ObjectKind.Retainer: - case ObjectKind.EventNpc: - return new NpcIdentifier(actor.Utf8Name, actor.Pointer->GameObject.ObjectIndex, - actor.Pointer->GameObject.DataID); - case ObjectKind.MountType: - case ObjectKind.Companion: - { - var idx = actor.Pointer->GameObject.ObjectIndex; - if (idx % 2 == 0) - return new InvalidIdentifier(); - - var owner = (Actor)Dalamud.Objects[idx - 1]?.Address; - if (!owner) - return new InvalidIdentifier(); - - var dataId = actor.ObjectKind switch - { - ObjectKind.MountType => owner.UsedMountId, - ObjectKind.Companion => actor.CompanionId, - _ => actor.Pointer->GameObject.DataID, - }; - - var name = actor.Utf8Name; - if (name.IsEmpty && dataId == 0) - return new InvalidIdentifier(); - - return new OwnedIdentifier(name, owner.Utf8Name, owner.Pointer->HomeWorld, - dataId, actor.ObjectKind); - } - default: return new InvalidIdentifier(); - } - } -} diff --git a/Glamourer/Interop/Actor.cs b/Glamourer/Interop/Actor.cs index 5197801..204e425 100644 --- a/Glamourer/Interop/Actor.cs +++ b/Glamourer/Interop/Actor.cs @@ -1,9 +1,11 @@ using System; using Dalamud.Game.ClientState.Objects.Enums; using Dalamud.Game.ClientState.Objects.Types; +using FFXIVClientStructs.FFXIV.Client.System.String; using Glamourer.Customization; -using Penumbra.GameData.ByteString; +using Penumbra.GameData.Actors; using Penumbra.GameData.Structs; +using Penumbra.String; using CustomizeData = Penumbra.GameData.Structs.CustomizeData; namespace Glamourer.Interop; @@ -23,10 +25,10 @@ public unsafe partial struct Actor : IEquatable, IDesignable public static implicit operator IntPtr(Actor actor) => actor.Pointer == null ? IntPtr.Zero : (IntPtr)actor.Pointer; - public IIdentifier GetIdentifier() - => CreateIdentifier(this); + public ActorIdentifier GetIdentifier() + => Glamourer.Actors.FromObject((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)Pointer); - public bool Identifier(out IIdentifier ident) + public bool Identifier(out ActorIdentifier ident) { if (Valid) { @@ -34,7 +36,7 @@ public unsafe partial struct Actor : IEquatable, IDesignable return true; } - ident = IIdentifier.Invalid; + ident = ActorIdentifier.Invalid; return false; } @@ -53,7 +55,7 @@ public unsafe partial struct Actor : IEquatable, IDesignable set => Pointer->GameObject.ObjectKind = (byte)value; } - public Utf8String Utf8Name + public ByteString Utf8Name => new(Pointer->GameObject.Name); public byte Job @@ -123,6 +125,8 @@ public unsafe partial struct Actor : IEquatable, IDesignable } } + public bool IsWet { get; set; } + public void SetModelId(int value) { diff --git a/Glamourer/Interop/DrawObject.cs b/Glamourer/Interop/DrawObject.cs index 3c1be30..fe67a7f 100644 --- a/Glamourer/Interop/DrawObject.cs +++ b/Glamourer/Interop/DrawObject.cs @@ -25,6 +25,9 @@ public unsafe partial struct DrawObject : IEquatable, IDesignable public uint ModelId => 0; + public bool IsWet + => false; + public uint Type => (*(delegate* unmanaged**)Pointer)[50](Pointer); diff --git a/Glamourer/Interop/IDesignable.cs b/Glamourer/Interop/IDesignable.cs index 30f9883..9c9e349 100644 --- a/Glamourer/Interop/IDesignable.cs +++ b/Glamourer/Interop/IDesignable.cs @@ -13,4 +13,5 @@ public interface IDesignable public CharacterWeapon OffHand { get; } public bool VisorEnabled { get; } public bool WeaponEnabled { get; } + public bool IsWet { get; } } diff --git a/Glamourer/Interop/ObjectManager.cs b/Glamourer/Interop/ObjectManager.cs index 6b85ae8..a9bb668 100644 --- a/Glamourer/Interop/ObjectManager.cs +++ b/Glamourer/Interop/ObjectManager.cs @@ -2,6 +2,7 @@ using System.Text; using Dalamud.Game.ClientState.Objects.Enums; using Lumina.Excel.GeneratedSheets; +using Penumbra.GameData.Actors; using static Glamourer.Interop.Actor; namespace Glamourer.Interop; @@ -42,67 +43,29 @@ public static class ObjectManager public static bool IsInGPose { get; private set; } public static ushort World { get; private set; } - public static IReadOnlyDictionary Actors + public static IReadOnlyDictionary Actors => Identifiers; - public static IReadOnlyList<(IIdentifier, ActorData)> List + public static IReadOnlyList<(ActorIdentifier, ActorData)> List => ListData; - private static readonly Dictionary Identifiers = new(200); - private static readonly List<(IIdentifier, ActorData)> ListData = new(Dalamud.Objects.Length); + private static readonly Dictionary Identifiers = new(200); + private static readonly List<(ActorIdentifier, ActorData)> ListData = new(Dalamud.Objects.Length); - private static void HandleIdentifier(IIdentifier identifier, Actor character) + private static void HandleIdentifier(ActorIdentifier identifier, Actor character) { - if (!character.DrawObject) + if (!character.DrawObject || !identifier.IsValid) return; - switch (identifier) + if (!Identifiers.TryGetValue(identifier, out var data)) { - case PlayerIdentifier p: - if (!Identifiers.TryGetValue(p, out var data)) - { - data = new ActorData(character, - World != p.HomeWorld - ? $"{p.Name} ({Dalamud.GameData.GetExcelSheet()!.GetRow(p.HomeWorld)!.Name})" - : p.Name.ToString()); - Identifiers[p] = data; - ListData.Add((p, data)); - } - else - { - data.Objects.Add(character); - } - - break; - case NpcIdentifier n when !n.Name.IsEmpty: - if (!Identifiers.TryGetValue(n, out data)) - { - data = new ActorData(character, $"{n.Name} (at {n.ObjectIndex})"); - Identifiers[n] = data; - ListData.Add((n, data)); - } - else - { - data.Objects.Add(character); - } - - break; - case OwnedIdentifier o: - if (!Identifiers.TryGetValue(o, out data)) - { - data = new ActorData(character, - World != o.OwnerHomeWorld - ? $"{o.OwnerName}s {o.Name} ({Dalamud.GameData.GetExcelSheet()!.GetRow(o.OwnerHomeWorld)!.Name})" - : $"{o.OwnerName}s {o.Name}"); - Identifiers[o] = data; - ListData.Add((o, data)); - } - else - { - data.Objects.Add(character); - } - - break; + data = new ActorData(character, identifier.ToString()); + Identifiers[identifier] = data; + ListData.Add((identifier, data)); + } + else + { + data.Objects.Add(character); } } diff --git a/Glamourer/Saves/Design.cs b/Glamourer/Saves/Design.cs index 7570c34..4a5ec93 100644 --- a/Glamourer/Saves/Design.cs +++ b/Glamourer/Saves/Design.cs @@ -1,212 +1,305 @@ using System; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; using Glamourer.Customization; +using Glamourer.Interop; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; -using CustomizeData = Penumbra.GameData.Structs.CustomizeData; namespace Glamourer.Saves; -public class EquipmentDesign +public partial class Design { - private Data _data = default; + public const int CurrentVersion = 1; - // @formatter:off - //public Slot Head => new(ref _data, 0); - //public Slot Body => new(ref _data, 1); - //public Slot Hands => new(ref _data, 2); - //public Slot Legs => new(ref _data, 3); - //public Slot Feet => new(ref _data, 4); - //public Slot Ears => new(ref _data, 5); - //public Slot Neck => new(ref _data, 6); - //public Slot Wrist => new(ref _data, 7); - //public Slot RFinger => new(ref _data, 8); - //public Slot LFinger => new(ref _data, 9); - //public Slot MainHand => new(ref _data, 10); - //public Slot OffHand => new(ref _data, 11); - //// @formatter:on - // - //public Slot this[EquipSlot slot] - // => new(ref _data, (int)slot.ToIndex()); - // - //public Slot this[int idx] - // => idx is >= 0 and < Data.NumEquipment ? new Slot(ref _data, idx) : throw new IndexOutOfRangeException(); + public FileInfo Identifier { get; private set; } = new(string.Empty); + public string Name { get; private set; } = "New Design"; + public string Description { get; private set; } = string.Empty; - public unsafe struct Data + public DateTimeOffset CreationDate { get; private init; } = DateTimeOffset.UtcNow; + public DateTimeOffset LastUpdateDate { get; private set; } = DateTimeOffset.UtcNow; + + private DesignFlagsV1 _flags; + + public bool VisorState { - public const int NumEquipment = 12; - - public fixed uint Ids[NumEquipment]; - public fixed byte Stains[NumEquipment]; - public ushort Flags; - public ushort StainFlags; + get => _flags.HasFlag(DesignFlagsV1.VisorState); + private set => _flags = value ? _flags | DesignFlagsV1.VisorState : _flags & ~DesignFlagsV1.VisorState; } - //public ref struct Slot - //{ - // private readonly ref Data _data; - // private readonly int _index; - // private readonly ushort _flag; - // - // public Slot(ref Data data, int idx) - // { - // _data = data; - // _index = idx; - // _flag = (ushort)(1 << idx); - // } - // - // public unsafe uint ItemId - // { - // get => _data.Ids[_index]; - // set => _data.Ids[_index] = value; - // } - // - // public unsafe StainId StainId - // { - // get => _data.Stains[_index]; - // set => _data.Stains[_index] = value.Value; - // } - // - // public bool ApplyItem - // { - // get => (_data.Flags & _flag) != 0; - // set => _data.Flags = (ushort)(value ? _data.Flags | _flag : _data.Flags & ~_flag); - // } - // - // public bool ApplyStain - // { - // get => (_data.StainFlags & _flag) != 0; - // set => _data.StainFlags = (ushort)(value ? _data.StainFlags | _flag : _data.StainFlags & ~_flag); - // } - //} -} - -public class HumanDesign -{ - public unsafe struct Data + public bool VisorApply { - public CustomizeData Values; - public CustomizeFlag Flag; + get => _flags.HasFlag(DesignFlagsV1.VisorApply); + private set => _flags = value ? _flags | DesignFlagsV1.VisorApply : _flags & ~DesignFlagsV1.VisorApply; } - //public ref struct Choice where T : unmanaged - //{ - // private readonly ref Data _data; - // private readonly CustomizeFlag _flag; - // - // public Choice(ref Data data, CustomizeFlag flag) - // { - // _data = data; - // _flag = flag; - // } - // - // public bool ApplyChoice - // { - // get => _data.Flag.HasFlag(_flag); - // set => _data.Flag = value ? _data.Flag | _flag : _data.Flag & ~_flag; - // } - //} -} - -public class Design -{ - public string Name { get; private set; } - public string Description { get; private set; } - - public DateTimeOffset CreationDate { get; } - public DateTimeOffset LastUpdateDate { get; private set; } - - public bool ReadOnly { get; private set; } - - public EquipmentDesign? Equipment; - public HumanDesign? Customize; - - public string WriteJson() + public bool WeaponStateShown { - return string.Empty; + get => _flags.HasFlag(DesignFlagsV1.WeaponStateShown); + private set => _flags = value ? _flags | DesignFlagsV1.WeaponStateShown : _flags & ~DesignFlagsV1.WeaponStateShown; } -} - -public struct DesignSaveV1 -{ - public string Name; - public string Description; - - public ulong CreationDate; - public ulong LastUpdateDate; - - public EquipmentPiece Head; - public EquipmentPiece Body; - public EquipmentPiece Hands; - public EquipmentPiece Legs; - public EquipmentPiece Feet; - public EquipmentPiece Ears; - public EquipmentPiece Neck; - public EquipmentPiece Wrists; - public EquipmentPiece LFinger; - public EquipmentPiece RFinger; - - public EquipmentPiece MainHand; - public EquipmentPiece OffHand; - - public CustomizationChoice ModelId; - public CustomizationChoice Race; - public CustomizationChoice Gender; - public CustomizationChoice BodyType; - public CustomizationChoice Height; - public CustomizationChoice Clan; - public CustomizationChoice Face; - public CustomizationChoice Hairstyle; - public CustomizationChoice Highlights; - public CustomizationChoice SkinColor; - public CustomizationChoice EyeColorRight; - public CustomizationChoice HairColor; - public CustomizationChoice HighlightsColor; - public CustomizationChoice FacialFeature1; - public CustomizationChoice FacialFeature2; - public CustomizationChoice FacialFeature3; - public CustomizationChoice FacialFeature4; - public CustomizationChoice FacialFeature5; - public CustomizationChoice FacialFeature6; - public CustomizationChoice FacialFeature7; - public CustomizationChoice LegacyTattoo; - public CustomizationChoice TattooColor; - public CustomizationChoice Eyebrows; - public CustomizationChoice EyeColorLeft; - public CustomizationChoice EyeShape; - public CustomizationChoice SmallIris; - public CustomizationChoice Nose; - public CustomizationChoice Jaw; - public CustomizationChoice Mouth; - public CustomizationChoice Lipstick; - public CustomizationChoice MuscleMass; - public CustomizationChoice TailShape; - public CustomizationChoice BustSize; - public CustomizationChoice FacePaint; - public CustomizationChoice FacePaintReversed; - public CustomizationChoice FacePaintColor; - - public bool ReadOnly; - - public override string ToString() - => Name; -} - -public struct EquipmentPiece -{ - public uint Item; - public bool ApplyItem; - public StainId Stain; - public bool ApplyStain; -} - -public struct CustomizationChoice -{ - public CustomizeValue Value; - public bool Apply; -} - -public struct CustomizationChoice where T : struct -{ - public T Value; - public bool Apply; + + public bool WeaponStateApply + { + get => _flags.HasFlag(DesignFlagsV1.WeaponStateApply); + private set => _flags = value ? _flags | DesignFlagsV1.WeaponStateApply : _flags & ~DesignFlagsV1.WeaponStateApply; + } + + public bool WetnessState + { + get => _flags.HasFlag(DesignFlagsV1.WetnessState); + private set => _flags = value ? _flags | DesignFlagsV1.WetnessState : _flags & ~DesignFlagsV1.WetnessState; + } + + public bool WetnessApply + { + get => _flags.HasFlag(DesignFlagsV1.WetnessApply); + private set => _flags = value ? _flags | DesignFlagsV1.WetnessApply : _flags & ~DesignFlagsV1.WetnessApply; + } + + public bool ReadOnly + { + get => _flags.HasFlag(DesignFlagsV1.ReadOnly); + private set => _flags = value ? _flags | DesignFlagsV1.ReadOnly : _flags & ~DesignFlagsV1.ReadOnly; + } + + private static bool FromDesignable(string identifier, string name, IDesignable data, [NotNullWhen(true)] out Design? design, + bool doWeapons = true, bool doFlags = true, bool doEquipment = true, bool doCustomize = true) + { + if (!data.Valid) + { + design = null; + return false; + } + + design = new Design + { + Identifier = new FileInfo(identifier), + Name = name, + Description = string.Empty, + CreationDate = DateTimeOffset.UtcNow, + LastUpdateDate = DateTimeOffset.UtcNow, + ReadOnly = false, + VisorApply = doFlags, + WeaponStateApply = doFlags, + WetnessApply = doFlags, + VisorState = data.VisorEnabled, + WeaponStateShown = data.WeaponEnabled, + WetnessState = data.IsWet, + }; + + if (doEquipment) + { + var equipment = data.Equip; + foreach (var slot in EquipSlotExtensions.EqdpSlots) + { + var s = design[slot]; + var e = equipment[slot]; + s.StainId = e.Stain; + s.ApplyStain = true; + s.ItemId = Glamourer.Identifier.Identify(e.Set, e.Variant, slot).FirstOrDefault()?.RowId ?? 0; + s.ApplyItem = s.ItemId != 0; + } + } + + if (doWeapons) + { + var m = design.MainHand; + var d = data.MainHand; + + m.StainId = d.Stain; + m.ApplyStain = true; + m.ItemId = Glamourer.Identifier.Identify(d.Set, d.Type, d.Variant, EquipSlot.MainHand).FirstOrDefault()?.RowId ?? 0; + m.ApplyItem = m.ItemId != 0; + + var o = design.OffHand; + d = data.OffHand; + o.StainId = d.Stain; + o.ApplyStain = true; + o.ItemId = Glamourer.Identifier.Identify(d.Set, d.Type, d.Variant, EquipSlot.MainHand).FirstOrDefault()?.RowId ?? 0; + o.ApplyItem = o.ItemId != 0; + } + + if (doCustomize) + { + var customize = data.Customize; + design.CustomizeFlags = Glamourer.Customization.GetList(customize.Clan, customize.Gender).SettingAvailable + | CustomizeFlag.Gender + | CustomizeFlag.Race + | CustomizeFlag.Clan; + foreach (var c in Enum.GetValues()) + { + if (!design.CustomizeFlags.HasFlag(c.ToFlag())) + continue; + + var choice = design[c]; + choice.Value = customize[c]; + } + } + + + return true; + } + + public void Save() + { + try + { + using var file = File.Open(Identifier.FullName, File.Exists(Identifier.FullName) ? FileMode.Truncate : FileMode.CreateNew); + WriteJson(file); + } + catch (Exception ex) + { + Glamourer.Log.Error($"Could not save design {Identifier.Name}:\n{ex}"); + } + } + + public void WriteJson(Stream s, Formatting formatting = Formatting.Indented) + { + var obj = new JObject(); + obj["Version"] = CurrentVersion; + obj[nameof(Name)] = Name; + obj[nameof(Description)] = Description; + obj[nameof(CreationDate)] = CreationDate.ToUnixTimeSeconds(); + obj[nameof(LastUpdateDate)] = LastUpdateDate.ToUnixTimeSeconds(); + obj[nameof(ReadOnly)] = ReadOnly; + WriteEquipment(obj); + WriteCustomization(obj); + WriteFlags(obj); + + using var t = new StreamWriter(s); + using var j = new JsonTextWriter(t) { Formatting = formatting }; + obj.WriteTo(j); + } + + private void WriteFlags(JObject obj) + { + obj[nameof(VisorState)] = VisorState; + obj[nameof(VisorApply)] = VisorApply; + obj[nameof(WeaponStateShown)] = WeaponStateShown; + obj[nameof(WeaponStateApply)] = WeaponStateApply; + obj[nameof(WetnessState)] = WetnessState; + obj[nameof(WetnessApply)] = WetnessApply; + } + + public static bool Load(string fileName, [NotNullWhen(true)] out Design? design) + { + design = null; + if (!File.Exists(fileName)) + { + Glamourer.Log.Error($"Could not load design {fileName}:\nFile does not exist."); + return false; + } + + try + { + var data = File.ReadAllText(fileName); + var obj = JObject.Parse(data); + + return obj["Version"]?.Value() switch + { + null => NoVersion(fileName), + 1 => LoadV1(fileName, obj, out design), + _ => UnknownVersion(fileName, obj["Version"]!.Value()), + }; + } + catch (Exception e) + { + Glamourer.Log.Error($"Could not load design {fileName}:\n{e}"); + } + + return false; + } + + private static bool NoVersion(string fileName) + { + Glamourer.Log.Error($"Could not load design {fileName}:\nNo version available."); + return false; + } + + private static bool UnknownVersion(string fileName, int version) + { + Glamourer.Log.Error($"Could not load design {fileName}:\nThe version {version} can not be handled."); + return false; + } + + private static bool LoadV1(string fileName, JObject obj, [NotNullWhen(true)] out Design? design) + { + design = new Design + { + Identifier = new FileInfo(fileName), + Name = obj[nameof(Name)]?.Value() ?? "New Design", + Description = obj[nameof(Description)]?.Value() ?? string.Empty, + CreationDate = GetDateTime(obj[nameof(CreationDate)]?.Value()), + LastUpdateDate = GetDateTime(obj[nameof(LastUpdateDate)]?.Value()), + ReadOnly = obj[nameof(ReadOnly)]?.Value() ?? false, + VisorState = obj[nameof(VisorState)]?.Value() ?? false, + VisorApply = obj[nameof(VisorApply)]?.Value() ?? false, + WeaponStateShown = obj[nameof(WeaponStateShown)]?.Value() ?? false, + WeaponStateApply = obj[nameof(WeaponStateApply)]?.Value() ?? false, + WetnessState = obj[nameof(WetnessState)]?.Value() ?? false, + WetnessApply = obj[nameof(WetnessApply)]?.Value() ?? false, + }; + + var equipment = obj[nameof(Equipment)]; + if (equipment == null) + { + design.EquipmentFlags = 0; + design.StainFlags = 0; + design._equipmentData = default; + } + else + { + foreach (var slot in design.Equipment) + { + var s = equipment[SlotName[slot.Index]]; + if (s == null) + { + slot.ItemId = 0; + slot.ApplyItem = false; + slot.ApplyStain = false; + slot.StainId = 0; + } + else + { + slot.ItemId = s[nameof(Slot.ItemId)]?.Value() ?? 0u; + slot.ApplyItem = obj[nameof(Slot.ApplyItem)]?.Value() ?? false; + slot.StainId = new StainId(s[nameof(Slot.StainId)]?.Value() ?? 0); + slot.ApplyStain = obj[nameof(Slot.ApplyStain)]?.Value() ?? false; + } + } + } + + var customize = obj[nameof(Customization)]; + if (customize == null) + { + design.CustomizeFlags = 0; + design._customizeData = Customize.Default; + } + else + { + foreach (var choice in design.Customization) + { + var c = customize[choice.Index.ToDefaultName()]; + if (c == null) + { + choice.Value = Customize.Default.Get(choice.Index); + choice.Apply = false; + } + else + { + choice.Value = new CustomizeValue(c[nameof(Choice.Value)]?.Value() ?? Customize.Default.Get(choice.Index).Value); + choice.Apply = c[nameof(Choice.Apply)]?.Value() ?? false; + } + } + } + + return true; + } + + private static DateTimeOffset GetDateTime(long? value) + => value == null ? DateTimeOffset.UtcNow : DateTimeOffset.FromUnixTimeSeconds(value.Value); } diff --git a/Glamourer/Saves/DesignFlagsV1.cs b/Glamourer/Saves/DesignFlagsV1.cs new file mode 100644 index 0000000..6163f3e --- /dev/null +++ b/Glamourer/Saves/DesignFlagsV1.cs @@ -0,0 +1,15 @@ +using System; + +namespace Glamourer.Saves; + +[Flags] +public enum DesignFlagsV1 : byte +{ + VisorState = 0x01, + VisorApply = 0x02, + WeaponStateShown = 0x04, + WeaponStateApply = 0x08, + WetnessState = 0x10, + WetnessApply = 0x20, + ReadOnly = 0x40, +} diff --git a/Glamourer/Saves/EquipmentDesign.cs b/Glamourer/Saves/EquipmentDesign.cs new file mode 100644 index 0000000..3eb6e41 --- /dev/null +++ b/Glamourer/Saves/EquipmentDesign.cs @@ -0,0 +1,115 @@ +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json.Linq; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; + +namespace Glamourer.Saves; + +public partial class Design +{ + private unsafe struct EquipmentData + { + public const int NumEquipment = 12; + public fixed uint Ids[NumEquipment]; + public fixed byte Stains[NumEquipment]; + } + + private EquipmentData _equipmentData = default; + public ushort EquipmentFlags { get; private set; } + public ushort StainFlags { get; private set; } + + // @formatter:off + public Slot Head => new(this, 0); + public Slot Body => new(this, 1); + public Slot Hands => new(this, 2); + public Slot Legs => new(this, 3); + public Slot Feet => new(this, 4); + public Slot Ears => new(this, 5); + public Slot Neck => new(this, 6); + public Slot Wrists => new(this, 7); + public Slot RFinger => new(this, 8); + public Slot LFinger => new(this, 9); + public Slot MainHand => new(this, 10); + public Slot OffHand => new(this, 11); + // @formatter:on + + public Slot this[EquipSlot slot] + => new(this, (int)slot.ToIndex()); + + + public static readonly string[] SlotName = + { + EquipSlot.Head.ToName(), + EquipSlot.Body.ToName(), + EquipSlot.Hands.ToName(), + EquipSlot.Legs.ToName(), + EquipSlot.Feet.ToName(), + EquipSlot.Ears.ToName(), + EquipSlot.Neck.ToName(), + EquipSlot.Wrists.ToName(), + EquipSlot.RFinger.ToName(), + EquipSlot.LFinger.ToName(), + EquipSlot.MainHand.ToName(), + EquipSlot.OffHand.ToName(), + }; + + + public readonly unsafe struct Slot + { + private readonly Design _data; + public readonly int Index; + public readonly ushort Flag; + + public Slot(Design design, int idx) + { + _data = design; + Index = idx; + Flag = (ushort)(1 << idx); + } + + public uint ItemId + { + get => _data._equipmentData.Ids[Index]; + set => _data._equipmentData.Ids[Index] = value; + } + + public StainId StainId + { + get => _data._equipmentData.Stains[Index]; + set => _data._equipmentData.Stains[Index] = value.Value; + } + + public bool ApplyItem + { + get => (_data.EquipmentFlags & Flag) != 0; + set => _data.EquipmentFlags = (ushort)(value ? _data.EquipmentFlags | Flag : _data.EquipmentFlags & ~Flag); + } + + public bool ApplyStain + { + get => (_data.StainFlags & Flag) != 0; + set => _data.StainFlags = (ushort)(value ? _data.StainFlags | Flag : _data.StainFlags & ~Flag); + } + } + + public IEnumerable Equipment + => Enumerable.Range(0, EquipmentData.NumEquipment).Select(i => new Slot(this, i)); + + private void WriteEquipment(JObject obj) + { + var tok = new JObject(); + foreach (var slot in Equipment) + { + tok[SlotName] = new JObject + { + [nameof(Slot.ItemId)] = slot.ItemId, + [nameof(Slot.ApplyItem)] = slot.ApplyItem, + [nameof(Slot.StainId)] = slot.StainId.Value, + [nameof(Slot.ApplyStain)] = slot.ApplyStain, + }; + } + + obj[nameof(Equipment)] = tok; + } +} diff --git a/Glamourer/Saves/HumanDesign.cs b/Glamourer/Saves/HumanDesign.cs new file mode 100644 index 0000000..0dea537 --- /dev/null +++ b/Glamourer/Saves/HumanDesign.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using Glamourer.Customization; +using Newtonsoft.Json.Linq; +using CustomizeData = Penumbra.GameData.Structs.CustomizeData; + +namespace Glamourer.Saves; + +public partial class Design +{ + private CustomizeData _customizeData; + public CustomizeFlag CustomizeFlags { get; private set; } + + public Choice this[CustomizeIndex index] + => new(this, index); + + public unsafe Customize Customize + => new((CustomizeData*)Unsafe.AsPointer(ref _customizeData)); + + public readonly struct Choice + { + private readonly Design _data; + private readonly CustomizeFlag _flag; + private readonly CustomizeIndex _index; + + public Choice(Design design, CustomizeIndex index) + { + _data = design; + _index = index; + _flag = index.ToFlag(); + } + + public CustomizeValue Value + { + get => _data._customizeData.Get(_index); + set => _data._customizeData.Set(_index, value); + } + + public bool Apply + { + get => _data.CustomizeFlags.HasFlag(_flag); + set => _data.CustomizeFlags = value ? _data.CustomizeFlags | _flag : _data.CustomizeFlags & ~_flag; + } + + public CustomizeIndex Index + => _index; + } + + public IEnumerable Customization + => Enum.GetValues().Select(index => new Choice(this, index)); + + + public IEnumerable ActiveCustomizations + => Customization.Where(c => c.Apply); + + private void WriteCustomization(JObject obj) + { + var tok = new JObject(); + foreach (var choice in Customization) + tok[choice.Index.ToString()] = choice.Value.Value; + + obj[nameof(Customization)] = tok; + } +} diff --git a/Glamourer/State/ApplicationFlags.cs b/Glamourer/State/ApplicationFlags.cs deleted file mode 100644 index e0d1605..0000000 --- a/Glamourer/State/ApplicationFlags.cs +++ /dev/null @@ -1,75 +0,0 @@ -using Penumbra.GameData.Enums; -using System; - -namespace Glamourer.State; - -[Flags] -public enum ApplicationFlags : uint -{ - Customizations = 0x000001, - MainHand = 0x000002, - OffHand = 0x000004, - Head = 0x000008, - Body = 0x000010, - Hands = 0x000020, - Legs = 0x000040, - Feet = 0x000080, - Ears = 0x000100, - Neck = 0x000200, - Wrist = 0x000400, - RFinger = 0x000800, - LFinger = 0x001000, - SetVisor = 0x002000, - Visor = 0x004000, - SetWeapon = 0x008000, - Weapon = 0x010000, - SetWet = 0x020000, - Wet = 0x040000, -} - -public static class ApplicationFlagExtensions -{ - public static ApplicationFlags ToApplicationFlag(this EquipSlot slot) - => slot switch - { - EquipSlot.MainHand => ApplicationFlags.MainHand, - EquipSlot.OffHand => ApplicationFlags.OffHand, - EquipSlot.Head => ApplicationFlags.Head, - EquipSlot.Body => ApplicationFlags.Body, - EquipSlot.Hands => ApplicationFlags.Hands, - EquipSlot.Legs => ApplicationFlags.Legs, - EquipSlot.Feet => ApplicationFlags.Feet, - EquipSlot.Ears => ApplicationFlags.Ears, - EquipSlot.Neck => ApplicationFlags.Neck, - EquipSlot.Wrists => ApplicationFlags.Wrist, - EquipSlot.RFinger => ApplicationFlags.RFinger, - EquipSlot.BothHand => ApplicationFlags.MainHand | ApplicationFlags.OffHand, - EquipSlot.LFinger => ApplicationFlags.LFinger, - EquipSlot.HeadBody => ApplicationFlags.Body, - EquipSlot.BodyHandsLegsFeet => ApplicationFlags.Body, - EquipSlot.LegsFeet => ApplicationFlags.Legs, - EquipSlot.FullBody => ApplicationFlags.Body, - EquipSlot.BodyHands => ApplicationFlags.Body, - EquipSlot.BodyLegsFeet => ApplicationFlags.Body, - EquipSlot.ChestHands => ApplicationFlags.Body, - _ => 0, - }; - - public static EquipSlot ToSlot(this ApplicationFlags flags) - => flags switch - { - ApplicationFlags.MainHand => EquipSlot.MainHand, - ApplicationFlags.OffHand => EquipSlot.OffHand, - ApplicationFlags.Head => EquipSlot.Head, - ApplicationFlags.Body => EquipSlot.Body, - ApplicationFlags.Hands => EquipSlot.Hands, - ApplicationFlags.Legs => EquipSlot.Legs, - ApplicationFlags.Feet => EquipSlot.Feet, - ApplicationFlags.Ears => EquipSlot.Ears, - ApplicationFlags.Neck => EquipSlot.Neck, - ApplicationFlags.Wrist => EquipSlot.Wrists, - ApplicationFlags.RFinger => EquipSlot.RFinger, - ApplicationFlags.LFinger => EquipSlot.LFinger, - _ => EquipSlot.Unknown, - }; -} diff --git a/Glamourer/State/CharacterSave.cs b/Glamourer/State/CharacterSave.cs index 6f09667..44fe527 100644 --- a/Glamourer/State/CharacterSave.cs +++ b/Glamourer/State/CharacterSave.cs @@ -2,12 +2,12 @@ using System.Runtime.InteropServices; using Glamourer.Customization; using Glamourer.Interop; +using ImGuiScene; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; +using Penumbra.String.Functions; using CustomizeData = Penumbra.GameData.Structs.CustomizeData; -using Functions = Penumbra.GameData.Util.Functions; namespace Glamourer.State; @@ -36,7 +36,6 @@ public struct CharacterData public const byte CurrentVersion = 3; public uint ModelId; - public ApplicationFlags Flags; public CustomizeData CustomizeData; public CharacterWeapon MainHand; public CharacterWeapon OffHand; @@ -77,7 +76,6 @@ public struct CharacterData = new() { ModelId = 0, - Flags = 0, CustomizeData = Customize.Default, MainHand = CharacterWeapon.Empty, OffHand = CharacterWeapon.Empty, @@ -98,28 +96,12 @@ public struct CharacterData var data = new CharacterData(); fixed (void* ptr = &this) { - Functions.MemCpyUnchecked(&data, ptr, sizeof(CharacterData)); + MemoryUtility.MemCpyUnchecked(&data, ptr, sizeof(CharacterData)); } return data; } - - private const ApplicationFlags SaveFlags = ApplicationFlags.Customizations - | ApplicationFlags.Head - | ApplicationFlags.Body - | ApplicationFlags.Hands - | ApplicationFlags.Legs - | ApplicationFlags.Feet - | ApplicationFlags.Ears - | ApplicationFlags.Neck - | ApplicationFlags.Wrist - | ApplicationFlags.RFinger - | ApplicationFlags.LFinger - | ApplicationFlags.MainHand - | ApplicationFlags.OffHand - | ApplicationFlags.SetVisor - | ApplicationFlags.SetWeapon; - + public void Load(IDesignable designable) { @@ -128,7 +110,6 @@ public struct CharacterData Equipment.Load(designable.Equip); MainHand = designable.MainHand; OffHand = designable.OffHand; - Flags = SaveFlags | (designable.VisorEnabled ? ApplicationFlags.Visor : 0) | (designable.WeaponEnabled ? ApplicationFlags.Weapon : 0); } } diff --git a/Glamourer/State/CurrentManipulations.cs b/Glamourer/State/CurrentManipulations.cs index a4963c7..fba9be5 100644 --- a/Glamourer/State/CurrentManipulations.cs +++ b/Glamourer/State/CurrentManipulations.cs @@ -2,14 +2,15 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using Glamourer.Interop; +using Penumbra.GameData.Actors; namespace Glamourer.State; -public class CurrentManipulations : IReadOnlyCollection> +public class CurrentManipulations : IReadOnlyCollection> { - private readonly Dictionary _characterSaves = new(); + private readonly Dictionary _characterSaves = new(); - public IEnumerator> GetEnumerator() + public IEnumerator> GetEnumerator() => _characterSaves.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() @@ -32,10 +33,10 @@ public class CurrentManipulations : IReadOnlyCollection _characterSaves.Remove(identifier); - public bool TryGetDesign(Actor.IIdentifier identifier, [NotNullWhen(true)] out CurrentDesign? save) + public bool TryGetDesign(ActorIdentifier identifier, [NotNullWhen(true)] out CurrentDesign? save) => _characterSaves.TryGetValue(identifier, out save); //public CharacterArmor? ChangeEquip(Actor actor, EquipSlot slot, CharacterArmor data) diff --git a/Glamourer/State/FixedDesigns.cs b/Glamourer/State/FixedDesigns.cs index a41c6c6..af5a55c 100644 --- a/Glamourer/State/FixedDesigns.cs +++ b/Glamourer/State/FixedDesigns.cs @@ -1,11 +1,12 @@ using System.Diagnostics.CodeAnalysis; using Glamourer.Interop; +using Penumbra.GameData.Actors; namespace Glamourer.State; public class FixedDesigns { - public bool TryGetDesign(Actor.IIdentifier actor, [NotNullWhen(true)] out CharacterSave? save) + public bool TryGetDesign(ActorIdentifier actor, [NotNullWhen(true)] out CharacterSave? save) { save = null; return false;