From 7caf6cc08a69e865a8dd8e02d661a872626e400f Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 11 Jul 2024 14:21:25 +0200 Subject: [PATCH] Initial Update for multiple stains, some facewear support, and API X --- Glamourer/Automation/AutoDesignManager.cs | 2 +- Glamourer/Automation/FixedDesignMigrator.cs | 2 +- Glamourer/Configuration.cs | 2 +- Glamourer/Designs/Design.cs | 2 +- Glamourer/Designs/DesignBase.cs | 15 +- Glamourer/Designs/DesignBase64Migration.cs | 9 +- Glamourer/Designs/DesignColors.cs | 2 +- Glamourer/Designs/DesignConverter.cs | 8 +- Glamourer/Designs/DesignData.cs | 150 ++++++++++-------- Glamourer/Designs/DesignEditor.cs | 17 +- Glamourer/Designs/DesignFileSystem.cs | 2 +- Glamourer/Designs/IDesignEditor.cs | 6 +- Glamourer/Designs/Links/DesignLinkLoader.cs | 3 +- Glamourer/EphemeralConfig.cs | 2 +- Glamourer/Events/MovedEquipment.cs | 2 +- Glamourer/GameData/CustomizeManager.cs | 13 +- Glamourer/GameData/CustomizeSetFactory.cs | 12 +- Glamourer/GameData/NpcCustomizeSet.cs | 60 +++---- Glamourer/GameData/NpcData.cs | 27 ++-- Glamourer/Glamourer.cs | 4 +- Glamourer/Glamourer.csproj | 1 + Glamourer/Glamourer.json | 2 +- .../CustomizationDrawer.GenderRace.cs | 11 +- .../Customization/CustomizationDrawer.Icon.cs | 23 ++- .../Gui/Customization/CustomizationDrawer.cs | 27 +--- Glamourer/Gui/Equipment/EquipDrawData.cs | 14 +- Glamourer/Gui/Equipment/EquipmentDrawer.cs | 49 +++--- Glamourer/Gui/GlamourerWindowSystem.cs | 4 +- Glamourer/Gui/MainWindow.cs | 4 +- Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 4 +- .../Gui/Tabs/DebugTab/ActiveStatePanel.cs | 4 +- .../Gui/Tabs/DebugTab/GlamourPlatePanel.cs | 14 +- Glamourer/Gui/Tabs/DebugTab/InventoryPanel.cs | 4 +- .../DebugTab/IpcTester/DesignIpcTester.cs | 2 +- .../Tabs/DebugTab/IpcTester/IpcTesterPanel.cs | 2 +- .../Tabs/DebugTab/IpcTester/ItemsIpcTester.cs | 6 +- .../Tabs/DebugTab/IpcTester/StateIpcTester.cs | 4 +- .../Gui/Tabs/DebugTab/ModelEvaluationPanel.cs | 42 ++++- .../Gui/Tabs/DesignTab/DesignDetailTab.cs | 2 +- .../DesignTab/DesignFileSystemSelector.cs | 2 +- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 6 +- Glamourer/Gui/Tabs/DesignTab/DesignTab.cs | 2 +- .../Gui/Tabs/DesignTab/ModAssociationsTab.cs | 2 +- Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs | 2 +- Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs | 3 +- .../Gui/Tabs/UnlocksTab/UnlockOverview.cs | 17 +- Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs | 70 +++++++- Glamourer/Interop/ChangeCustomizeService.cs | 2 +- Glamourer/Interop/CharaFile/CharaFile.cs | 4 +- Glamourer/Interop/CharaFile/CmaFile.cs | 10 +- Glamourer/Interop/ContextMenuService.cs | 18 ++- Glamourer/Interop/CrestService.cs | 17 +- Glamourer/Interop/ImportService.cs | 2 +- Glamourer/Interop/InventoryService.cs | 82 +++++----- Glamourer/Interop/Material/MaterialManager.cs | 9 +- .../Interop/Material/MaterialValueIndex.cs | 4 +- .../Interop/Material/MaterialValueManager.cs | 2 +- Glamourer/Interop/Material/PrepareColorSet.cs | 26 +-- Glamourer/Interop/MetaService.cs | 12 +- Glamourer/Interop/ObjectManager.cs | 2 +- .../Interop/PalettePlus/PaletteImport.cs | 2 +- .../Interop/PalettePlus/PalettePlusChecker.cs | 8 +- Glamourer/Interop/Penumbra/PenumbraService.cs | 6 +- Glamourer/Interop/ScalingService.cs | 15 +- Glamourer/Interop/UpdateSlotService.cs | 34 +++- Glamourer/Interop/WeaponService.cs | 6 +- .../Services/CollectionOverrideService.cs | 4 +- Glamourer/Services/CustomizeService.cs | 18 --- Glamourer/Services/DalamudServices.cs | 2 +- Glamourer/Services/FilenameService.cs | 2 +- Glamourer/Services/HeightService.cs | 3 +- Glamourer/Services/ItemManager.cs | 10 +- Glamourer/Services/ServiceManager.cs | 5 +- Glamourer/Services/TextureService.cs | 6 +- Glamourer/State/FunEquipSet.cs | 7 +- Glamourer/State/FunModule.cs | 43 +++-- Glamourer/State/InternalStateEditor.cs | 16 +- Glamourer/State/StateApplier.cs | 26 +-- Glamourer/State/StateEditor.cs | 38 ++--- Glamourer/State/StateListener.cs | 34 ++-- Glamourer/State/StateManager.cs | 20 +-- Glamourer/State/WorldSets.cs | 2 +- Glamourer/Unlocks/CustomizeUnlockManager.cs | 7 +- Glamourer/Unlocks/FavoriteManager.cs | 2 +- Glamourer/Unlocks/ItemUnlockManager.cs | 12 +- Glamourer/Unlocks/UnlockDictionaryHelpers.cs | 2 +- OtterGui | 2 +- Penumbra.GameData | 2 +- Penumbra.String | 2 +- repo.json | 2 +- 90 files changed, 654 insertions(+), 537 deletions(-) diff --git a/Glamourer/Automation/AutoDesignManager.cs b/Glamourer/Automation/AutoDesignManager.cs index 1b95f73..da9b014 100644 --- a/Glamourer/Automation/AutoDesignManager.cs +++ b/Glamourer/Automation/AutoDesignManager.cs @@ -1,5 +1,5 @@ using Dalamud.Game.ClientState.Objects.Enums; -using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.ImGuiNotification; using Glamourer.Designs; using Glamourer.Designs.Special; using Glamourer.Events; diff --git a/Glamourer/Automation/FixedDesignMigrator.cs b/Glamourer/Automation/FixedDesignMigrator.cs index 856561a..fb9bca4 100644 --- a/Glamourer/Automation/FixedDesignMigrator.cs +++ b/Glamourer/Automation/FixedDesignMigrator.cs @@ -1,4 +1,4 @@ -using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.ImGuiNotification; using Glamourer.Designs; using Glamourer.Interop; using Newtonsoft.Json.Linq; diff --git a/Glamourer/Configuration.cs b/Glamourer/Configuration.cs index dbd245f..d0b3f98 100644 --- a/Glamourer/Configuration.cs +++ b/Glamourer/Configuration.cs @@ -1,6 +1,6 @@ using Dalamud.Configuration; using Dalamud.Game.ClientState.Keys; -using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.ImGuiNotification; using Glamourer.Designs; using Glamourer.Gui; using Glamourer.Gui.Tabs.DesignTab; diff --git a/Glamourer/Designs/Design.cs b/Glamourer/Designs/Design.cs index f09e7bc..b8dc0a2 100644 --- a/Glamourer/Designs/Design.cs +++ b/Glamourer/Designs/Design.cs @@ -1,4 +1,4 @@ -using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.ImGuiNotification; using Glamourer.Automation; using Glamourer.Designs.Links; using Glamourer.Interop.Material; diff --git a/Glamourer/Designs/DesignBase.cs b/Glamourer/Designs/DesignBase.cs index 4910793..3791442 100644 --- a/Glamourer/Designs/DesignBase.cs +++ b/Glamourer/Designs/DesignBase.cs @@ -1,4 +1,4 @@ -using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.ImGuiNotification; using Glamourer.GameData; using Glamourer.Interop.Material; using Glamourer.Services; @@ -257,10 +257,10 @@ public class DesignBase foreach (var slot in EquipSlotExtensions.EqdpSlots.Prepend(EquipSlot.OffHand).Prepend(EquipSlot.MainHand)) { var item = _designData.Item(slot); - var stain = _designData.Stain(slot); + var stains = _designData.Stain(slot); var crestSlot = slot.ToCrestFlag(); var crest = _designData.Crest(crestSlot); - ret[slot.ToString()] = Serialize(item.Id, stain, crest, DoApplyEquip(slot), DoApplyStain(slot), DoApplyCrest(crestSlot)); + ret[slot.ToString()] = Serialize(item.Id, stains, crest, DoApplyEquip(slot), DoApplyStain(slot), DoApplyCrest(crestSlot)); } ret["Hat"] = new QuadBool(_designData.IsHatVisible(), DoApplyMeta(MetaIndex.HatState)).ToJObject("Show", "Apply"); @@ -274,16 +274,15 @@ public class DesignBase return ret; - static JObject Serialize(CustomItemId id, StainId stain, bool crest, bool apply, bool applyStain, bool applyCrest) - => new() + static JObject Serialize(CustomItemId id, StainIds stains, bool crest, bool apply, bool applyStain, bool applyCrest) + => stains.AddToObject(new JObject { ["ItemId"] = id.Id, - ["Stain"] = stain.Id, ["Crest"] = crest, ["Apply"] = apply, ["ApplyStain"] = applyStain, ["ApplyCrest"] = applyCrest, - }; + }); } protected JObject SerializeCustomize() @@ -522,7 +521,7 @@ public class DesignBase return; } - static (CustomItemId, StainId, bool, bool, bool, bool) ParseItem(EquipSlot slot, JToken? item) + static (CustomItemId, StainIds, bool, bool, bool, bool) ParseItem(EquipSlot slot, JToken? item) { var id = item?["ItemId"]?.ToObject() ?? ItemManager.NothingId(slot).Id; var stain = (StainId)(item?["Stain"]?.ToObject() ?? 0); diff --git a/Glamourer/Designs/DesignBase64Migration.cs b/Glamourer/Designs/DesignBase64Migration.cs index a8b2f7b..d6d0886 100644 --- a/Glamourer/Designs/DesignBase64Migration.cs +++ b/Glamourer/Designs/DesignBase64Migration.cs @@ -1,5 +1,4 @@ using Glamourer.Services; -using Glamourer.State; using OtterGui; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; @@ -117,7 +116,7 @@ public class DesignBase64Migration } data.SetItem(slot, item); - data.SetStain(slot, mdl.Stain); + data.SetStain(slot, mdl.Stains); } var main = cur[0].Skeleton.Id == 0 @@ -130,7 +129,7 @@ public class DesignBase64Migration } data.SetItem(EquipSlot.MainHand, main); - data.SetStain(EquipSlot.MainHand, cur[0].Stain); + data.SetStain(EquipSlot.MainHand, cur[0].Stains); EquipItem off; // Fist weapon hack @@ -141,7 +140,7 @@ public class DesignBase64Migration if (gauntlet.Valid) { data.SetItem(EquipSlot.Hands, gauntlet); - data.SetStain(EquipSlot.Hands, cur[0].Stain); + data.SetStain(EquipSlot.Hands, cur[0].Stains); } } else @@ -158,7 +157,7 @@ public class DesignBase64Migration } data.SetItem(EquipSlot.OffHand, off); - data.SetStain(EquipSlot.OffHand, cur[1].Stain); + data.SetStain(EquipSlot.OffHand, cur[1].Stains); return data; } } diff --git a/Glamourer/Designs/DesignColors.cs b/Glamourer/Designs/DesignColors.cs index 8bc5539..5577c2c 100644 --- a/Glamourer/Designs/DesignColors.cs +++ b/Glamourer/Designs/DesignColors.cs @@ -1,5 +1,5 @@ using Dalamud.Interface; -using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.ImGuiNotification; using Dalamud.Interface.Utility.Raii; using Glamourer.Gui; using Glamourer.Services; diff --git a/Glamourer/Designs/DesignConverter.cs b/Glamourer/Designs/DesignConverter.cs index f3955c5..b1b5c61 100644 --- a/Glamourer/Designs/DesignConverter.cs +++ b/Glamourer/Designs/DesignConverter.cs @@ -176,7 +176,7 @@ public class DesignConverter( return System.Convert.ToBase64String(compressed); } - public IEnumerable<(EquipSlot Slot, EquipItem Item, StainId Stain)> FromDrawData(IReadOnlyList armors, + public IEnumerable<(EquipSlot Slot, EquipItem Item, StainIds Stains)> FromDrawData(IReadOnlyList armors, CharacterWeapon mainhand, CharacterWeapon offhand, bool skipWarnings) { if (armors.Count != 10) @@ -194,7 +194,7 @@ public class DesignConverter( item = ItemManager.NothingItem(slot); } - yield return (slot, item, armor.Stain); + yield return (slot, item, armor.Stains); } var mh = _items.Identify(EquipSlot.MainHand, mainhand.Skeleton, mainhand.Weapon, mainhand.Variant); @@ -204,7 +204,7 @@ public class DesignConverter( mh = _items.DefaultSword; } - yield return (EquipSlot.MainHand, mh, mainhand.Stain); + yield return (EquipSlot.MainHand, mh, mainhand.Stains); var oh = _items.Identify(EquipSlot.OffHand, offhand.Skeleton, offhand.Weapon, offhand.Variant, mh.Type); if (!skipWarnings && !oh.Valid) @@ -215,7 +215,7 @@ public class DesignConverter( oh = ItemManager.NothingItem(FullEquipType.Shield); } - yield return (EquipSlot.OffHand, oh, offhand.Stain); + yield return (EquipSlot.OffHand, oh, offhand.Stains); } private static void ComputeMaterials(DesignMaterialManager manager, in StateMaterialManager materials, diff --git a/Glamourer/Designs/DesignData.cs b/Glamourer/Designs/DesignData.cs index 6b84768..a762a84 100644 --- a/Glamourer/Designs/DesignData.cs +++ b/Glamourer/Designs/DesignData.cs @@ -9,6 +9,8 @@ namespace Glamourer.Designs; public unsafe struct DesignData { + public const int EquipmentByteSize = 10 * CharacterArmor.Size; + private string _nameHead = string.Empty; private string _nameBody = string.Empty; private string _nameHands = string.Empty; @@ -21,15 +23,14 @@ public unsafe struct DesignData private string _nameLFinger = string.Empty; private string _nameMainhand = string.Empty; private string _nameOffhand = string.Empty; + private string _nameFaceWear = string.Empty; private fixed uint _itemIds[12]; - private fixed ushort _iconIds[12]; - private fixed byte _equipmentBytes[48]; + private fixed uint _iconIds[12]; + private fixed byte _equipmentBytes[EquipmentByteSize + 16]; public CustomizeParameterData Parameters; public CustomizeArray Customize = CustomizeArray.Default; public uint ModelId; public CrestFlag CrestVisibility; - private SecondaryId _secondaryMainhand; - private SecondaryId _secondaryOffhand; private FullEquipType _typeMainhand; private FullEquipType _typeOffhand; private byte _states; @@ -50,12 +51,19 @@ public unsafe struct DesignData || name.IsContained(_nameRFinger) || name.IsContained(_nameLFinger) || name.IsContained(_nameMainhand) - || name.IsContained(_nameOffhand); + || name.IsContained(_nameOffhand) + || name.IsContained(_nameFaceWear); - public readonly StainId Stain(EquipSlot slot) + public readonly StainIds Stain(EquipSlot slot) { var index = slot.ToIndex(); - return index > 11 ? (StainId)0 : _equipmentBytes[4 * index + 3]; + return index switch + { + < 10 => new StainIds(_equipmentBytes[CharacterArmor.Size * index + 3], _equipmentBytes[CharacterArmor.Size * index + 4]), + 10 => new StainIds(_equipmentBytes[EquipmentByteSize + 6], _equipmentBytes[EquipmentByteSize + 7]), + 11 => new StainIds(_equipmentBytes[EquipmentByteSize + 14], _equipmentBytes[EquipmentByteSize + 15]), + _ => StainIds.None, + }; } public readonly bool Crest(CrestFlag slot) @@ -69,24 +77,29 @@ public unsafe struct DesignData => _typeOffhand; public readonly EquipItem Item(EquipSlot slot) - => slot.ToIndex() switch + { + fixed (byte* ptr = _equipmentBytes) { + return slot.ToIndex() switch + { // @formatter:off - 0 => EquipItem.FromIds((ItemId)_itemIds[ 0], (IconId)_iconIds[ 0], (PrimaryId)(_equipmentBytes[ 0] | (_equipmentBytes[ 1] << 8)), (SecondaryId)0, (Variant)_equipmentBytes[ 2], FullEquipType.Head, name: _nameHead ), - 1 => EquipItem.FromIds((ItemId)_itemIds[ 1], (IconId)_iconIds[ 1], (PrimaryId)(_equipmentBytes[ 4] | (_equipmentBytes[ 5] << 8)), (SecondaryId)0, (Variant)_equipmentBytes[ 6], FullEquipType.Body, name: _nameBody ), - 2 => EquipItem.FromIds((ItemId)_itemIds[ 2], (IconId)_iconIds[ 2], (PrimaryId)(_equipmentBytes[ 8] | (_equipmentBytes[ 9] << 8)), (SecondaryId)0, (Variant)_equipmentBytes[10], FullEquipType.Hands, name: _nameHands ), - 3 => EquipItem.FromIds((ItemId)_itemIds[ 3], (IconId)_iconIds[ 3], (PrimaryId)(_equipmentBytes[12] | (_equipmentBytes[13] << 8)), (SecondaryId)0, (Variant)_equipmentBytes[14], FullEquipType.Legs, name: _nameLegs ), - 4 => EquipItem.FromIds((ItemId)_itemIds[ 4], (IconId)_iconIds[ 4], (PrimaryId)(_equipmentBytes[16] | (_equipmentBytes[17] << 8)), (SecondaryId)0, (Variant)_equipmentBytes[18], FullEquipType.Feet, name: _nameFeet ), - 5 => EquipItem.FromIds((ItemId)_itemIds[ 5], (IconId)_iconIds[ 5], (PrimaryId)(_equipmentBytes[20] | (_equipmentBytes[21] << 8)), (SecondaryId)0, (Variant)_equipmentBytes[22], FullEquipType.Ears, name: _nameEars ), - 6 => EquipItem.FromIds((ItemId)_itemIds[ 6], (IconId)_iconIds[ 6], (PrimaryId)(_equipmentBytes[24] | (_equipmentBytes[25] << 8)), (SecondaryId)0, (Variant)_equipmentBytes[26], FullEquipType.Neck, name: _nameNeck ), - 7 => EquipItem.FromIds((ItemId)_itemIds[ 7], (IconId)_iconIds[ 7], (PrimaryId)(_equipmentBytes[28] | (_equipmentBytes[29] << 8)), (SecondaryId)0, (Variant)_equipmentBytes[30], FullEquipType.Wrists, name: _nameWrists ), - 8 => EquipItem.FromIds((ItemId)_itemIds[ 8], (IconId)_iconIds[ 8], (PrimaryId)(_equipmentBytes[32] | (_equipmentBytes[33] << 8)), (SecondaryId)0, (Variant)_equipmentBytes[34], FullEquipType.Finger, name: _nameRFinger ), - 9 => EquipItem.FromIds((ItemId)_itemIds[ 9], (IconId)_iconIds[ 9], (PrimaryId)(_equipmentBytes[36] | (_equipmentBytes[37] << 8)), (SecondaryId)0, (Variant)_equipmentBytes[38], FullEquipType.Finger, name: _nameLFinger ), - 10 => EquipItem.FromIds((ItemId)_itemIds[10], (IconId)_iconIds[10], (PrimaryId)(_equipmentBytes[40] | (_equipmentBytes[41] << 8)), _secondaryMainhand, (Variant)_equipmentBytes[42], _typeMainhand, name: _nameMainhand), - 11 => EquipItem.FromIds((ItemId)_itemIds[11], (IconId)_iconIds[11], (PrimaryId)(_equipmentBytes[44] | (_equipmentBytes[45] << 8)), _secondaryOffhand, (Variant)_equipmentBytes[46], _typeOffhand, name: _nameOffhand ), + 0 => EquipItem.FromIds(_itemIds[ 0], _iconIds[ 0], ((CharacterArmor*)ptr)[0].Set, 0, ((CharacterArmor*)ptr)[0].Variant, FullEquipType.Head, name: _nameHead ), + 1 => EquipItem.FromIds(_itemIds[ 1], _iconIds[ 1], ((CharacterArmor*)ptr)[1].Set, 0, ((CharacterArmor*)ptr)[1].Variant, FullEquipType.Body, name: _nameBody ), + 2 => EquipItem.FromIds(_itemIds[ 2], _iconIds[ 2], ((CharacterArmor*)ptr)[2].Set, 0, ((CharacterArmor*)ptr)[2].Variant, FullEquipType.Hands, name: _nameHands ), + 3 => EquipItem.FromIds(_itemIds[ 3], _iconIds[ 3], ((CharacterArmor*)ptr)[3].Set, 0, ((CharacterArmor*)ptr)[3].Variant, FullEquipType.Legs, name: _nameLegs ), + 4 => EquipItem.FromIds(_itemIds[ 4], _iconIds[ 4], ((CharacterArmor*)ptr)[4].Set, 0, ((CharacterArmor*)ptr)[4].Variant, FullEquipType.Feet, name: _nameFeet ), + 5 => EquipItem.FromIds(_itemIds[ 5], _iconIds[ 5], ((CharacterArmor*)ptr)[5].Set, 0, ((CharacterArmor*)ptr)[5].Variant, FullEquipType.Ears, name: _nameEars ), + 6 => EquipItem.FromIds(_itemIds[ 6], _iconIds[ 6], ((CharacterArmor*)ptr)[6].Set, 0, ((CharacterArmor*)ptr)[6].Variant, FullEquipType.Neck, name: _nameNeck ), + 7 => EquipItem.FromIds(_itemIds[ 7], _iconIds[ 7], ((CharacterArmor*)ptr)[7].Set, 0, ((CharacterArmor*)ptr)[7].Variant, FullEquipType.Wrists, name: _nameWrists ), + 8 => EquipItem.FromIds(_itemIds[ 8], _iconIds[ 8], ((CharacterArmor*)ptr)[8].Set, 0, ((CharacterArmor*)ptr)[8].Variant, FullEquipType.Finger, name: _nameRFinger ), + 9 => EquipItem.FromIds(_itemIds[ 9], _iconIds[ 9], ((CharacterArmor*)ptr)[9].Set, 0, ((CharacterArmor*)ptr)[9].Variant, FullEquipType.Finger, name: _nameLFinger ), + 10 => EquipItem.FromIds(_itemIds[10], _iconIds[10], *(PrimaryId*)(ptr + EquipmentByteSize + 0), *(SecondaryId*)(ptr + EquipmentByteSize + 2), *(Variant*)(ptr + EquipmentByteSize + 4), _typeMainhand, name: _nameMainhand), + 11 => EquipItem.FromIds(_itemIds[11], _iconIds[11], *(PrimaryId*)(ptr + EquipmentByteSize + 4), *(SecondaryId*)(ptr + EquipmentByteSize + 2), *(Variant*)(ptr + EquipmentByteSize + 4), _typeOffhand, name: _nameOffhand ), _ => new EquipItem(), - // @formatter:on - }; + // @formatter:on + }; + } + } public readonly CharacterArmor Armor(EquipSlot slot) { @@ -113,8 +126,8 @@ public unsafe struct DesignData { fixed (byte* ptr = _equipmentBytes) { - var armorPtr = (CharacterArmor*)ptr; - return slot is EquipSlot.MainHand ? armorPtr[10].ToWeapon(_secondaryMainhand) : armorPtr[11].ToWeapon(_secondaryOffhand); + var weaponPtr = (CharacterWeapon*)(ptr + EquipmentByteSize); + return weaponPtr[slot is EquipSlot.MainHand ? 0 : 1]; } } @@ -124,11 +137,11 @@ public unsafe struct DesignData if (index > 11) return false; - _itemIds[index] = item.ItemId.Id; - _iconIds[index] = item.IconId.Id; - _equipmentBytes[4 * index + 0] = (byte)item.PrimaryId.Id; - _equipmentBytes[4 * index + 1] = (byte)(item.PrimaryId.Id >> 8); - _equipmentBytes[4 * index + 2] = item.Variant.Id; + _itemIds[index] = item.ItemId.Id; + _iconIds[index] = item.IconId.Id; + _equipmentBytes[CharacterArmor.Size * index + 0] = (byte)item.PrimaryId.Id; + _equipmentBytes[CharacterArmor.Size * index + 1] = (byte)(item.PrimaryId.Id >> 8); + _equipmentBytes[CharacterArmor.Size * index + 2] = item.Variant.Id; switch (index) { // @formatter:off @@ -144,36 +157,40 @@ public unsafe struct DesignData case 9: _nameLFinger = item.Name; return true; // @formatter:on case 10: - _nameMainhand = item.Name; - _secondaryMainhand = item.SecondaryId; - _typeMainhand = item.Type; + _nameMainhand = item.Name; + _equipmentBytes[EquipmentByteSize + 2] = (byte)item.SecondaryId.Id; + _equipmentBytes[EquipmentByteSize + 3] = (byte)(item.SecondaryId.Id >> 8); + _typeMainhand = item.Type; return true; case 11: - _nameOffhand = item.Name; - _secondaryOffhand = item.SecondaryId; - _typeOffhand = item.Type; + _nameOffhand = item.Name; + _equipmentBytes[EquipmentByteSize + 2] = (byte)item.SecondaryId.Id; + _equipmentBytes[EquipmentByteSize + 3] = (byte)(item.SecondaryId.Id >> 8); + _typeOffhand = item.Type; return true; } return true; } - public bool SetStain(EquipSlot slot, StainId stain) + public bool SetStain(EquipSlot slot, StainIds stains) => slot.ToIndex() switch { - 0 => SetIfDifferent(ref _equipmentBytes[3], stain.Id), - 1 => SetIfDifferent(ref _equipmentBytes[7], stain.Id), - 2 => SetIfDifferent(ref _equipmentBytes[11], stain.Id), - 3 => SetIfDifferent(ref _equipmentBytes[15], stain.Id), - 4 => SetIfDifferent(ref _equipmentBytes[19], stain.Id), - 5 => SetIfDifferent(ref _equipmentBytes[23], stain.Id), - 6 => SetIfDifferent(ref _equipmentBytes[27], stain.Id), - 7 => SetIfDifferent(ref _equipmentBytes[31], stain.Id), - 8 => SetIfDifferent(ref _equipmentBytes[35], stain.Id), - 9 => SetIfDifferent(ref _equipmentBytes[39], stain.Id), - 10 => SetIfDifferent(ref _equipmentBytes[43], stain.Id), - 11 => SetIfDifferent(ref _equipmentBytes[47], stain.Id), - _ => false, + // @formatter:off + 0 => SetIfDifferent(ref _equipmentBytes[0 * CharacterArmor.Size + 3], stains.Stain1.Id) || SetIfDifferent(ref _equipmentBytes[0 * CharacterArmor.Size + 4], stains.Stain2.Id), + 1 => SetIfDifferent(ref _equipmentBytes[1 * CharacterArmor.Size + 3], stains.Stain1.Id) || SetIfDifferent(ref _equipmentBytes[1 * CharacterArmor.Size + 4], stains.Stain2.Id), + 2 => SetIfDifferent(ref _equipmentBytes[2 * CharacterArmor.Size + 3], stains.Stain1.Id) || SetIfDifferent(ref _equipmentBytes[2 * CharacterArmor.Size + 4], stains.Stain2.Id), + 3 => SetIfDifferent(ref _equipmentBytes[3 * CharacterArmor.Size + 3], stains.Stain1.Id) || SetIfDifferent(ref _equipmentBytes[3 * CharacterArmor.Size + 4], stains.Stain2.Id), + 4 => SetIfDifferent(ref _equipmentBytes[4 * CharacterArmor.Size + 3], stains.Stain1.Id) || SetIfDifferent(ref _equipmentBytes[4 * CharacterArmor.Size + 4], stains.Stain2.Id), + 5 => SetIfDifferent(ref _equipmentBytes[5 * CharacterArmor.Size + 3], stains.Stain1.Id) || SetIfDifferent(ref _equipmentBytes[5 * CharacterArmor.Size + 4], stains.Stain2.Id), + 6 => SetIfDifferent(ref _equipmentBytes[6 * CharacterArmor.Size + 3], stains.Stain1.Id) || SetIfDifferent(ref _equipmentBytes[6 * CharacterArmor.Size + 4], stains.Stain2.Id), + 7 => SetIfDifferent(ref _equipmentBytes[7 * CharacterArmor.Size + 3], stains.Stain1.Id) || SetIfDifferent(ref _equipmentBytes[7 * CharacterArmor.Size + 4], stains.Stain2.Id), + 8 => SetIfDifferent(ref _equipmentBytes[8 * CharacterArmor.Size + 3], stains.Stain1.Id) || SetIfDifferent(ref _equipmentBytes[8 * CharacterArmor.Size + 4], stains.Stain2.Id), + 9 => SetIfDifferent(ref _equipmentBytes[9 * CharacterArmor.Size + 3], stains.Stain1.Id) || SetIfDifferent(ref _equipmentBytes[9 * CharacterArmor.Size + 4], stains.Stain2.Id), + 10 => SetIfDifferent(ref _equipmentBytes[EquipmentByteSize + 6], stains.Stain1.Id) || SetIfDifferent(ref _equipmentBytes[EquipmentByteSize + 7], stains.Stain2.Id), + 11 => SetIfDifferent(ref _equipmentBytes[EquipmentByteSize + 14], stains.Stain1.Id) || SetIfDifferent(ref _equipmentBytes[EquipmentByteSize + 15], stains.Stain2.Id), + _ => false, + // @formatter:on }; public bool SetCrest(CrestFlag slot, bool visible) @@ -260,15 +277,15 @@ public unsafe struct DesignData foreach (var slot in EquipSlotExtensions.EqdpSlots) { SetItem(slot, ItemManager.NothingItem(slot)); - SetStain(slot, 0); + SetStain(slot, StainIds.None); SetCrest(slot.ToCrestFlag(), false); } SetItem(EquipSlot.MainHand, items.DefaultSword); - SetStain(EquipSlot.MainHand, 0); + SetStain(EquipSlot.MainHand, StainIds.None); SetCrest(CrestFlag.MainHand, false); SetItem(EquipSlot.OffHand, ItemManager.NothingItem(FullEquipType.Shield)); - SetStain(EquipSlot.OffHand, 0); + SetStain(EquipSlot.OffHand, StainIds.None); SetCrest(CrestFlag.OffHand, false); } @@ -291,21 +308,22 @@ public unsafe struct DesignData MemoryUtility.MemSet(ptr, 0, 10 * 4); } - fixed (ushort* ptr = _iconIds) + fixed (uint* ptr = _iconIds) { MemoryUtility.MemSet(ptr, 0, 10 * 2); } - _nameHead = string.Empty; - _nameBody = string.Empty; - _nameHands = string.Empty; - _nameLegs = string.Empty; - _nameFeet = string.Empty; - _nameEars = string.Empty; - _nameNeck = string.Empty; - _nameWrists = string.Empty; - _nameRFinger = string.Empty; - _nameLFinger = string.Empty; + _nameHead = string.Empty; + _nameBody = string.Empty; + _nameHands = string.Empty; + _nameLegs = string.Empty; + _nameFeet = string.Empty; + _nameEars = string.Empty; + _nameNeck = string.Empty; + _nameWrists = string.Empty; + _nameRFinger = string.Empty; + _nameLFinger = string.Empty; + _nameFaceWear = string.Empty; return true; } @@ -322,7 +340,7 @@ public unsafe struct DesignData public readonly byte[] GetEquipmentBytes() { - var ret = new byte[40]; + var ret = new byte[80]; fixed (byte* retPtr = ret, inPtr = _equipmentBytes) { MemoryUtility.MemCpyUnchecked(retPtr, inPtr, ret.Length); @@ -343,8 +361,8 @@ public unsafe struct DesignData { fixed (byte* dataPtr = _equipmentBytes) { - var data = new Span(dataPtr, 40); - return Convert.TryFromBase64String(base64, data, out var written) && written == 40; + var data = new Span(dataPtr, 80); + return Convert.TryFromBase64String(base64, data, out var written) && written == 80; } } diff --git a/Glamourer/Designs/DesignEditor.cs b/Glamourer/Designs/DesignEditor.cs index 91050ec..32e38ac 100644 --- a/Glamourer/Designs/DesignEditor.cs +++ b/Glamourer/Designs/DesignEditor.cs @@ -3,7 +3,6 @@ using Glamourer.Events; using Glamourer.GameData; using Glamourer.Interop.Material; using Glamourer.Services; -using Glamourer.State; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -167,29 +166,29 @@ public class DesignEditor( } /// - public void ChangeStain(object data, EquipSlot slot, StainId stain, ApplySettings _ = default) + public void ChangeStains(object data, EquipSlot slot, StainIds stains, ApplySettings _ = default) { var design = (Design)data; - if (Items.ValidateStain(stain, out var _, false).Length > 0) + if (Items.ValidateStain(stains, out var _, false).Length > 0) return; var oldStain = design.DesignData.Stain(slot); - if (!design.GetDesignDataRef().SetStain(slot, stain)) + if (!design.GetDesignDataRef().SetStain(slot, stains)) return; design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); - Glamourer.Log.Debug($"Set stain of {slot} equipment piece to {stain.Id}."); - DesignChanged.Invoke(DesignChanged.Type.Stain, design, (oldStain, stain, slot)); + Glamourer.Log.Debug($"Set stain of {slot} equipment piece to {stains}."); + DesignChanged.Invoke(DesignChanged.Type.Stain, design, (oldStain, stains, slot)); } /// - public void ChangeEquip(object data, EquipSlot slot, EquipItem? item, StainId? stain, ApplySettings _ = default) + public void ChangeEquip(object data, EquipSlot slot, EquipItem? item, StainIds? stains, ApplySettings _ = default) { if (item.HasValue) ChangeItem(data, slot, item.Value, _); - if (stain.HasValue) - ChangeStain(data, slot, stain.Value, _); + if (stains.HasValue) + ChangeStains(data, slot, stains.Value, _); } /// diff --git a/Glamourer/Designs/DesignFileSystem.cs b/Glamourer/Designs/DesignFileSystem.cs index 00277c2..f154684 100644 --- a/Glamourer/Designs/DesignFileSystem.cs +++ b/Glamourer/Designs/DesignFileSystem.cs @@ -1,4 +1,4 @@ -using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.ImGuiNotification; using Glamourer.Events; using Glamourer.Services; using Newtonsoft.Json; diff --git a/Glamourer/Designs/IDesignEditor.cs b/Glamourer/Designs/IDesignEditor.cs index a0aab84..cd51cf2 100644 --- a/Glamourer/Designs/IDesignEditor.cs +++ b/Glamourer/Designs/IDesignEditor.cs @@ -65,11 +65,11 @@ public interface IDesignEditor => ChangeEquip(data, slot, item, null, settings); /// Change the stain for any equipment piece. - public void ChangeStain(object data, EquipSlot slot, StainId stain, ApplySettings settings = default) - => ChangeEquip(data, slot, null, stain, settings); + public void ChangeStains(object data, EquipSlot slot, StainIds stains, ApplySettings settings = default) + => ChangeEquip(data, slot, null, stains, settings); /// Change an equipment piece and its stain at the same time. - public void ChangeEquip(object data, EquipSlot slot, EquipItem? item, StainId? stain, ApplySettings settings = default); + public void ChangeEquip(object data, EquipSlot slot, EquipItem? item, StainIds? stains, ApplySettings settings = default); /// Change the crest visibility for any equipment piece. public void ChangeCrest(object data, CrestFlag slot, bool crest, ApplySettings settings = default); diff --git a/Glamourer/Designs/Links/DesignLinkLoader.cs b/Glamourer/Designs/Links/DesignLinkLoader.cs index 4d438bd..fc7a26d 100644 --- a/Glamourer/Designs/Links/DesignLinkLoader.cs +++ b/Glamourer/Designs/Links/DesignLinkLoader.cs @@ -1,7 +1,8 @@ -using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.ImGuiNotification; using OtterGui; using OtterGui.Classes; using OtterGui.Services; +using Notification = OtterGui.Classes.Notification; namespace Glamourer.Designs.Links; diff --git a/Glamourer/EphemeralConfig.cs b/Glamourer/EphemeralConfig.cs index 027685f..3e13dc4 100644 --- a/Glamourer/EphemeralConfig.cs +++ b/Glamourer/EphemeralConfig.cs @@ -1,4 +1,4 @@ -using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.ImGuiNotification; using Glamourer.Gui; using Glamourer.Services; using Newtonsoft.Json; diff --git a/Glamourer/Events/MovedEquipment.cs b/Glamourer/Events/MovedEquipment.cs index 53491f1..9d24a03 100644 --- a/Glamourer/Events/MovedEquipment.cs +++ b/Glamourer/Events/MovedEquipment.cs @@ -11,7 +11,7 @@ namespace Glamourer.Events; /// /// public sealed class MovedEquipment() - : EventWrapper<(EquipSlot, uint, StainId)[], MovedEquipment.Priority>(nameof(MovedEquipment)) + : EventWrapper<(EquipSlot, uint, StainIds)[], MovedEquipment.Priority>(nameof(MovedEquipment)) { public enum Priority { diff --git a/Glamourer/GameData/CustomizeManager.cs b/Glamourer/GameData/CustomizeManager.cs index 57df104..5a06cf4 100644 --- a/Glamourer/GameData/CustomizeManager.cs +++ b/Glamourer/GameData/CustomizeManager.cs @@ -1,4 +1,5 @@ -using Dalamud.Interface.Internal; +using Dalamud.Interface.Textures; +using Dalamud.Interface.Textures.TextureWraps; using Dalamud.Plugin.Services; using OtterGui.Classes; using OtterGui.Services; @@ -32,8 +33,8 @@ public class CustomizeManager : IAsyncDataContainer } /// Get specific icons. - public IDalamudTextureWrap GetIcon(uint id) - => _icons.LoadIcon(id)!; + public ISharedImmediateTexture GetIcon(uint id) + => _icons.TextureProvider.GetFromGameIcon(id); /// Iterate over all supported genders and clans. public static IEnumerable<(SubRace Clan, Gender Gender)> AllSets() @@ -47,8 +48,8 @@ public class CustomizeManager : IAsyncDataContainer public CustomizeManager(ITextureProvider textures, IDataManager gameData, IPluginLog log, NpcCustomizeSet npcCustomizeSet) { - _icons = new IconStorage(textures, gameData); - var stopwatch = new Stopwatch(); + _icons = new TextureCache(gameData, textures); + var stopwatch = new Stopwatch(); var tmpTask = Task.Run(() => { stopwatch.Start(); @@ -72,7 +73,7 @@ public class CustomizeManager : IAsyncDataContainer public bool Finished => Awaiter.IsCompletedSuccessfully; - private readonly IconStorage _icons; + private readonly TextureCache _icons; private static readonly int ListSize = Clans.Count * Genders.Count; private readonly CustomizeSet[] _customizationSets = new CustomizeSet[ListSize]; diff --git a/Glamourer/GameData/CustomizeSetFactory.cs b/Glamourer/GameData/CustomizeSetFactory.cs index ba892ec..850c7c9 100644 --- a/Glamourer/GameData/CustomizeSetFactory.cs +++ b/Glamourer/GameData/CustomizeSetFactory.cs @@ -1,4 +1,5 @@ using Dalamud; +using Dalamud.Game; using Dalamud.Plugin.Services; using Dalamud.Utility; using Lumina.Excel; @@ -13,11 +14,11 @@ namespace Glamourer.GameData; internal class CustomizeSetFactory( IDataManager _gameData, IPluginLog _log, - IconStorage _icons, + TextureCache _icons, NpcCustomizeSet _npcCustomizeSet, ColorParameters _colors) { - public CustomizeSetFactory(IDataManager gameData, IPluginLog log, IconStorage icons, NpcCustomizeSet npcCustomizeSet) + public CustomizeSetFactory(IDataManager gameData, IPluginLog log, TextureCache icons, NpcCustomizeSet npcCustomizeSet) : this(gameData, log, icons, npcCustomizeSet, new ColorParameters(gameData, log)) { } @@ -87,7 +88,8 @@ internal class CustomizeSetFactory( var npcCustomizations = new HashSet<(CustomizeIndex, CustomizeValue)>(); _npcCustomizeSet.Awaiter.Wait(); - foreach (var customize in _npcCustomizeSet.Select(s => s.Customize).Where(c => c.Clan == race && c.Gender == gender && c.BodyType.Value == 1)) + foreach (var customize in _npcCustomizeSet.Select(s => s.Customize) + .Where(c => c.Clan == race && c.Gender == gender && c.BodyType.Value == 1)) { foreach (var customizeIndex in customizeIndices) { @@ -346,10 +348,6 @@ internal class CustomizeSetFactory( /// Set the availability of options according to actual availability. private static void SetAvailability(CustomizeSet set, CharaMakeParams row) { - // TODO: Hrothgar female - if (set is { Race: Race.Hrothgar, Gender: Gender.Female }) - return; - Set(true, CustomizeIndex.Height); Set(set.Faces.Count > 0, CustomizeIndex.Face); Set(true, CustomizeIndex.Hairstyle); diff --git a/Glamourer/GameData/NpcCustomizeSet.cs b/Glamourer/GameData/NpcCustomizeSet.cs index d9d8f27..dbf74cc 100644 --- a/Glamourer/GameData/NpcCustomizeSet.cs +++ b/Glamourer/GameData/NpcCustomizeSet.cs @@ -80,25 +80,9 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList // Event NPCs have a reference to NpcEquip but also contain the appearance in their own row. // Prefer the NpcEquip reference if it is set, otherwise use the own. if (row.NpcEquip.Row != 0 && row.NpcEquip.Value is { } equip) - { ApplyNpcEquip(ref ret, equip); - } else - { - ret.Set(0, row.ModelHead | (row.DyeHead.Row << 24)); - ret.Set(1, row.ModelBody | (row.DyeBody.Row << 24)); - ret.Set(2, row.ModelHands | (row.DyeHands.Row << 24)); - ret.Set(3, row.ModelLegs | (row.DyeLegs.Row << 24)); - ret.Set(4, row.ModelFeet | (row.DyeFeet.Row << 24)); - ret.Set(5, row.ModelEars | (row.DyeEars.Row << 24)); - ret.Set(6, row.ModelNeck | (row.DyeNeck.Row << 24)); - ret.Set(7, row.ModelWrists | (row.DyeWrists.Row << 24)); - ret.Set(8, row.ModelRightRing | (row.DyeRightRing.Row << 24)); - ret.Set(9, row.ModelLeftRing | (row.DyeLeftRing.Row << 24)); - ret.Mainhand = new CharacterWeapon(row.ModelMainHand | ((ulong)row.DyeMainHand.Row << 48)); - ret.Offhand = new CharacterWeapon(row.ModelOffHand | ((ulong)row.DyeOffHand.Row << 48)); - ret.VisorToggled = row.Visor; - } + ApplyNpcEquip(ref ret, row); list.Add(ret); } @@ -202,18 +186,36 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList /// Apply equipment from a NpcEquip row. private static void ApplyNpcEquip(ref NpcData data, NpcEquip row) { - data.Set(0, row.ModelHead | (row.DyeHead.Row << 24)); - data.Set(1, row.ModelBody | (row.DyeBody.Row << 24)); - data.Set(2, row.ModelHands | (row.DyeHands.Row << 24)); - data.Set(3, row.ModelLegs | (row.DyeLegs.Row << 24)); - data.Set(4, row.ModelFeet | (row.DyeFeet.Row << 24)); - data.Set(5, row.ModelEars | (row.DyeEars.Row << 24)); - data.Set(6, row.ModelNeck | (row.DyeNeck.Row << 24)); - data.Set(7, row.ModelWrists | (row.DyeWrists.Row << 24)); - data.Set(8, row.ModelRightRing | (row.DyeRightRing.Row << 24)); - data.Set(9, row.ModelLeftRing | (row.DyeLeftRing.Row << 24)); - data.Mainhand = new CharacterWeapon(row.ModelMainHand | ((ulong)row.DyeMainHand.Row << 48)); - data.Offhand = new CharacterWeapon(row.ModelOffHand | ((ulong)row.DyeOffHand.Row << 48)); + data.Set(0, row.ModelHead | (row.DyeHead.Row << 24) | ((ulong)row.Dye2Head.Row << 32)); + data.Set(1, row.ModelBody | (row.DyeBody.Row << 24) | ((ulong)row.Dye2Body.Row << 32)); + data.Set(2, row.ModelHands | (row.DyeHands.Row << 24) | ((ulong)row.Dye2Hands.Row << 32)); + data.Set(3, row.ModelLegs | (row.DyeLegs.Row << 24) | ((ulong)row.Dye2Legs.Row << 32)); + data.Set(4, row.ModelFeet | (row.DyeFeet.Row << 24) | ((ulong)row.Dye2Feet.Row << 32)); + data.Set(5, row.ModelEars | (row.DyeEars.Row << 24) | ((ulong)row.Dye2Ears.Row << 32)); + data.Set(6, row.ModelNeck | (row.DyeNeck.Row << 24) | ((ulong)row.Dye2Neck.Row << 32)); + data.Set(7, row.ModelWrists | (row.DyeWrists.Row << 24) | ((ulong)row.Dye2Wrists.Row << 32)); + data.Set(8, row.ModelRightRing | (row.DyeRightRing.Row << 24) | ((ulong)row.Dye2RightRing.Row << 32)); + data.Set(9, row.ModelLeftRing | (row.DyeLeftRing.Row << 24) | ((ulong)row.Dye2LeftRing.Row << 32)); + data.Mainhand = new CharacterWeapon(row.ModelMainHand | ((ulong)row.DyeMainHand.Row << 48) | ((ulong)row.Dye2MainHand.Row << 56)); + data.Offhand = new CharacterWeapon(row.ModelOffHand | ((ulong)row.DyeOffHand.Row << 48) | ((ulong)row.Dye2OffHand.Row << 56)); + data.VisorToggled = row.Visor; + } + + /// Apply equipment from a ENpcBase Row row. + private static void ApplyNpcEquip(ref NpcData data, ENpcBase row) + { + data.Set(0, row.ModelHead | (row.DyeHead.Row << 24) | ((ulong)row.Dye2Head.Row << 32)); + data.Set(1, row.ModelBody | (row.DyeBody.Row << 24) | ((ulong)row.Dye2Body.Row << 32)); + data.Set(2, row.ModelHands | (row.DyeHands.Row << 24) | ((ulong)row.Dye2Hands.Row << 32)); + data.Set(3, row.ModelLegs | (row.DyeLegs.Row << 24) | ((ulong)row.Dye2Legs.Row << 32)); + data.Set(4, row.ModelFeet | (row.DyeFeet.Row << 24) | ((ulong)row.Dye2Feet.Row << 32)); + data.Set(5, row.ModelEars | (row.DyeEars.Row << 24) | ((ulong)row.Dye2Ears.Row << 32)); + data.Set(6, row.ModelNeck | (row.DyeNeck.Row << 24) | ((ulong)row.Dye2Neck.Row << 32)); + data.Set(7, row.ModelWrists | (row.DyeWrists.Row << 24) | ((ulong)row.Dye2Wrists.Row << 32)); + data.Set(8, row.ModelRightRing | (row.DyeRightRing.Row << 24) | ((ulong)row.Dye2RightRing.Row << 32)); + data.Set(9, row.ModelLeftRing | (row.DyeLeftRing.Row << 24) | ((ulong)row.Dye2LeftRing.Row << 32)); + data.Mainhand = new CharacterWeapon(row.ModelMainHand | ((ulong)row.DyeMainHand.Row << 48) | ((ulong)row.Dye2MainHand.Row << 56)); + data.Offhand = new CharacterWeapon(row.ModelOffHand | ((ulong)row.DyeOffHand.Row << 48) | ((ulong)row.Dye2OffHand.Row << 56)); data.VisorToggled = row.Visor; } diff --git a/Glamourer/GameData/NpcData.cs b/Glamourer/GameData/NpcData.cs index 6b5f2bd..0076bb6 100644 --- a/Glamourer/GameData/NpcData.cs +++ b/Glamourer/GameData/NpcData.cs @@ -13,7 +13,7 @@ public unsafe struct NpcData public CustomizeArray Customize; /// The equipment appearance of the NPC, 10 * CharacterArmor. - private fixed byte _equip[40]; + private fixed byte _equip[CharacterArmor.Size * 10]; /// The mainhand weapon appearance of the NPC. public CharacterWeapon Mainhand; @@ -54,36 +54,35 @@ public unsafe struct NpcData { sb.Append(span[i].Set.Id.ToString("D4")) .Append('-') - .Append(span[i].Variant.Id.ToString("D3")) - .Append('-') - .Append(span[i].Stain.Id.ToString("D3")) - .Append(", "); + .Append(span[i].Variant.Id.ToString("D3")); + foreach (var stain in span[i].Stains) + sb.Append('-').Append(stain.Id.ToString("D3")); } sb.Append(Mainhand.Skeleton.Id.ToString("D4")) .Append('-') .Append(Mainhand.Weapon.Id.ToString("D4")) .Append('-') - .Append(Mainhand.Variant.Id.ToString("D3")) - .Append('-') - .Append(Mainhand.Stain.Id.ToString("D4")) - .Append(", ") + .Append(Mainhand.Variant.Id.ToString("D3")); + foreach (var stain in Mainhand.Stains) + sb.Append('-').Append(stain.Id.ToString("D3")); + sb.Append(", ") .Append(Offhand.Skeleton.Id.ToString("D4")) .Append('-') .Append(Offhand.Weapon.Id.ToString("D4")) .Append('-') - .Append(Offhand.Variant.Id.ToString("D3")) - .Append('-') - .Append(Offhand.Stain.Id.ToString("D3")); + .Append(Offhand.Variant.Id.ToString("D3")); + foreach (var stain in Mainhand.Stains) + sb.Append('-').Append(stain.Id.ToString("D3")); return sb.ToString(); } /// Set an equipment piece to a given value. - internal void Set(int idx, uint value) + internal void Set(int idx, ulong value) { fixed (byte* ptr = _equip) { - ((uint*)ptr)[idx] = value; + ((ulong*)ptr)[idx] = value; } } diff --git a/Glamourer/Glamourer.cs b/Glamourer/Glamourer.cs index 5dd6040..84312d9 100644 --- a/Glamourer/Glamourer.cs +++ b/Glamourer/Glamourer.cs @@ -28,7 +28,7 @@ public class Glamourer : IDalamudPlugin private readonly ServiceManager _services; - public Glamourer(DalamudPluginInterface pluginInterface) + public Glamourer(IDalamudPluginInterface pluginInterface) { try { @@ -128,7 +128,7 @@ public class Glamourer : IDalamudPlugin [ "Penumbra", "MareSynchronos", "CustomizePlus", "SimpleHeels", "VfxEditor", "heliosphere-plugin", "Ktisis", "Brio", "DynamicBridge", ]; - var plugins = _services.GetService().InstalledPlugins + var plugins = _services.GetService().InstalledPlugins .GroupBy(p => p.InternalName) .ToDictionary(g => g.Key, g => { diff --git a/Glamourer/Glamourer.csproj b/Glamourer/Glamourer.csproj index 9a1b95b..3251543 100644 --- a/Glamourer/Glamourer.csproj +++ b/Glamourer/Glamourer.csproj @@ -49,6 +49,7 @@ $(AppData)\XIVLauncher\addon\Hooks\dev\ + H:\Projects\FFPlugins\Dalamud\bin\Release\ diff --git a/Glamourer/Glamourer.json b/Glamourer/Glamourer.json index cdf2cba..08c18f5 100644 --- a/Glamourer/Glamourer.json +++ b/Glamourer/Glamourer.json @@ -8,7 +8,7 @@ "AssemblyVersion": "9.0.0.1", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", - "DalamudApiLevel": 9, + "DalamudApiLevel": 10, "ImageUrls": null, "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/master/images/icon.png" } \ No newline at end of file diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.GenderRace.cs b/Glamourer/Gui/Customization/CustomizationDrawer.GenderRace.cs index ae64075..2f67012 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.GenderRace.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.GenderRace.cs @@ -34,14 +34,13 @@ public partial class CustomizationDrawer private void DrawGenderSelector() { - using (var disabled = ImRaii.Disabled(_locked || _lockedRedraw)) + using (ImRaii.Disabled(_locked || _lockedRedraw)) { var icon = _customize.Gender switch { - Gender.Male when _customize.Race is Race.Hrothgar => FontAwesomeIcon.MarsDouble, - Gender.Male => FontAwesomeIcon.Mars, - Gender.Female => FontAwesomeIcon.Venus, - _ => FontAwesomeIcon.Question, + Gender.Male => FontAwesomeIcon.Mars, + Gender.Female => FontAwesomeIcon.Venus, + _ => FontAwesomeIcon.Question, }; if (ImGuiUtil.DrawDisabledButton(icon.ToIconString(), _framedIconSize, string.Empty, @@ -56,7 +55,7 @@ public partial class CustomizationDrawer private void DrawRaceCombo() { - using (var disabled = ImRaii.Disabled(_locked || _lockedRedraw)) + using (ImRaii.Disabled(_locked || _lockedRedraw)) { ImGui.SetNextItemWidth(_raceSelectorWidth); using (var combo = ImRaii.Combo("##subRaceCombo", _service.ClanName(_customize.Clan, _customize.Gender))) diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs b/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs index c0c45d2..8631481 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs @@ -29,10 +29,11 @@ public partial class CustomizationDrawer npc = true; } - var icon = _service.Manager.GetIcon(custom!.Value.IconId); + var icon = _service.Manager.GetIcon(custom!.Value.IconId); + var hasIcon = icon.TryGetWrap(out var wrap, out _); using (_ = ImRaii.Disabled(_locked || _currentIndex is CustomizeIndex.Face && _lockedRedraw)) { - if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize)) + if (ImGui.ImageButton(wrap?.ImGuiHandle ?? icon.GetWrapOrEmpty().ImGuiHandle, _iconSize)) { ImGui.OpenPopup(IconSelectorPopup); } @@ -43,7 +44,8 @@ public partial class CustomizationDrawer } } - ImGuiUtil.HoverIconTooltip(icon, _iconSize); + if (hasIcon) + ImGuiUtil.HoverIconTooltip(wrap!, _iconSize); ImGui.SameLine(); using (_ = ImRaii.Group()) @@ -83,8 +85,9 @@ public partial class CustomizationDrawer using var frameColor = current == i ? ImRaii.PushColor(ImGuiCol.Button, Colors.SelectedRed) : ImRaii.PushColor(ImGuiCol.Button, ColorId.FavoriteStarOn.Value(), isFavorite); + var hasIcon = icon.TryGetWrap(out var wrap, out var _); - if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize)) + if (ImGui.ImageButton(wrap?.ImGuiHandle ?? icon.GetWrapOrEmpty().ImGuiHandle, _iconSize)) { UpdateValue(custom.Value); ImGui.CloseCurrentPopup(); @@ -96,8 +99,9 @@ public partial class CustomizationDrawer else _favorites.TryAdd(_set.Gender, _set.Clan, _currentIndex, custom.Value); - ImGuiUtil.HoverIconTooltip(icon, _iconSize, - FavoriteManager.TypeAllowed(_currentIndex) ? "Right-Click to toggle favorite." : string.Empty); + if (hasIcon) + ImGuiUtil.HoverIconTooltip(wrap!, _iconSize, + FavoriteManager.TypeAllowed(_currentIndex) ? "Right-Click to toggle favorite." : string.Empty); var text = custom.Value.ToString(); var textWidth = ImGui.CalcTextSize(text).X; @@ -199,14 +203,17 @@ public partial class CustomizationDrawer var icon = featureIdx == CustomizeIndex.LegacyTattoo ? _legacyTattoo ?? _service.Manager.GetIcon(feature.IconId) : _service.Manager.GetIcon(feature.IconId); - if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize, Vector2.Zero, Vector2.One, (int)ImGui.GetStyle().FramePadding.X, + var hasIcon = icon.TryGetWrap(out var wrap, out _); + if (ImGui.ImageButton(wrap?.ImGuiHandle ?? icon.GetWrapOrEmpty().ImGuiHandle, _iconSize, Vector2.Zero, Vector2.One, + (int)ImGui.GetStyle().FramePadding.X, Vector4.Zero, enabled ? Vector4.One : _redTint)) { _customize.Set(featureIdx, enabled ? CustomizeValue.Zero : CustomizeValue.Max); Changed |= _currentFlag; } - ImGuiUtil.HoverIconTooltip(icon, _iconSize); + if (hasIcon) + ImGuiUtil.HoverIconTooltip(wrap!, _iconSize); if (idx % 4 != 3) ImGui.SameLine(); } diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.cs b/Glamourer/Gui/Customization/CustomizationDrawer.cs index 60251df..384307c 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.cs @@ -1,4 +1,6 @@ using Dalamud.Interface.Internal; +using Dalamud.Interface.Textures; +using Dalamud.Interface.Textures.TextureWraps; using Dalamud.Interface.Utility; using Dalamud.Plugin; using Glamourer.GameData; @@ -6,6 +8,7 @@ using Glamourer.Services; using Glamourer.Unlocks; using ImGuiNET; using OtterGui; +using OtterGui.Classes; using OtterGui.Raii; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -13,16 +16,15 @@ using Penumbra.GameData.Structs; namespace Glamourer.Gui.Customization; public partial class CustomizationDrawer( - DalamudPluginInterface pi, + TextureCache textureCache, CustomizeService _service, CodeService _codes, Configuration _config, FavoriteManager _favorites, HeightService _heightService) - : IDisposable { - private readonly Vector4 _redTint = new(0.6f, 0.3f, 0.3f, 1f); - private readonly IDalamudTextureWrap? _legacyTattoo = GetLegacyTattooIcon(pi); + private readonly Vector4 _redTint = new(0.6f, 0.3f, 0.3f, 1f); + private readonly ISharedImmediateTexture? _legacyTattoo = GetLegacyTattooIcon(textureCache); private Exception? _terminate; @@ -47,9 +49,6 @@ public partial class CustomizationDrawer( private float _raceSelectorWidth; private bool _withApply; - public void Dispose() - => _legacyTattoo?.Dispose(); - public bool Draw(CustomizeArray current, bool locked, bool lockedRedraw) { _withApply = false; @@ -190,16 +189,6 @@ public partial class CustomizationDrawer( _raceSelectorWidth = _inputIntSize + _comboSelectorSize - _framedIconSize.X; } - private static IDalamudTextureWrap? GetLegacyTattooIcon(DalamudPluginInterface pi) - { - using var resource = Assembly.GetExecutingAssembly().GetManifestResourceStream("Glamourer.LegacyTattoo.raw"); - if (resource == null) - return null; - - var rawImage = new byte[resource.Length]; - var length = resource.Read(rawImage, 0, (int)resource.Length); - return length == resource.Length - ? pi.UiBuilder.LoadImageRaw(rawImage, 192, 192, 4) - : null; - } + private static ISharedImmediateTexture? GetLegacyTattooIcon(TextureCache icons) + => icons.TextureProvider.GetFromManifestResource(Assembly.GetExecutingAssembly(), "Glamourer.LegacyTattoo.raw"); } diff --git a/Glamourer/Gui/Equipment/EquipDrawData.cs b/Glamourer/Gui/Equipment/EquipDrawData.cs index e6b5d0d..58f7efc 100644 --- a/Glamourer/Gui/Equipment/EquipDrawData.cs +++ b/Glamourer/Gui/Equipment/EquipDrawData.cs @@ -23,8 +23,8 @@ public struct EquipDrawData(EquipSlot slot, in DesignData designData) public readonly void SetItem(EquipItem item) => _editor.ChangeItem(_object, Slot, item, ApplySettings.Manual); - public readonly void SetStain(StainId stain) - => _editor.ChangeStain(_object, Slot, stain, ApplySettings.Manual); + public readonly void SetStains(StainIds stains) + => _editor.ChangeStains(_object, Slot, stains, ApplySettings.Manual); public readonly void SetApplyItem(bool value) { @@ -40,10 +40,10 @@ public struct EquipDrawData(EquipSlot slot, in DesignData designData) manager.ChangeApplyStain(design, Slot, value); } - public EquipItem CurrentItem = designData.Item(slot); - public StainId CurrentStain = designData.Stain(slot); - public EquipItem GameItem = default; - public StainId GameStain = default; + public EquipItem CurrentItem = designData.Item(slot); + public StainIds CurrentStains = designData.Stain(slot); + public EquipItem GameItem = default; + public StainIds GameStains = default; public bool CurrentApply; public bool CurrentApplyStain; @@ -69,7 +69,7 @@ public struct EquipDrawData(EquipSlot slot, in DesignData designData) Locked = state.IsLocked, DisplayApplication = false, GameItem = state.BaseData.Item(slot), - GameStain = state.BaseData.Stain(slot), + GameStains = state.BaseData.Stain(slot), AllowRevert = true, }; } diff --git a/Glamourer/Gui/Equipment/EquipmentDrawer.cs b/Glamourer/Gui/Equipment/EquipmentDrawer.cs index c5e5f7e..53e8ed8 100644 --- a/Glamourer/Gui/Equipment/EquipmentDrawer.cs +++ b/Glamourer/Gui/Equipment/EquipmentDrawer.cs @@ -8,6 +8,7 @@ using Glamourer.Unlocks; using ImGuiNET; using OtterGui; using OtterGui.Raii; +using OtterGui.Text; using OtterGui.Widgets; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; @@ -236,14 +237,18 @@ public class EquipmentDrawer /// Draw an input for stain that can set arbitrary values instead of choosing valid stains. private static void DrawStainArtisan(EquipDrawData data) { - int stainId = data.CurrentStain.Id; - ImGui.SetNextItemWidth(40 * ImGuiHelpers.GlobalScale); - if (!ImGui.InputInt("##stain", ref stainId, 0, 0)) - return; + foreach (var (stain, index) in data.CurrentStains.WithIndex()) + { + using var id = ImUtf8.PushId(index); + int stainId = stain.Id; + ImGui.SetNextItemWidth(40 * ImGuiHelpers.GlobalScale); + if (!ImGui.InputInt("##stain", ref stainId, 0, 0)) + return; - var newStainId = (StainId)Math.Clamp(stainId, 0, byte.MaxValue); - if (newStainId != data.CurrentStain.Id) - data.SetStain(newStainId); + var newStainId = (StainId)Math.Clamp(stainId, 0, byte.MaxValue); + if (newStainId != stain.Id) + data.SetStains(data.CurrentStains.With(index, newStainId)); + } } /// Draw an input for armor that can set arbitrary values instead of choosing items. @@ -441,19 +446,27 @@ public class EquipmentDrawer private void DrawStain(in EquipDrawData data, bool small) { - var found = _stainData.TryGetValue(data.CurrentStain, out var stain); using var disabled = ImRaii.Disabled(data.Locked); - var change = small - ? _stainCombo.Draw($"##stain{data.Slot}", stain.RgbaColor, stain.Name, found, stain.Gloss) - : _stainCombo.Draw($"##stain{data.Slot}", stain.RgbaColor, stain.Name, found, stain.Gloss, _comboLength); - if (change) - if (_stainData.TryGetValue(_stainCombo.CurrentSelection.Key, out stain)) - data.SetStain(stain.RowIndex); - else if (_stainCombo.CurrentSelection.Key == Stain.None.RowIndex) - data.SetStain(Stain.None.RowIndex); + var width = (_comboLength - ImUtf8.ItemInnerSpacing.X * (data.CurrentStains.Count - 1)) / data.CurrentStains.Count; + foreach (var (stainId, index) in data.CurrentStains.WithIndex()) + { + using var id = ImUtf8.PushId(index); + var found = _stainData.TryGetValue(stainId, out var stain); + var change = small + ? _stainCombo.Draw($"##stain{data.Slot}", stain.RgbaColor, stain.Name, found, stain.Gloss) + : _stainCombo.Draw($"##stain{data.Slot}", stain.RgbaColor, stain.Name, found, stain.Gloss, width); + if (index < data.CurrentStains.Count - 1) + ImUtf8.SameLineInner(); - if (ResetOrClear(data.Locked, false, data.AllowRevert, true, data.CurrentStain, data.GameStain, Stain.None.RowIndex, out var newStain)) - data.SetStain(newStain); + if (change) + if (_stainData.TryGetValue(_stainCombo.CurrentSelection.Key, out stain)) + data.SetStains(data.CurrentStains.With(index, stain.RowIndex)); + else if (_stainCombo.CurrentSelection.Key == Stain.None.RowIndex) + data.SetStains(data.CurrentStains.With(index, Stain.None.RowIndex)); + if (ResetOrClear(data.Locked, false, data.AllowRevert, true, stainId, data.GameStains[index], Stain.None.RowIndex, + out var newStain)) + data.SetStains(data.CurrentStains.With(index, newStain)); + } } private void DrawItem(in EquipDrawData data, out string label, bool small, bool clear, bool open) diff --git a/Glamourer/Gui/GlamourerWindowSystem.cs b/Glamourer/Gui/GlamourerWindowSystem.cs index 6b34c78..f86f42b 100644 --- a/Glamourer/Gui/GlamourerWindowSystem.cs +++ b/Glamourer/Gui/GlamourerWindowSystem.cs @@ -7,11 +7,11 @@ namespace Glamourer.Gui; public class GlamourerWindowSystem : IDisposable { private readonly WindowSystem _windowSystem = new("Glamourer"); - private readonly UiBuilder _uiBuilder; + private readonly IUiBuilder _uiBuilder; private readonly MainWindow _ui; private readonly PenumbraChangedItemTooltip _penumbraTooltip; - public GlamourerWindowSystem(UiBuilder uiBuilder, MainWindow ui, GenericPopupWindow popups, PenumbraChangedItemTooltip penumbraTooltip, + public GlamourerWindowSystem(IUiBuilder uiBuilder, MainWindow ui, GenericPopupWindow popups, PenumbraChangedItemTooltip penumbraTooltip, Configuration config, UnlocksTab unlocksTab, GlamourerChangelog changelog, DesignQuickBar quick) { _uiBuilder = uiBuilder; diff --git a/Glamourer/Gui/MainWindow.cs b/Glamourer/Gui/MainWindow.cs index 007d936..f21a2b7 100644 --- a/Glamourer/Gui/MainWindow.cs +++ b/Glamourer/Gui/MainWindow.cs @@ -1,4 +1,4 @@ -using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.ImGuiNotification; using Dalamud.Interface.Windowing; using Dalamud.Plugin; using Glamourer.Designs; @@ -64,7 +64,7 @@ public class MainWindow : Window, IDisposable public TabType SelectTab; - public MainWindow(DalamudPluginInterface pi, Configuration config, SettingsTab settings, ActorTab actors, DesignTab designs, + public MainWindow(IDalamudPluginInterface pi, Configuration config, SettingsTab settings, ActorTab actors, DesignTab designs, DebugTab debugTab, AutomationTab automation, UnlocksTab unlocks, TabSelected @event, MessagesTab messages, DesignQuickBar quickBar, NpcTab npcs, MainWindowPosition position, PenumbraService penumbra) : base("GlamourerMainWindow") diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index 8371232..5218581 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -1,6 +1,6 @@ using Dalamud.Game.ClientState.Conditions; using Dalamud.Interface; -using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.ImGuiNotification; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game; using Glamourer.Automation; @@ -208,7 +208,7 @@ public class ActorPanel var data = EquipDrawData.FromState(_stateManager, _state!, slot); _equipmentDrawer.DrawEquip(data); if (usedAllStain) - _stateManager.ChangeStain(_state, slot, newAllStain, ApplySettings.Manual); + _stateManager.ChangeStains(_state, slot, newAllStain, ApplySettings.Manual); } var mainhand = EquipDrawData.FromState(_stateManager, _state, EquipSlot.MainHand); diff --git a/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs b/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs index 4aa0163..0a29314 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs @@ -87,8 +87,8 @@ public class ActiveStatePanel(StateManager _stateManager, ObjectManager _objectM foreach (var slot in EquipSlotExtensions.EqdpSlots.Prepend(EquipSlot.OffHand).Prepend(EquipSlot.MainHand)) { PrintRow(slot.ToName(), ItemString(state.BaseData, slot), ItemString(state.ModelData, slot), state.Sources[slot, false]); - ImGuiUtil.DrawTableColumn(state.BaseData.Stain(slot).Id.ToString()); - ImGuiUtil.DrawTableColumn(state.ModelData.Stain(slot).Id.ToString()); + ImGuiUtil.DrawTableColumn(state.BaseData.Stain(slot).ToString()); + ImGuiUtil.DrawTableColumn(state.ModelData.Stain(slot).ToString()); ImGuiUtil.DrawTableColumn(state.Sources[slot, true].ToString()); } diff --git a/Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs b/Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs index 2129c1f..ff9c2b8 100644 --- a/Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs @@ -8,8 +8,10 @@ using Glamourer.Services; using Glamourer.State; using ImGuiNET; using OtterGui; +using Penumbra.GameData; using Penumbra.GameData.Enums; using Penumbra.GameData.Gui.Debug; +using Penumbra.GameData.Structs; namespace Glamourer.Gui.Tabs.DebugTab; @@ -51,7 +53,7 @@ public unsafe class GlamourPlatePanel : IGameDataDrawer using (ImRaii.Group()) { ImGuiUtil.CopyOnClickSelectable($"0x{(ulong)manager:X}"); - ImGui.TextUnformatted(manager == null ? "-" : manager->GlamourPlatesSpan.Length.ToString()); + ImGui.TextUnformatted(manager == null ? "-" : manager->GlamourPlates.Length.ToString()); ImGui.TextUnformatted(manager == null ? "-" : manager->GlamourPlatesRequested.ToString()); ImGui.SameLine(); if (ImGui.SmallButton("Request Update")) @@ -67,13 +69,13 @@ public unsafe class GlamourPlatePanel : IGameDataDrawer var (identifier, data) = _objects.PlayerData; var enabled = data.Valid && _state.GetOrCreate(identifier, data.Objects[0], out state); - for (var i = 0; i < manager->GlamourPlatesSpan.Length; ++i) + for (var i = 0; i < manager->GlamourPlates.Length; ++i) { using var tree = ImRaii.TreeNode($"Plate #{i + 1:D2}"); if (!tree) continue; - ref var plate = ref manager->GlamourPlatesSpan[i]; + ref var plate = ref manager->GlamourPlates[i]; if (ImGuiUtil.DrawDisabledButton("Apply to Player", Vector2.Zero, string.Empty, !enabled)) { var design = CreateDesign(plate); @@ -90,12 +92,12 @@ public unsafe class GlamourPlatePanel : IGameDataDrawer using (ImRaii.Group()) { foreach (var (_, index) in EquipSlotExtensions.FullSlots.WithIndex()) - ImGui.TextUnformatted($"{plate.ItemIds[index]:D6}, {plate.StainIds[index]:D3}"); + ImGui.TextUnformatted($"{plate.ItemIds[index]:D6}, {StainIds.FromGlamourPlate(plate, index)}"); } } } - [Signature("E8 ?? ?? ?? ?? 32 C0 48 8B 5C 24 ?? 48 8B 6C 24 ?? 48 83 C4 ?? 5F")] + [Signature(Sigs.RequestGlamourPlates)] private readonly delegate* unmanaged _requestUpdate = null!; public void RequestGlamour() @@ -126,7 +128,7 @@ public unsafe class GlamourPlatePanel : IGameDataDrawer continue; design.GetDesignDataRef().SetItem(slot, item); - design.GetDesignDataRef().SetStain(slot, plate.StainIds[index]); + design.GetDesignDataRef().SetStain(slot, StainIds.FromGlamourPlate(plate, index)); design.ApplyEquip |= slot.ToBothFlags(); } diff --git a/Glamourer/Gui/Tabs/DebugTab/InventoryPanel.cs b/Glamourer/Gui/Tabs/DebugTab/InventoryPanel.cs index bd447e7..b021656 100644 --- a/Glamourer/Gui/Tabs/DebugTab/InventoryPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/InventoryPanel.cs @@ -43,8 +43,8 @@ public unsafe class InventoryPanel : IGameDataDrawer } else { - ImGuiUtil.DrawTableColumn(item->ItemID.ToString()); - ImGuiUtil.DrawTableColumn(item->GlamourID.ToString()); + ImGuiUtil.DrawTableColumn(item->ItemId.ToString()); + ImGuiUtil.DrawTableColumn(item->GlamourId.ToString()); ImGui.TableNextColumn(); ImGuiUtil.CopyOnClickSelectable($"0x{(ulong)item:X}"); } diff --git a/Glamourer/Gui/Tabs/DebugTab/IpcTester/DesignIpcTester.cs b/Glamourer/Gui/Tabs/DebugTab/IpcTester/DesignIpcTester.cs index 1a74778..918c7ad 100644 --- a/Glamourer/Gui/Tabs/DebugTab/IpcTester/DesignIpcTester.cs +++ b/Glamourer/Gui/Tabs/DebugTab/IpcTester/DesignIpcTester.cs @@ -10,7 +10,7 @@ using OtterGui.Services; namespace Glamourer.Gui.Tabs.DebugTab.IpcTester; -public class DesignIpcTester(DalamudPluginInterface pluginInterface) : IUiService +public class DesignIpcTester(IDalamudPluginInterface pluginInterface) : IUiService { private Dictionary _designs = []; private int _gameObjectIndex; diff --git a/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterPanel.cs b/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterPanel.cs index 5e6f4a2..8f561af 100644 --- a/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterPanel.cs @@ -7,7 +7,7 @@ using Penumbra.GameData.Gui.Debug; namespace Glamourer.Gui.Tabs.DebugTab.IpcTester; public class IpcTesterPanel( - DalamudPluginInterface pluginInterface, + IDalamudPluginInterface pluginInterface, DesignIpcTester designs, ItemsIpcTester items, StateIpcTester state, diff --git a/Glamourer/Gui/Tabs/DebugTab/IpcTester/ItemsIpcTester.cs b/Glamourer/Gui/Tabs/DebugTab/IpcTester/ItemsIpcTester.cs index 3d61df7..5f9e748 100644 --- a/Glamourer/Gui/Tabs/DebugTab/IpcTester/ItemsIpcTester.cs +++ b/Glamourer/Gui/Tabs/DebugTab/IpcTester/ItemsIpcTester.cs @@ -11,7 +11,7 @@ using Penumbra.GameData.Structs; namespace Glamourer.Gui.Tabs.DebugTab.IpcTester; -public class ItemsIpcTester(DalamudPluginInterface pluginInterface) : IUiService +public class ItemsIpcTester(IDalamudPluginInterface pluginInterface) : IUiService { private int _gameObjectIndex; private string _gameObjectName = string.Empty; @@ -40,12 +40,12 @@ public class ItemsIpcTester(DalamudPluginInterface pluginInterface) : IUiService IpcTesterHelpers.DrawIntro(SetItem.Label); if (ImGui.Button("Set##Idx")) - _lastError = new SetItem(pluginInterface).Invoke(_gameObjectIndex, (ApiEquipSlot)_slot, _customItemId.Id, _stainId.Id, _key, + _lastError = new SetItem(pluginInterface).Invoke(_gameObjectIndex, (ApiEquipSlot)_slot, _customItemId.Id, [_stainId.Id], _key, _flags); IpcTesterHelpers.DrawIntro(SetItemName.Label); if (ImGui.Button("Set##Name")) - _lastError = new SetItemName(pluginInterface).Invoke(_gameObjectName, (ApiEquipSlot)_slot, _customItemId.Id, _stainId.Id, _key, + _lastError = new SetItemName(pluginInterface).Invoke(_gameObjectName, (ApiEquipSlot)_slot, _customItemId.Id, [_stainId.Id], _key, _flags); } diff --git a/Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs b/Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs index 81c8cab..f378625 100644 --- a/Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs +++ b/Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs @@ -18,7 +18,7 @@ namespace Glamourer.Gui.Tabs.DebugTab.IpcTester; public class StateIpcTester : IUiService, IDisposable { - private readonly DalamudPluginInterface _pluginInterface; + private readonly IDalamudPluginInterface _pluginInterface; private int _gameObjectIndex; private string _gameObjectName = string.Empty; @@ -41,7 +41,7 @@ public class StateIpcTester : IUiService, IDisposable private int _numUnlocked; - public StateIpcTester(DalamudPluginInterface pluginInterface) + public StateIpcTester(IDalamudPluginInterface pluginInterface) { _pluginInterface = pluginInterface; StateChanged = Api.IpcSubscribers.StateChangedWithType.Subscriber(_pluginInterface, OnStateChanged); diff --git a/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs b/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs index c557064..ddf42f1 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs @@ -5,6 +5,8 @@ using Glamourer.Interop.Structs; using ImGuiNET; using OtterGui; using OtterGui.Raii; +using OtterGui.Text; +using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; using Penumbra.GameData.Gui.Debug; using Penumbra.GameData.Interop; @@ -18,7 +20,8 @@ public unsafe class ModelEvaluationPanel( VisorService _visorService, UpdateSlotService _updateSlotService, ChangeCustomizeService _changeCustomizeService, - CrestService _crestService) : IGameDataDrawer + CrestService _crestService, + DictGlasses _glasses) : IGameDataDrawer { public string Label => "Model Evaluation"; @@ -177,7 +180,7 @@ public unsafe class ModelEvaluationPanel( { using var id = ImRaii.PushId("Wetness"); ImGuiUtil.DrawTableColumn("Wetness"); - ImGuiUtil.DrawTableColumn(actor.IsCharacter ? actor.AsCharacter->IsGPoseWet ? "GPose" : "None" : "No Character"); + ImGuiUtil.DrawTableColumn(actor.IsCharacter ? actor.IsGPoseWet ? "GPose" : "None" : "No Character"); var modelString = model.IsCharacterBase ? $"{model.AsCharacterBase->SwimmingWetness:F4} Swimming\n" + $"{model.AsCharacterBase->WeatherWetness:F4} Weather\n" @@ -190,13 +193,13 @@ public unsafe class ModelEvaluationPanel( return; if (ImGui.SmallButton("GPose On")) - actor.AsCharacter->IsGPoseWet = true; + actor.IsGPoseWet = true; ImGui.SameLine(); if (ImGui.SmallButton("GPose Off")) - actor.AsCharacter->IsGPoseWet = false; + actor.IsGPoseWet = false; ImGui.SameLine(); if (ImGui.SmallButton("GPose Toggle")) - actor.AsCharacter->IsGPoseWet = !actor.AsCharacter->IsGPoseWet; + actor.IsGPoseWet = !actor.IsGPoseWet; } private void DrawEquip(Actor actor, Model model) @@ -214,14 +217,39 @@ public unsafe class ModelEvaluationPanel( if (ImGui.SmallButton("Change Piece")) _updateSlotService.UpdateArmor(model, slot, - new CharacterArmor((PrimaryId)(slot == EquipSlot.Hands ? 6064 : slot == EquipSlot.Head ? 6072 : 1), 1, 0)); + new CharacterArmor((PrimaryId)(slot == EquipSlot.Hands ? 6064 : slot == EquipSlot.Head ? 6072 : 1), 1, StainIds.None)); ImGui.SameLine(); if (ImGui.SmallButton("Change Stain")) - _updateSlotService.UpdateStain(model, slot, 5); + _updateSlotService.UpdateStain(model, slot, new StainIds(5, 7)); ImGui.SameLine(); if (ImGui.SmallButton("Reset")) _updateSlotService.UpdateSlot(model, slot, actor.GetArmor(slot)); } + + using (ImRaii.PushId((int)EquipSlot.FaceWear)) + { + ImGuiUtil.DrawTableColumn(EquipSlot.FaceWear.ToName()); + if (!actor.IsCharacter) + { + ImGuiUtil.DrawTableColumn("No Character"); + } + else + { + var glassesId = actor.AsCharacter->DrawData.GlassesIds[(int)EquipSlot.FaceWear.ToBonusIndex()]; + if (_glasses.TryGetValue(glassesId, out var glasses)) + ImGuiUtil.DrawTableColumn($"{glasses.Id.Id},{glasses.Variant.Id} ({glassesId})"); + else + ImGuiUtil.DrawTableColumn($"{glassesId}"); + } + + ImGuiUtil.DrawTableColumn(model.IsHuman ? model.GetArmor(EquipSlot.FaceWear).ToString() : "No Human"); + ImGui.TableNextColumn(); + if (ImUtf8.SmallButton("Change Piece"u8)) + { + var data = model.GetArmor(EquipSlot.FaceWear); + _updateSlotService.UpdateSlot(model, EquipSlot.FaceWear, data with { Variant = (Variant)((data.Variant.Id + 1) % 12) }); + } + } } private void DrawCustomize(Actor actor, Model model) diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs b/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs index ecac046..e9fbcf4 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs @@ -1,5 +1,5 @@ using Dalamud.Interface; -using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.ImGuiNotification; using Glamourer.Designs; using Glamourer.Services; using ImGuiNET; diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs b/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs index 2608dd3..11147c8 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs @@ -1,5 +1,5 @@ using Dalamud.Interface; -using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.ImGuiNotification; using Dalamud.Plugin.Services; using Glamourer.Designs; using Glamourer.Events; diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index bf9ba69..50fd936 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -1,6 +1,6 @@ using Dalamud.Interface; using Dalamud.Interface.ImGuiFileDialog; -using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.ImGuiNotification; using FFXIVClientStructs.FFXIV.Client.System.Framework; using Glamourer.Automation; using Glamourer.Designs; @@ -106,7 +106,7 @@ public class DesignPanel var data = EquipDrawData.FromDesign(_manager, _selector.Selected!, slot); _equipmentDrawer.DrawEquip(data); if (usedAllStain) - _manager.ChangeStain(_selector.Selected, slot, newAllStain); + _manager.ChangeStains(_selector.Selected, slot, newAllStain); } var mainhand = EquipDrawData.FromDesign(_manager, _selector.Selected!, EquipSlot.MainHand); @@ -453,7 +453,7 @@ public class DesignPanel } private static unsafe string GetUserPath() - => Framework.Instance()->UserPath; + => Framework.Instance()->UserPathString; private sealed class LockButton(DesignPanel panel) : Button diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignTab.cs b/Glamourer/Gui/Tabs/DesignTab/DesignTab.cs index 7fca8c2..9832451 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignTab.cs @@ -1,4 +1,4 @@ -using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.ImGuiNotification; using Dalamud.Interface.Utility; using Glamourer.Designs; using Glamourer.Interop; diff --git a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs index 1915241..9db8c19 100644 --- a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs @@ -1,5 +1,5 @@ using Dalamud.Interface; -using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.ImGuiNotification; using Dalamud.Interface.Utility; using Dalamud.Utility; using Glamourer.Designs; diff --git a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs index c08d5c9..974912e 100644 --- a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs +++ b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs @@ -1,5 +1,5 @@ using Dalamud.Interface; -using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.ImGuiNotification; using FFXIVClientStructs.FFXIV.Client.Game.Object; using Glamourer.Designs; using Glamourer.Gui.Customization; diff --git a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs index 834b9fc..f38d81d 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs @@ -10,7 +10,6 @@ using Glamourer.Interop.PalettePlus; using ImGuiNET; using OtterGui; using OtterGui.Raii; -using OtterGui.Text; using OtterGui.Widgets; namespace Glamourer.Gui.Tabs.SettingsTab; @@ -19,7 +18,7 @@ public class SettingsTab( Configuration config, DesignFileSystemSelector selector, ContextMenuService contextMenuService, - UiBuilder uiBuilder, + IUiBuilder uiBuilder, GlamourerChangelog changelog, IKeyState keys, DesignColorUi designColorUi, diff --git a/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs b/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs index aa67fb4..9749ce6 100644 --- a/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs +++ b/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs @@ -116,20 +116,20 @@ public class UnlockOverview var unlocked = _customizeUnlocks.IsUnlocked(customize, out var time); var icon = _customizations.Manager.GetIcon(customize.IconId); - - ImGui.Image(icon.ImGuiHandle, iconSize, Vector2.Zero, Vector2.One, + var hasIcon = icon.TryGetWrap(out var wrap, out _); + ImGui.Image(wrap?.ImGuiHandle ?? icon.GetWrapOrEmpty().ImGuiHandle, iconSize, Vector2.Zero, Vector2.One, unlocked || _codes.Enabled(CodeService.CodeFlag.Shirts) ? Vector4.One : UnavailableTint); if (_favorites.Contains(_selected3, _selected2, customize.Index, customize.Value)) ImGui.GetWindowDrawList().AddRect(ImGui.GetItemRectMin(), ImGui.GetItemRectMax(), ColorId.FavoriteStarOn.Value(), 12 * ImGuiHelpers.GlobalScale, ImDrawFlags.RoundCornersAll, 6 * ImGuiHelpers.GlobalScale); - if (ImGui.IsItemHovered()) + if (hasIcon && ImGui.IsItemHovered()) { using var tt = ImRaii.Tooltip(); - var size = new Vector2(icon.Width, icon.Height); + var size = new Vector2(wrap!.Width, wrap.Height); if (size.X >= iconSize.X && size.Y >= iconSize.Y) - ImGui.Image(icon.ImGuiHandle, size); + ImGui.Image(wrap.ImGuiHandle, size); ImGui.TextUnformatted(unlockData.Name); ImGui.TextUnformatted($"{customize.Index.ToDefaultName()} {customize.Value.Value}"); ImGui.TextUnformatted(unlocked ? $"Unlocked on {time:g}" : "Not unlocked."); @@ -191,7 +191,8 @@ public class UnlockOverview var (icon, size) = (iconHandle.ImGuiHandle, new Vector2(iconHandle.Width, iconHandle.Height)); - ImGui.Image(icon, iconSize, Vector2.Zero, Vector2.One, unlocked || _codes.Enabled(CodeService.CodeFlag.Shirts) ? Vector4.One : UnavailableTint); + ImGui.Image(icon, iconSize, Vector2.Zero, Vector2.One, + unlocked || _codes.Enabled(CodeService.CodeFlag.Shirts) ? Vector4.One : UnavailableTint); if (_favorites.Contains(item)) ImGui.GetWindowDrawList().AddRect(ImGui.GetItemRectMin(), ImGui.GetItemRectMax(), ColorId.FavoriteStarOn.Value(), 2 * ImGuiHelpers.GlobalScale, ImDrawFlags.RoundCornersAll, 4 * ImGuiHelpers.GlobalScale); @@ -233,8 +234,8 @@ public class UnlockOverview ImGui.TextUnformatted($"For all {_jobs.AllJobGroups[item.JobRestrictions.Id].Name} of at least Level {item.Level}"); } - if (item.Flags.HasFlag(ItemFlags.IsDyable)) - ImGui.TextUnformatted("Dyable"); + if (item.Flags.HasFlag(ItemFlags.IsDyable1)) + ImGui.TextUnformatted(item.Flags.HasFlag(ItemFlags.IsDyable2) ? "Dyable (2 Slots)" : "Dyable"); if (item.Flags.HasFlag(ItemFlags.IsTradable)) ImGui.TextUnformatted("Tradable"); if (item.Flags.HasFlag(ItemFlags.IsCrestWorthy)) diff --git a/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs b/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs index d4fd4b0..600546f 100644 --- a/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs +++ b/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs @@ -1,4 +1,5 @@ using Dalamud.Game.Text.SeStringHandling; +using Dalamud.Interface; using Dalamud.Interface.Utility; using Glamourer.Events; using Glamourer.Interop; @@ -249,7 +250,7 @@ public class UnlockTable : Table, IDisposable => 70 * ImGuiHelpers.GlobalScale; public override int ToValue(EquipItem item) - => (int) item.Id.Id; + => (int)item.Id.Id; public ItemIdColumn() : base(ComparisonMethod.Equal) @@ -378,13 +379,68 @@ public class UnlockTable : Table, IDisposable } } - private sealed class DyableColumn : YesNoColumn - { - public DyableColumn() - => Tooltip = "Whether the item is dyable."; - protected override bool GetValue(EquipItem item) - => item.Flags.HasFlag(ItemFlags.IsDyable); + private sealed class DyableColumn : ColumnFlags + { + [Flags] + public enum Dyable : byte + { + No = 1, + Yes = 2, + Two = 4, + } + + private Dyable _filterValue; + + public DyableColumn() + { + AllFlags = Dyable.No | Dyable.Yes | Dyable.Two; + Flags &= ~ImGuiTableColumnFlags.NoResize; + _filterValue = AllFlags; + } + + public override Dyable FilterValue + => _filterValue; + + protected override void SetValue(Dyable value, bool enable) + => _filterValue = enable ? _filterValue | value : _filterValue & ~value; + + public override float Width + => ImGui.GetFrameHeight() * 2; + + public override bool FilterFunc(EquipItem item) + => GetValue(item) switch + { + 0 => _filterValue.HasFlag(Dyable.No), + ItemFlags.IsDyable2 => _filterValue.HasFlag(Dyable.Yes), + ItemFlags.IsDyable1 => _filterValue.HasFlag(Dyable.Yes), + _ => _filterValue.HasFlag(Dyable.Two), + }; + + public override int Compare(EquipItem lhs, EquipItem rhs) + => GetValue(lhs).CompareTo(GetValue(rhs)); + + public override void DrawColumn(EquipItem item, int idx) + { + using (ImRaii.PushFont(UiBuilder.IconFont)) + { + ImGuiUtil.Center(Icon(item)); + } + + ImGuiUtil.HoverTooltip("Whether the item is dyable, and how many slots it has."); + } + + private static string Icon(EquipItem item) + => GetValue(item) switch + { + 0 => FontAwesomeIcon.Times.ToIconString(), + ItemFlags.IsDyable2 => FontAwesomeIcon.Check.ToIconString(), + ItemFlags.IsDyable1 => FontAwesomeIcon.Check.ToIconString(), + _ => FontAwesomeIcon.DiceTwo.ToIconString(), + }; + + private static ItemFlags GetValue(EquipItem item) + => item.Flags & (ItemFlags.IsDyable1 | ItemFlags.IsDyable2); } private sealed class TradableColumn : YesNoColumn diff --git a/Glamourer/Interop/ChangeCustomizeService.cs b/Glamourer/Interop/ChangeCustomizeService.cs index cfff90f..495d69c 100644 --- a/Glamourer/Interop/ChangeCustomizeService.cs +++ b/Glamourer/Interop/ChangeCustomizeService.cs @@ -17,7 +17,7 @@ public unsafe class ChangeCustomizeService : EventWrapperRef2 _original; + private readonly delegate* unmanaged _original; private readonly Post _postEvent = new(); diff --git a/Glamourer/Interop/CharaFile/CharaFile.cs b/Glamourer/Interop/CharaFile/CharaFile.cs index 0613fb3..4bf08cd 100644 --- a/Glamourer/Interop/CharaFile/CharaFile.cs +++ b/Glamourer/Interop/CharaFile/CharaFile.cs @@ -60,7 +60,7 @@ public sealed class CharaFile return; data.SetItem(slot, item); - data.SetStain(slot, dye); + data.SetStain(slot, new StainIds(dye)); flags |= slot.ToFlag(); flags |= slot.ToStainFlag(); } @@ -79,7 +79,7 @@ public sealed class CharaFile return; data.SetItem(slot, item); - data.SetStain(slot, dye); + data.SetStain(slot, new StainIds(dye)); flags |= slot.ToFlag(); flags |= slot.ToStainFlag(); } diff --git a/Glamourer/Interop/CharaFile/CmaFile.cs b/Glamourer/Interop/CharaFile/CmaFile.cs index dab91ac..da3fb43 100644 --- a/Glamourer/Interop/CharaFile/CmaFile.cs +++ b/Glamourer/Interop/CharaFile/CmaFile.cs @@ -61,7 +61,7 @@ public sealed class CmaFile var armor = ((CharacterArmor*)ptr)[idx]; var item = items.Identify(slot, armor.Set, armor.Variant); data.SetItem(slot, item); - data.SetStain(slot, armor.Stain); + data.SetStain(slot, armor.Stains); } data.Customize.Read(ptr); @@ -74,7 +74,7 @@ public sealed class CmaFile if (mainhand == null) { data.SetItem(EquipSlot.MainHand, items.DefaultSword); - data.SetStain(EquipSlot.MainHand, 0); + data.SetStain(EquipSlot.MainHand, StainIds.None); return; } @@ -85,7 +85,7 @@ public sealed class CmaFile var item = items.Identify(EquipSlot.MainHand, set, type, variant); data.SetItem(EquipSlot.MainHand, item.Valid ? item : items.DefaultSword); - data.SetStain(EquipSlot.MainHand, stain); + data.SetStain(EquipSlot.MainHand, new StainIds(stain)); } private static void ParseOffHand(ItemManager items, JObject jObj, ref DesignData data) @@ -95,7 +95,7 @@ public sealed class CmaFile if (offhand == null) { data.SetItem(EquipSlot.MainHand, defaultOffhand); - data.SetStain(EquipSlot.MainHand, defaultOffhand.PrimaryId.Id == 0 ? 0 : data.Stain(EquipSlot.MainHand)); + data.SetStain(EquipSlot.MainHand, defaultOffhand.PrimaryId.Id == 0 ? StainIds.None : data.Stain(EquipSlot.MainHand)); return; } @@ -106,6 +106,6 @@ public sealed class CmaFile var item = items.Identify(EquipSlot.OffHand, set, type, variant, data.MainhandType); data.SetItem(EquipSlot.OffHand, item.Valid ? item : defaultOffhand); - data.SetStain(EquipSlot.OffHand, defaultOffhand.PrimaryId.Id == 0 ? 0 : (StainId)stain); + data.SetStain(EquipSlot.OffHand, defaultOffhand.PrimaryId.Id == 0 ? StainIds.None : new StainIds(stain)); } } diff --git a/Glamourer/Interop/ContextMenuService.cs b/Glamourer/Interop/ContextMenuService.cs index 8cd5391..19a805f 100644 --- a/Glamourer/Interop/ContextMenuService.cs +++ b/Glamourer/Interop/ContextMenuService.cs @@ -20,7 +20,7 @@ public class ContextMenuService : IDisposable private readonly ObjectManager _objects; private readonly IGameGui _gameGui; private EquipItem _lastItem; - private StainId _lastStain; + private readonly StainId[] _lastStains = new StainId[StainId.NumStains]; private readonly MenuItem _inventoryItem; @@ -47,14 +47,15 @@ public class ContextMenuService : IDisposable }; } - private unsafe void OnMenuOpened(MenuOpenedArgs args) + private unsafe void OnMenuOpened(IMenuOpenedArgs args) { if (args.MenuType is ContextMenuType.Inventory) { var arg = (MenuTargetInventory)args.Target; if (arg.TargetItem.HasValue && HandleItem(arg.TargetItem.Value.ItemId)) { - _lastStain = arg.TargetItem.Value.Stain; + for (var i = 0; i < arg.TargetItem.Value.Stains.Length; ++i) + _lastStains[i] = (StainId)arg.TargetItem.Value.Stains[i]; args.AddMenuItem(_inventoryItem); } } @@ -77,7 +78,8 @@ public class ContextMenuService : IDisposable if (HandleItem(*(ItemId*)(agent + ChatLogContextItemId))) { - _lastStain = 0; + for (var i = 0; i < _lastStains.Length; ++i) + _lastStains[i] = 0; args.AddMenuItem(_inventoryItem); } @@ -96,7 +98,7 @@ public class ContextMenuService : IDisposable public void Dispose() => Disable(); - private void OnClick(MenuItemClickedArgs _) + private void OnClick(IMenuItemClickedArgs _) { var (id, playerData) = _objects.PlayerData; if (!playerData.Valid) @@ -106,15 +108,15 @@ public class ContextMenuService : IDisposable return; var slot = _lastItem.Type.ToSlot(); - _state.ChangeEquip(state, slot, _lastItem, _lastStain, ApplySettings.Manual); + _state.ChangeEquip(state, slot, _lastItem, _lastStains[0], ApplySettings.Manual); if (!_lastItem.Type.ValidOffhand().IsOffhandType()) return; if (_lastItem.PrimaryId.Id is > 1600 and < 1651 && _items.ItemData.TryGetValue(_lastItem.ItemId, EquipSlot.Hands, out var gauntlets)) - _state.ChangeEquip(state, EquipSlot.Hands, gauntlets, _lastStain, ApplySettings.Manual); + _state.ChangeEquip(state, EquipSlot.Hands, gauntlets, _lastStains[0], ApplySettings.Manual); if (_items.ItemData.TryGetValue(_lastItem.ItemId, EquipSlot.OffHand, out var offhand)) - _state.ChangeEquip(state, EquipSlot.OffHand, offhand, _lastStain, ApplySettings.Manual); + _state.ChangeEquip(state, EquipSlot.OffHand, offhand, _lastStains[0], ApplySettings.Manual); } private bool HandleItem(ItemId id) diff --git a/Glamourer/Interop/CrestService.cs b/Glamourer/Interop/CrestService.cs index 75e2a81..2b6f1ac 100644 --- a/Glamourer/Interop/CrestService.cs +++ b/Glamourer/Interop/CrestService.cs @@ -4,6 +4,7 @@ using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.Game.Character; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using OtterGui.Classes; +using Penumbra.GameData; using Penumbra.GameData.Enums; using Penumbra.GameData.Interop; @@ -30,9 +31,9 @@ public sealed unsafe class CrestService : EventWrapperRef3(_humanVTable[96], HumanSetFreeCompanyCrestVisibleOnSlotDetour); + interop.HookFromAddress(_humanVTable[109], HumanSetFreeCompanyCrestVisibleOnSlotDetour); _weaponSetFreeCompanyCrestVisibleOnSlot = - interop.HookFromAddress(_weaponVTable[96], WeaponSetFreeCompanyCrestVisibleOnSlotDetour); + interop.HookFromAddress(_weaponVTable[109], WeaponSetFreeCompanyCrestVisibleOnSlotDetour); _humanSetFreeCompanyCrestVisibleOnSlot.Enable(); _weaponSetFreeCompanyCrestVisibleOnSlot.Enable(); _crestChangeHook.Enable(); @@ -63,7 +64,7 @@ public sealed unsafe class CrestService : EventWrapperRef3 _crestChangeHook = null!; private void CrestChangeDetour(Character* character, byte crestFlags) @@ -96,8 +97,7 @@ public sealed unsafe class CrestService : EventWrapperRef3)((nint*)model.AsCharacterBase->VTable)[95]; - return getter(model.AsHuman, index) != 0; + return model.AsHuman->IsFreeCompanyCrestVisibleOnSlot(index) != 0; } case CrestType.Offhand: { @@ -105,8 +105,7 @@ public sealed unsafe class CrestService : EventWrapperRef3)((nint*)model.AsCharacterBase->VTable)[95]; - return getter(model.AsWeapon, index) != 0; + return model.AsWeapon->IsFreeCompanyCrestVisibleOnSlot(index) != 0; } } @@ -117,10 +116,10 @@ public sealed unsafe class CrestService : EventWrapperRef3 _humanSetFreeCompanyCrestVisibleOnSlot; diff --git a/Glamourer/Interop/ImportService.cs b/Glamourer/Interop/ImportService.cs index 9587feb..7dc3313 100644 --- a/Glamourer/Interop/ImportService.cs +++ b/Glamourer/Interop/ImportService.cs @@ -1,5 +1,5 @@ using Dalamud.Interface.DragDrop; -using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.ImGuiNotification; using Glamourer.Designs; using Glamourer.Interop.CharaFile; using Glamourer.Services; diff --git a/Glamourer/Interop/InventoryService.cs b/Glamourer/Interop/InventoryService.cs index 9ad8737..6d8e58b 100644 --- a/Glamourer/Interop/InventoryService.cs +++ b/Glamourer/Interop/InventoryService.cs @@ -12,9 +12,9 @@ namespace Glamourer.Interop; public sealed unsafe class InventoryService : IDisposable, IRequiredService { - private readonly MovedEquipment _movedItemsEvent; - private readonly EquippedGearset _gearsetEvent; - private readonly List<(EquipSlot, uint, StainId)> _itemList = new(12); + private readonly MovedEquipment _movedItemsEvent; + private readonly EquippedGearset _gearsetEvent; + private readonly List<(EquipSlot, uint, StainIds)> _itemList = new(12); public InventoryService(MovedEquipment movedItemsEvent, IGameInteropProvider interop, EquippedGearset gearsetEvent) { @@ -60,56 +60,56 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService if (glamourPlateId != 0) { - void Add(EquipSlot slot, uint glamourId, StainId glamourStain, ref RaptureGearsetModule.GearsetItem item) + void Add(EquipSlot slot, uint glamourId, StainIds glamourStain, ref RaptureGearsetModule.GearsetItem item) { - if (item.ItemID == 0) - _itemList.Add((slot, 0, 0)); + if (item.ItemId == 0) + _itemList.Add((slot, 0, StainIds.None)); else if (glamourId != 0) _itemList.Add((slot, glamourId, glamourStain)); else if (item.GlamourId != 0) - _itemList.Add((slot, item.GlamourId, item.Stain)); + _itemList.Add((slot, item.GlamourId, StainIds.FromGearsetItem(item))); else - _itemList.Add((slot, FixId(item.ItemID), item.Stain)); + _itemList.Add((slot, FixId(item.ItemId), StainIds.FromGearsetItem(item))); } - var plate = MirageManager.Instance()->GlamourPlatesSpan[glamourPlateId - 1]; - Add(EquipSlot.MainHand, plate.ItemIds[0], plate.StainIds[0], ref entry->ItemsSpan[0]); - Add(EquipSlot.OffHand, plate.ItemIds[1], plate.StainIds[1], ref entry->ItemsSpan[1]); - Add(EquipSlot.Head, plate.ItemIds[2], plate.StainIds[2], ref entry->ItemsSpan[2]); - Add(EquipSlot.Body, plate.ItemIds[3], plate.StainIds[3], ref entry->ItemsSpan[3]); - Add(EquipSlot.Hands, plate.ItemIds[4], plate.StainIds[4], ref entry->ItemsSpan[5]); - Add(EquipSlot.Legs, plate.ItemIds[5], plate.StainIds[5], ref entry->ItemsSpan[6]); - Add(EquipSlot.Feet, plate.ItemIds[6], plate.StainIds[6], ref entry->ItemsSpan[7]); - Add(EquipSlot.Ears, plate.ItemIds[7], plate.StainIds[7], ref entry->ItemsSpan[8]); - Add(EquipSlot.Neck, plate.ItemIds[8], plate.StainIds[8], ref entry->ItemsSpan[9]); - Add(EquipSlot.Wrists, plate.ItemIds[9], plate.StainIds[9], ref entry->ItemsSpan[10]); - Add(EquipSlot.RFinger, plate.ItemIds[10], plate.StainIds[10], ref entry->ItemsSpan[11]); - Add(EquipSlot.LFinger, plate.ItemIds[11], plate.StainIds[11], ref entry->ItemsSpan[12]); + var plate = MirageManager.Instance()->GlamourPlates[glamourPlateId - 1]; + Add(EquipSlot.MainHand, plate.ItemIds[0], StainIds.FromGlamourPlate(plate, 0), ref entry->Items[0]); + Add(EquipSlot.OffHand, plate.ItemIds[1], StainIds.FromGlamourPlate(plate, 1), ref entry->Items[1]); + Add(EquipSlot.Head, plate.ItemIds[2], StainIds.FromGlamourPlate(plate, 2), ref entry->Items[2]); + Add(EquipSlot.Body, plate.ItemIds[3], StainIds.FromGlamourPlate(plate, 3), ref entry->Items[3]); + Add(EquipSlot.Hands, plate.ItemIds[4], StainIds.FromGlamourPlate(plate, 4), ref entry->Items[5]); + Add(EquipSlot.Legs, plate.ItemIds[5], StainIds.FromGlamourPlate(plate, 5), ref entry->Items[6]); + Add(EquipSlot.Feet, plate.ItemIds[6], StainIds.FromGlamourPlate(plate, 6), ref entry->Items[7]); + Add(EquipSlot.Ears, plate.ItemIds[7], StainIds.FromGlamourPlate(plate, 7), ref entry->Items[8]); + Add(EquipSlot.Neck, plate.ItemIds[8], StainIds.FromGlamourPlate(plate, 8), ref entry->Items[9]); + Add(EquipSlot.Wrists, plate.ItemIds[9], StainIds.FromGlamourPlate(plate, 9), ref entry->Items[10]); + Add(EquipSlot.RFinger, plate.ItemIds[10], StainIds.FromGlamourPlate(plate, 10), ref entry->Items[11]); + Add(EquipSlot.LFinger, plate.ItemIds[11], StainIds.FromGlamourPlate(plate, 11), ref entry->Items[12]); } else { void Add(EquipSlot slot, ref RaptureGearsetModule.GearsetItem item) { - if (item.ItemID == 0) - _itemList.Add((slot, 0, 0)); + if (item.ItemId == 0) + _itemList.Add((slot, 0, StainIds.None)); else if (item.GlamourId != 0) - _itemList.Add((slot, item.GlamourId, item.Stain)); + _itemList.Add((slot, item.GlamourId, StainIds.FromGearsetItem(item))); else - _itemList.Add((slot, FixId(item.ItemID), item.Stain)); + _itemList.Add((slot, FixId(item.ItemId), StainIds.FromGearsetItem(item))); } - Add(EquipSlot.MainHand, ref entry->ItemsSpan[0]); - Add(EquipSlot.OffHand, ref entry->ItemsSpan[1]); - Add(EquipSlot.Head, ref entry->ItemsSpan[2]); - Add(EquipSlot.Body, ref entry->ItemsSpan[3]); - Add(EquipSlot.Hands, ref entry->ItemsSpan[5]); - Add(EquipSlot.Legs, ref entry->ItemsSpan[6]); - Add(EquipSlot.Feet, ref entry->ItemsSpan[7]); - Add(EquipSlot.Ears, ref entry->ItemsSpan[8]); - Add(EquipSlot.Neck, ref entry->ItemsSpan[9]); - Add(EquipSlot.Wrists, ref entry->ItemsSpan[10]); - Add(EquipSlot.RFinger, ref entry->ItemsSpan[11]); - Add(EquipSlot.LFinger, ref entry->ItemsSpan[12]); + Add(EquipSlot.MainHand, ref entry->Items[0]); + Add(EquipSlot.OffHand, ref entry->Items[1]); + Add(EquipSlot.Head, ref entry->Items[2]); + Add(EquipSlot.Body, ref entry->Items[3]); + Add(EquipSlot.Hands, ref entry->Items[5]); + Add(EquipSlot.Legs, ref entry->Items[6]); + Add(EquipSlot.Feet, ref entry->Items[7]); + Add(EquipSlot.Ears, ref entry->Items[8]); + Add(EquipSlot.Neck, ref entry->Items[9]); + Add(EquipSlot.Wrists, ref entry->Items[10]); + Add(EquipSlot.RFinger, ref entry->Items[11]); + Add(EquipSlot.LFinger, ref entry->Items[12]); } _movedItemsEvent.Invoke(_itemList.ToArray()); @@ -155,7 +155,7 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService return ret; } - private static bool InvokeSource(InventoryType sourceContainer, uint sourceSlot, out (EquipSlot, uint, StainId) tuple) + private static bool InvokeSource(InventoryType sourceContainer, uint sourceSlot, out (EquipSlot, uint, StainIds) tuple) { tuple = default; if (sourceContainer is not InventoryType.EquippedItems) @@ -165,12 +165,12 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService if (slot is EquipSlot.Unknown) return false; - tuple = (slot, 0u, 0); + tuple = (slot, 0u, StainIds.None); return true; } private static bool InvokeTarget(InventoryManager* manager, InventoryType targetContainer, uint targetSlot, - out (EquipSlot, uint, StainId) tuple) + out (EquipSlot, uint, StainIds) tuple) { tuple = default; if (targetContainer is not InventoryType.EquippedItems) @@ -189,7 +189,7 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService if (item == null) return false; - tuple = (slot, item->GlamourID != 0 ? item->GlamourID : item->ItemID, item->Stain); + tuple = (slot, item->GlamourId != 0 ? item->GlamourId : item->ItemId, new StainIds(item->Stains)); return true; } diff --git a/Glamourer/Interop/Material/MaterialManager.cs b/Glamourer/Interop/Material/MaterialManager.cs index b8941e0..3e984c8 100644 --- a/Glamourer/Interop/Material/MaterialManager.cs +++ b/Glamourer/Interop/Material/MaterialManager.cs @@ -169,13 +169,13 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable if (!actor.AsObject->IsCharacter()) return false; - if (actor.AsCharacter->DrawData.WeaponDataSpan[0].DrawObject == characterBase) + if (actor.AsCharacter->DrawData.WeaponData[0].DrawObject == characterBase) { type = MaterialValueIndex.DrawObjectType.Mainhand; return true; } - if (actor.AsCharacter->DrawData.WeaponDataSpan[1].DrawObject == characterBase) + if (actor.AsCharacter->DrawData.WeaponData[1].DrawObject == characterBase) { type = MaterialValueIndex.DrawObjectType.Offhand; return true; @@ -199,10 +199,11 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable /// private static CharacterWeapon GetTempSlot(Weapon* weapon) { + // TODO: Fix offset var changedData = *(void**)((byte*)weapon + 0x918); if (changedData == null) - return new CharacterWeapon(weapon->ModelSetId, weapon->SecondaryId, (Variant)weapon->Variant, (StainId)weapon->ModelUnknown); + return new CharacterWeapon(weapon->ModelSetId, weapon->SecondaryId, (Variant)weapon->Variant, StainIds.FromWeapon(*weapon)); - return new CharacterWeapon(weapon->ModelSetId, *(SecondaryId*)changedData, ((Variant*)changedData)[2], ((StainId*)changedData)[3]); + return new CharacterWeapon(weapon->ModelSetId, *(SecondaryId*)changedData, ((Variant*)changedData)[2], new StainIds(((StainId*)changedData)[3], ((StainId*)changedData)[4])); } } diff --git a/Glamourer/Interop/Material/MaterialValueIndex.cs b/Glamourer/Interop/Material/MaterialValueIndex.cs index 9bfcc4c..2096bc7 100644 --- a/Glamourer/Interop/Material/MaterialValueIndex.cs +++ b/Glamourer/Interop/Material/MaterialValueIndex.cs @@ -62,8 +62,8 @@ public readonly record struct MaterialValueIndex( model = DrawObject switch { DrawObjectType.Human => actor.Model, - DrawObjectType.Mainhand => actor.IsCharacter ? actor.AsCharacter->DrawData.WeaponDataSpan[0].DrawObject : Model.Null, - DrawObjectType.Offhand => actor.IsCharacter ? actor.AsCharacter->DrawData.WeaponDataSpan[1].DrawObject : Model.Null, + DrawObjectType.Mainhand => actor.IsCharacter ? actor.AsCharacter->DrawData.WeaponData[0].DrawObject : Model.Null, + DrawObjectType.Offhand => actor.IsCharacter ? actor.AsCharacter->DrawData.WeaponData[1].DrawObject : Model.Null, _ => Model.Null, }; return model.IsCharacterBase; diff --git a/Glamourer/Interop/Material/MaterialValueManager.cs b/Glamourer/Interop/Material/MaterialValueManager.cs index 483e6af..ae08c71 100644 --- a/Glamourer/Interop/Material/MaterialValueManager.cs +++ b/Glamourer/Interop/Material/MaterialValueManager.cs @@ -203,7 +203,7 @@ public struct MaterialValueState( => DrawData.Skeleton == rhsData.Skeleton && DrawData.Weapon == rhsData.Weapon && DrawData.Variant == rhsData.Variant - && DrawData.Stain == rhsData.Stain + && DrawData.Stains == rhsData.Stains && Game.NearEqual(rhsRow); public readonly MaterialValueDesign Convert() diff --git a/Glamourer/Interop/Material/PrepareColorSet.cs b/Glamourer/Interop/Material/PrepareColorSet.cs index 1661037..3866d74 100644 --- a/Glamourer/Interop/Material/PrepareColorSet.cs +++ b/Glamourer/Interop/Material/PrepareColorSet.cs @@ -4,6 +4,7 @@ using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; using OtterGui.Classes; using OtterGui.Services; +using Penumbra.GameData; using Penumbra.GameData.Enums; using Penumbra.GameData.Files.MaterialStructs; using Penumbra.GameData.Interop; @@ -22,7 +23,7 @@ public sealed unsafe class PrepareColorSet public PrepareColorSet(HookManager hooks) : base("Prepare Color Set ") - => _task = hooks.CreateHook(Name, "40 55 56 41 56 48 83 EC ?? 80 BA", Detour, true); + => _task = hooks.CreateHook(Name, Sigs.PrepareColorSet, Detour, true); private readonly Task> _task; @@ -54,7 +55,7 @@ public sealed unsafe class PrepareColorSet return _task.Result.Original(characterBase, material, stainId); } - public static bool TryGetColorTable(CharacterBase* characterBase, MaterialResourceHandle* material, StainId stainId, + public static bool TryGetColorTable(CharacterBase* characterBase, MaterialResourceHandle* material, StainIds stainIds, out LegacyColorTable table) { if (material->ColorTable == null) @@ -64,8 +65,9 @@ public sealed unsafe class PrepareColorSet } var newTable = *(LegacyColorTable*)material->ColorTable; - if (stainId.Id != 0) - characterBase->ReadStainingTemplate(material, stainId.Id, (Half*)(&newTable)); + // TODO + //if (stainIds.Stain1.Id != 0 || stainIds.Stain2.Id != 0) + // characterBase->ReadStainingTemplate(material, stainId.Id, (Half*)(&newTable)); table = newTable; return true; } @@ -84,21 +86,21 @@ public sealed unsafe class PrepareColorSet return false; } - return TryGetColorTable(model.AsCharacterBase, handle, GetStain(), out table); + return TryGetColorTable(model.AsCharacterBase, handle, GetStains(), out table); - StainId GetStain() + StainIds GetStains() { switch (index.DrawObject) { case MaterialValueIndex.DrawObjectType.Human: - return index.SlotIndex < 10 ? actor.Model.GetArmor(((uint)index.SlotIndex).ToEquipSlot()).Stain : 0; + return index.SlotIndex < 10 ? actor.Model.GetArmor(((uint)index.SlotIndex).ToEquipSlot()).Stains : StainIds.None; case MaterialValueIndex.DrawObjectType.Mainhand: - var mainhand = (Model)actor.AsCharacter->DrawData.WeaponDataSpan[1].DrawObject; - return mainhand.IsWeapon ? (StainId)mainhand.AsWeapon->ModelUnknown : 0; + var mainhand = (Model)actor.AsCharacter->DrawData.WeaponData[1].DrawObject; + return mainhand.IsWeapon ? StainIds.FromWeapon(*mainhand.AsWeapon) : StainIds.None; case MaterialValueIndex.DrawObjectType.Offhand: - var offhand = (Model)actor.AsCharacter->DrawData.WeaponDataSpan[1].DrawObject; - return offhand.IsWeapon ? (StainId)offhand.AsWeapon->ModelUnknown : 0; - default: return 0; + var offhand = (Model)actor.AsCharacter->DrawData.WeaponData[1].DrawObject; + return offhand.IsWeapon ? StainIds.FromWeapon(*offhand.AsWeapon) : StainIds.None; + default: return StainIds.None; } } } diff --git a/Glamourer/Interop/MetaService.cs b/Glamourer/Interop/MetaService.cs index 1bc7a32..062986b 100644 --- a/Glamourer/Interop/MetaService.cs +++ b/Glamourer/Interop/MetaService.cs @@ -49,11 +49,11 @@ public unsafe class MetaService : IDisposable return; // The function seems to not do anything if the head is 0, but also breaks for carbuncles turned human, sometimes? - var old = actor.AsCharacter->DrawData.Head.Id; + var old = actor.AsCharacter->DrawData.Equipment(DrawDataContainer.EquipmentSlot.Head).Id; if (old == 0 && actor.AsCharacter->CharacterData.ModelCharaId == 0) - actor.AsCharacter->DrawData.Head.Id = 1; + actor.AsCharacter->DrawData.Equipment(DrawDataContainer.EquipmentSlot.Head).Id = 1; _hideHatGearHook.Original(&actor.AsCharacter->DrawData, 0, (byte)(value ? 0 : 1)); - actor.AsCharacter->DrawData.Head.Id = old; + actor.AsCharacter->DrawData.Equipment(DrawDataContainer.EquipmentSlot.Head).Id = old; } public void SetWeaponState(Actor actor, bool value) @@ -72,7 +72,7 @@ public unsafe class MetaService : IDisposable return; } - Actor actor = drawData->Parent; + Actor actor = drawData->OwnerObject; var v = value == 0; _headGearEvent.Invoke(actor, ref v); value = (byte)(v ? 0 : 1); @@ -82,7 +82,7 @@ public unsafe class MetaService : IDisposable private void HideWeaponsDetour(DrawDataContainer* drawData, bool value) { - Actor actor = drawData->Parent; + Actor actor = drawData->OwnerObject; value = !value; _weaponEvent.Invoke(actor, ref value); value = !value; @@ -92,7 +92,7 @@ public unsafe class MetaService : IDisposable private void ToggleVisorDetour(DrawDataContainer* drawData, bool value) { - Actor actor = drawData->Parent; + Actor actor = drawData->OwnerObject; _visorEvent.Invoke(actor.Model, true, ref value); Glamourer.Log.Verbose($"[MetaService] Toggle Visor triggered with 0x{(nint)drawData:X} {value} for {actor.Utf8Name}."); _toggleVisorHook.Original(drawData, value); diff --git a/Glamourer/Interop/ObjectManager.cs b/Glamourer/Interop/ObjectManager.cs index f59e95c..f8f5813 100644 --- a/Glamourer/Interop/ObjectManager.cs +++ b/Glamourer/Interop/ObjectManager.cs @@ -14,7 +14,7 @@ public class ObjectManager( IFramework framework, IClientState clientState, IObjectTable objects, - DalamudPluginInterface pi, + IDalamudPluginInterface pi, Logger log, ActorManager actors, ITargetManager targets) diff --git a/Glamourer/Interop/PalettePlus/PaletteImport.cs b/Glamourer/Interop/PalettePlus/PaletteImport.cs index 8513036..93c3fa2 100644 --- a/Glamourer/Interop/PalettePlus/PaletteImport.cs +++ b/Glamourer/Interop/PalettePlus/PaletteImport.cs @@ -6,7 +6,7 @@ using OtterGui.Services; namespace Glamourer.Interop.PalettePlus; -public class PaletteImport(DalamudPluginInterface pluginInterface, DesignManager designManager, DesignFileSystem designFileSystem) : IService +public class PaletteImport(IDalamudPluginInterface pluginInterface, DesignManager designManager, DesignFileSystem designFileSystem) : IService { private string ConfigFile => Path.Combine(Path.GetDirectoryName(pluginInterface.GetPluginConfigDirectory())!, "PalettePlus.json"); diff --git a/Glamourer/Interop/PalettePlus/PalettePlusChecker.cs b/Glamourer/Interop/PalettePlus/PalettePlusChecker.cs index 6a23e90..a5a5ed9 100644 --- a/Glamourer/Interop/PalettePlus/PalettePlusChecker.cs +++ b/Glamourer/Interop/PalettePlus/PalettePlusChecker.cs @@ -1,7 +1,7 @@ -using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.ImGuiNotification; using Dalamud.Plugin; -using OtterGui.Classes; using OtterGui.Services; +using Notification = OtterGui.Classes.Notification; namespace Glamourer.Interop.PalettePlus; @@ -9,9 +9,9 @@ public sealed class PalettePlusChecker : IRequiredService, IDisposable { private readonly Timer _paletteTimer; private readonly Configuration _config; - private readonly DalamudPluginInterface _pluginInterface; + private readonly IDalamudPluginInterface _pluginInterface; - public PalettePlusChecker(Configuration config, DalamudPluginInterface pluginInterface) + public PalettePlusChecker(Configuration config, IDalamudPluginInterface pluginInterface) { _config = config; _pluginInterface = pluginInterface; diff --git a/Glamourer/Interop/Penumbra/PenumbraService.cs b/Glamourer/Interop/Penumbra/PenumbraService.cs index 58422d7..85235e7 100644 --- a/Glamourer/Interop/Penumbra/PenumbraService.cs +++ b/Glamourer/Interop/Penumbra/PenumbraService.cs @@ -1,4 +1,4 @@ -using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.ImGuiNotification; using Dalamud.Plugin; using Glamourer.Events; using OtterGui.Classes; @@ -36,7 +36,7 @@ public unsafe class PenumbraService : IDisposable public const int RequiredPenumbraBreakingVersion = 5; public const int RequiredPenumbraFeatureVersion = 0; - private readonly DalamudPluginInterface _pluginInterface; + private readonly IDalamudPluginInterface _pluginInterface; private readonly EventSubscriber _tooltipSubscriber; private readonly EventSubscriber _clickSubscriber; private readonly EventSubscriber _creatingCharacterBase; @@ -68,7 +68,7 @@ public unsafe class PenumbraService : IDisposable public int CurrentMinor { get; private set; } public DateTime AttachTime { get; private set; } - public PenumbraService(DalamudPluginInterface pi, PenumbraReloaded penumbraReloaded) + public PenumbraService(IDalamudPluginInterface pi, PenumbraReloaded penumbraReloaded) { _pluginInterface = pi; _penumbraReloaded = penumbraReloaded; diff --git a/Glamourer/Interop/ScalingService.cs b/Glamourer/Interop/ScalingService.cs index 66036d9..f7b8f08 100644 --- a/Glamourer/Interop/ScalingService.cs +++ b/Glamourer/Interop/ScalingService.cs @@ -3,6 +3,7 @@ using Dalamud.Hooking; using Dalamud.Plugin.Services; using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.Game.Character; +using Penumbra.GameData; using Penumbra.GameData.Interop; using Character = FFXIVClientStructs.FFXIV.Client.Game.Character.Character; @@ -14,7 +15,7 @@ public unsafe class ScalingService : IDisposable { interop.InitializeFromAttributes(this); _setupMountHook = - interop.HookFromAddress((nint)Character.MountContainer.MemberFunctionPointers.SetupMount, SetupMountDetour); + interop.HookFromAddress((nint)MountContainer.MemberFunctionPointers.SetupMount, SetupMountDetour); _setupOrnamentHook = interop.HookFromAddress((nint)Ornament.MemberFunctionPointers.SetupOrnament, SetupOrnamentDetour); _calculateHeightHook = interop.HookFromAddress((nint)Character.MemberFunctionPointers.CalculateHeight, CalculateHeightDetour); @@ -33,7 +34,7 @@ public unsafe class ScalingService : IDisposable _calculateHeightHook.Dispose(); } - private delegate void SetupMount(Character.MountContainer* container, short mountId, uint unk1, uint unk2, uint unk3, byte unk4); + private delegate void SetupMount(MountContainer* container, short mountId, uint unk1, uint unk2, uint unk3, byte unk4); private delegate void SetupOrnament(Ornament* ornament, uint* unk1, float* unk2); private delegate void PlaceMinion(Companion* character); private delegate float CalculateHeight(Character* character); @@ -45,15 +46,15 @@ public unsafe class ScalingService : IDisposable private readonly Hook _calculateHeightHook; // TODO: Use client structs sig. - [Signature("48 89 5C 24 ?? 55 57 41 57 48 8D 6C 24", DetourName = nameof(PlaceMinionDetour))] + [Signature(Sigs.PlaceMinion, DetourName = nameof(PlaceMinionDetour))] private readonly Hook _placeMinionHook = null!; - private void SetupMountDetour(Character.MountContainer* container, short mountId, uint unk1, uint unk2, uint unk3, byte unk4) + private void SetupMountDetour(MountContainer* container, short mountId, uint unk1, uint unk2, uint unk3, byte unk4) { - var (race, clan, gender) = GetScaleRelevantCustomize(&container->OwnerObject->Character); - SetScaleCustomize(&container->OwnerObject->Character, container->OwnerObject->Character.GameObject.DrawObject); + var (race, clan, gender) = GetScaleRelevantCustomize(container->OwnerObject); + SetScaleCustomize(container->OwnerObject, container->OwnerObject->DrawObject); _setupMountHook.Original(container, mountId, unk1, unk2, unk3, unk4); - SetScaleCustomize(&container->OwnerObject->Character, race, clan, gender); + SetScaleCustomize(container->OwnerObject, race, clan, gender); } private void SetupOrnamentDetour(Ornament* ornament, uint* unk1, float* unk2) diff --git a/Glamourer/Interop/UpdateSlotService.cs b/Glamourer/Interop/UpdateSlotService.cs index f2f7423..65a168e 100644 --- a/Glamourer/Interop/UpdateSlotService.cs +++ b/Glamourer/Interop/UpdateSlotService.cs @@ -18,33 +18,44 @@ public unsafe class UpdateSlotService : IDisposable SlotUpdatingEvent = slotUpdating; interop.InitializeFromAttributes(this); _flagSlotForUpdateHook.Enable(); + _flagBonusSlotForUpdateHook.Enable(); } public void Dispose() - => _flagSlotForUpdateHook.Dispose(); + { + _flagSlotForUpdateHook.Dispose(); + _flagBonusSlotForUpdateHook.Dispose(); + } public void UpdateSlot(Model drawObject, EquipSlot slot, CharacterArmor data) { if (!drawObject.IsCharacterBase) return; - FlagSlotForUpdateInterop(drawObject, slot, data); + var bonusSlot = slot.ToBonusIndex(); + if (bonusSlot == uint.MaxValue) + FlagSlotForUpdateInterop(drawObject, slot, data); + else + _flagBonusSlotForUpdateHook.Original(drawObject.Address, bonusSlot, &data); } - public void UpdateArmor(Model drawObject, EquipSlot slot, CharacterArmor armor, StainId stain) - => UpdateSlot(drawObject, slot, armor.With(stain)); + public void UpdateArmor(Model drawObject, EquipSlot slot, CharacterArmor armor, StainIds stains) + => UpdateSlot(drawObject, slot, armor.With(stains)); public void UpdateArmor(Model drawObject, EquipSlot slot, CharacterArmor armor) - => UpdateArmor(drawObject, slot, armor, drawObject.GetArmor(slot).Stain); + => UpdateArmor(drawObject, slot, armor, drawObject.GetArmor(slot).Stains); - public void UpdateStain(Model drawObject, EquipSlot slot, StainId stain) - => UpdateArmor(drawObject, slot, drawObject.GetArmor(slot), stain); + public void UpdateStain(Model drawObject, EquipSlot slot, StainIds stains) + => UpdateArmor(drawObject, slot, drawObject.GetArmor(slot), stains); private delegate ulong FlagSlotForUpdateDelegateIntern(nint drawObject, uint slot, CharacterArmor* data); [Signature(Sigs.FlagSlotForUpdate, DetourName = nameof(FlagSlotForUpdateDetour))] private readonly Hook _flagSlotForUpdateHook = null!; + [Signature(Sigs.FlagBonusSlotForUpdate, DetourName = nameof(FlagBonusSlotForUpdateDetour))] + private readonly Hook _flagBonusSlotForUpdateHook = null!; + private ulong FlagSlotForUpdateDetour(nint drawObject, uint slotIdx, CharacterArmor* data) { var slot = slotIdx.ToEquipSlot(); @@ -54,6 +65,15 @@ public unsafe class UpdateSlotService : IDisposable return returnValue == ulong.MaxValue ? _flagSlotForUpdateHook.Original(drawObject, slotIdx, data) : returnValue; } + private ulong FlagBonusSlotForUpdateDetour(nint drawObject, uint slotIdx, CharacterArmor* data) + { + var slot = slotIdx.ToBonusSlot(); + var returnValue = ulong.MaxValue; + SlotUpdatingEvent.Invoke(drawObject, slot, ref *data, ref returnValue); + Glamourer.Log.Excessive($"[FlagBonusSlotForUpdate] Called with 0x{drawObject:X} for slot {slot} with {*data} ({returnValue:X})."); + return returnValue == ulong.MaxValue ? _flagBonusSlotForUpdateHook.Original(drawObject, slotIdx, data) : returnValue; + } + private ulong FlagSlotForUpdateInterop(Model drawObject, EquipSlot slot, CharacterArmor armor) => _flagSlotForUpdateHook.Original(drawObject.Address, slot.ToIndex(), &armor); } diff --git a/Glamourer/Interop/WeaponService.cs b/Glamourer/Interop/WeaponService.cs index d2aac1a..5ab2a40 100644 --- a/Glamourer/Interop/WeaponService.cs +++ b/Glamourer/Interop/WeaponService.cs @@ -70,7 +70,7 @@ public unsafe class WeaponService : IDisposable if (tmpWeapon.Value != weapon.Value) { if (tmpWeapon.Skeleton.Id == 0) - tmpWeapon.Stain = 0; + tmpWeapon.Stains = StainIds.None; _loadWeaponHook.Original(drawData, slot, tmpWeapon.Value, 1, unk2, 1, unk4); } @@ -107,12 +107,12 @@ public unsafe class WeaponService : IDisposable } } - public void LoadStain(Actor character, EquipSlot slot, StainId stain) + public void LoadStain(Actor character, EquipSlot slot, StainIds stains) { var mdl = character.Model; var (_, _, mh, oh) = mdl.GetWeapons(character); var value = slot == EquipSlot.OffHand ? oh : mh; - var weapon = value.With(value.Skeleton.Id == 0 ? 0 : stain); + var weapon = value.With(value.Skeleton.Id == 0 ? StainIds.None : stains); LoadWeapon(character, slot, weapon); } } diff --git a/Glamourer/Services/CollectionOverrideService.cs b/Glamourer/Services/CollectionOverrideService.cs index 691118f..fcc9998 100644 --- a/Glamourer/Services/CollectionOverrideService.cs +++ b/Glamourer/Services/CollectionOverrideService.cs @@ -1,13 +1,13 @@ -using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.ImGuiNotification; using Glamourer.Interop.Penumbra; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using OtterGui; -using OtterGui.Classes; using OtterGui.Filesystem; using OtterGui.Services; using Penumbra.GameData.Actors; using Penumbra.GameData.Interop; +using Notification = OtterGui.Classes.Notification; namespace Glamourer.Services; diff --git a/Glamourer/Services/CustomizeService.cs b/Glamourer/Services/CustomizeService.cs index bb9737d..6505846 100644 --- a/Glamourer/Services/CustomizeService.cs +++ b/Glamourer/Services/CustomizeService.cs @@ -161,13 +161,6 @@ public sealed class CustomizeService( return $"The gender {gender.ToName()} is unknown, reset to {Gender.Male.ToName()}."; } - // TODO: Female Hrothgar - if (gender is Gender.Female && race is Race.Hrothgar) - { - actualGender = Gender.Male; - return $"{Race.Hrothgar.ToName()} do not currently support {Gender.Female.ToName()} characters, reset to {Gender.Male.ToName()}."; - } - actualGender = gender; return string.Empty; } @@ -225,13 +218,6 @@ public sealed class CustomizeService( customize.Race = newRace; customize.Clan = newClan; - // TODO Female Hrothgar - if (newRace == Race.Hrothgar) - { - customize.Gender = Gender.Male; - flags |= CustomizeFlag.Gender; - } - var set = Manager.GetSet(customize.Clan, customize.Gender); return FixValues(set, ref customize) | flags; } @@ -242,10 +228,6 @@ public sealed class CustomizeService( if (customize.Gender == newGender) return 0; - // TODO Female Hrothgar - if (customize.Race is Race.Hrothgar) - return 0; - if (ValidateGender(customize.Race, newGender, out newGender).Length > 0) return 0; diff --git a/Glamourer/Services/DalamudServices.cs b/Glamourer/Services/DalamudServices.cs index fd001d7..02df634 100644 --- a/Glamourer/Services/DalamudServices.cs +++ b/Glamourer/Services/DalamudServices.cs @@ -8,7 +8,7 @@ namespace Glamourer.Services; public class DalamudServices { - public static void AddServices(ServiceManager services, DalamudPluginInterface pi) + public static void AddServices(ServiceManager services, IDalamudPluginInterface pi) { services.AddExistingService(pi); services.AddExistingService(pi.UiBuilder); diff --git a/Glamourer/Services/FilenameService.cs b/Glamourer/Services/FilenameService.cs index e19e289..cd25c64 100644 --- a/Glamourer/Services/FilenameService.cs +++ b/Glamourer/Services/FilenameService.cs @@ -19,7 +19,7 @@ public class FilenameService public readonly string NpcAppearanceFile; public readonly string CollectionOverrideFile; - public FilenameService(DalamudPluginInterface pi) + public FilenameService(IDalamudPluginInterface pi) { ConfigDirectory = pi.ConfigDirectory.FullName; ConfigFile = pi.ConfigFile.FullName; diff --git a/Glamourer/Services/HeightService.cs b/Glamourer/Services/HeightService.cs index 48f0dd6..0a6c7bb 100644 --- a/Glamourer/Services/HeightService.cs +++ b/Glamourer/Services/HeightService.cs @@ -2,6 +2,7 @@ using Dalamud.Plugin.Services; using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using OtterGui.Services; +using Penumbra.GameData; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -9,7 +10,7 @@ namespace Glamourer.Services; public unsafe class HeightService : IService { - [Signature("E8 ?? ?? ?? FF 48 8B 0D ?? ?? ?? ?? 0F 28 F0")] + [Signature(Sigs.CalculateHeight)] private readonly delegate* unmanaged[Stdcall] _calculateHeight = null!; public HeightService(IGameInteropProvider interop) diff --git a/Glamourer/Services/ItemManager.cs b/Glamourer/Services/ItemManager.cs index 8f52815..c5f537f 100644 --- a/Glamourer/Services/ItemManager.cs +++ b/Glamourer/Services/ItemManager.cs @@ -195,16 +195,16 @@ public class ItemManager /// The returned stain id is either the input or 0. /// The return value is an empty string if there was no problem and a warning otherwise. /// - public string ValidateStain(StainId stain, out StainId ret, bool allowUnknown) + public string ValidateStain(StainIds stains, out StainIds ret, bool allowUnknown) { - if (allowUnknown || IsStainValid(stain)) + if (allowUnknown || stains.All(IsStainValid)) { - ret = stain; + ret = stains; return string.Empty; } - ret = 0; - return $"The Stain {stain} does not exist, reset to unstained."; + ret = StainIds.None; + return $"The Stain {stains} does not exist, reset to unstained."; } /// Returns whether an offhand is valid given the required offhand type. diff --git a/Glamourer/Services/ServiceManager.cs b/Glamourer/Services/ServiceManager.cs index 005944e..48632ab 100644 --- a/Glamourer/Services/ServiceManager.cs +++ b/Glamourer/Services/ServiceManager.cs @@ -33,7 +33,7 @@ namespace Glamourer.Services; public static class StaticServiceManager { - public static ServiceManager CreateProvider(DalamudPluginInterface pi, Logger log, Glamourer glamourer) + public static ServiceManager CreateProvider(IDalamudPluginInterface pi, Logger log, Glamourer glamourer) { EventWrapperBase.ChangeLogger(log); var services = new ServiceManager(log) @@ -165,5 +165,6 @@ public static class StaticServiceManager .AddSingleton() .AddSingleton() .AddSingleton() - .AddSingleton(); + .AddSingleton() + .AddSingleton(); } diff --git a/Glamourer/Services/TextureService.cs b/Glamourer/Services/TextureService.cs index 0619279..99436e4 100644 --- a/Glamourer/Services/TextureService.cs +++ b/Glamourer/Services/TextureService.cs @@ -1,5 +1,5 @@ using Dalamud.Interface; -using Dalamud.Interface.Internal; +using Dalamud.Interface.Textures.TextureWraps; using Dalamud.Plugin.Services; using OtterGui.Classes; using Penumbra.GameData.Enums; @@ -7,7 +7,7 @@ using Penumbra.GameData.Structs; namespace Glamourer.Services; -public sealed class TextureService(UiBuilder uiBuilder, IDataManager dataManager, ITextureProvider textureProvider) +public sealed class TextureService(IUiBuilder uiBuilder, IDataManager dataManager, ITextureProvider textureProvider) : TextureCache(dataManager, textureProvider), IDisposable { private readonly IDalamudTextureWrap?[] _slotIcons = CreateSlotIcons(uiBuilder); @@ -32,7 +32,7 @@ public sealed class TextureService(UiBuilder uiBuilder, IDataManager dataManager } } - private static IDalamudTextureWrap?[] CreateSlotIcons(UiBuilder uiBuilder) + private static IDalamudTextureWrap?[] CreateSlotIcons(IUiBuilder uiBuilder) { var ret = new IDalamudTextureWrap?[12]; diff --git a/Glamourer/State/FunEquipSet.cs b/Glamourer/State/FunEquipSet.cs index 91e6419..c1ae02e 100644 --- a/Glamourer/State/FunEquipSet.cs +++ b/Glamourer/State/FunEquipSet.cs @@ -1,5 +1,4 @@ -using Glamourer.Interop.Structs; -using Penumbra.GameData.Structs; +using Penumbra.GameData.Structs; namespace Glamourer.State; @@ -21,8 +20,8 @@ internal class FunEquipSet { public Group(ushort headS, byte headV, ushort bodyS, byte bodyV, ushort handsS, byte handsV, ushort legsS, byte legsV, ushort feetS, byte feetV, StainId[]? stains = null) - : this(new CharacterArmor(headS, headV, 0), new CharacterArmor(bodyS, bodyV, 0), new CharacterArmor(handsS, handsV, 0), - new CharacterArmor(legsS, legsV, 0), new CharacterArmor(feetS, feetV, 0), stains) + : this(new CharacterArmor(headS, headV, StainIds.None), new CharacterArmor(bodyS, bodyV, StainIds.None), new CharacterArmor(handsS, handsV, StainIds.None), + new CharacterArmor(legsS, legsV, StainIds.None), new CharacterArmor(feetS, feetV, StainIds.None), stains) { } public static Group FullSetWithoutHat(ushort modelSet, byte variant, StainId[]? stains = null) diff --git a/Glamourer/State/FunModule.cs b/Glamourer/State/FunModule.cs index 25b8946..a9ce8e2 100644 --- a/Glamourer/State/FunModule.cs +++ b/Glamourer/State/FunModule.cs @@ -1,5 +1,5 @@ -using Dalamud.Game.ClientState.Objects.Enums; -using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.ImGuiNotification; +using FFXIVClientStructs.FFXIV.Client.Game.Object; using Glamourer.Designs; using Glamourer.Gui; using Glamourer.Services; @@ -106,7 +106,7 @@ public unsafe class FunModule : IDisposable && actor.OnlineStatus is OnlineStatus.PvEMentor or OnlineStatus.PvPMentor or OnlineStatus.TradeMentor && slot.IsEquipment()) { - armor = new CharacterArmor(6117, 1, 0); + armor = new CharacterArmor(6117, 1, StainIds.None); return; } @@ -171,7 +171,7 @@ public unsafe class FunModule : IDisposable break; case CodeService.CodeFlag.Dolphins: SetDolphin(EquipSlot.Body, ref armor[1]); - SetDolphin(EquipSlot.Head, ref armor[0]); + SetDolphin(EquipSlot.Head, ref armor[0]); break; case CodeService.CodeFlag.World when actor.Index != 0: _worldSets.Apply(actor, _rng, armor); @@ -198,7 +198,7 @@ public unsafe class FunModule : IDisposable private static bool ValidFunTarget(Actor actor) => actor.IsCharacter - && actor.AsObject->ObjectKind is (byte)ObjectKind.Player + && actor.AsObject->ObjectKind is ObjectKind.Pc && !actor.IsTransformed && actor.AsCharacter->CharacterData.ModelCharaId == 0; @@ -208,7 +208,7 @@ public unsafe class FunModule : IDisposable private void SetRandomDye(ref CharacterArmor armor) { var stainIdx = _rng.Next(0, _stains.Length - 1); - armor.Stain = _stains[stainIdx]; + armor.Stains = _stains[stainIdx]; } private void SetRandomItem(EquipSlot slot, ref CharacterArmor armor) @@ -235,17 +235,17 @@ public unsafe class FunModule : IDisposable private static IReadOnlyList DolphinBodies => [ - new CharacterArmor(6089, 1, 4), // Toad - new CharacterArmor(6089, 1, 4), // Toad - new CharacterArmor(6089, 1, 4), // Toad - new CharacterArmor(6023, 1, 4), // Swine - new CharacterArmor(6023, 1, 4), // Swine - new CharacterArmor(6023, 1, 4), // Swine - new CharacterArmor(6133, 1, 4), // Gaja - new CharacterArmor(6182, 1, 3), // Imp - new CharacterArmor(6182, 1, 3), // Imp - new CharacterArmor(6182, 1, 4), // Imp - new CharacterArmor(6182, 1, 4), // Imp + new CharacterArmor(6089, 1, new StainIds(4)), // Toad + new CharacterArmor(6089, 1, new StainIds(4)), // Toad + new CharacterArmor(6089, 1, new StainIds(4)), // Toad + new CharacterArmor(6023, 1, new StainIds(4)), // Swine + new CharacterArmor(6023, 1, new StainIds(4)), // Swine + new CharacterArmor(6023, 1, new StainIds(4)), // Swine + new CharacterArmor(6133, 1, new StainIds(4)), // Gaja + new CharacterArmor(6182, 1, new StainIds(3)), // Imp + new CharacterArmor(6182, 1, new StainIds(3)), // Imp + new CharacterArmor(6182, 1, new StainIds(4)), // Imp + new CharacterArmor(6182, 1, new StainIds(4)), // Imp ]; private void SetDolphin(EquipSlot slot, ref CharacterArmor armor) @@ -253,7 +253,7 @@ public unsafe class FunModule : IDisposable armor = slot switch { EquipSlot.Body => DolphinBodies[_rng.Next(0, DolphinBodies.Count - 1)], - EquipSlot.Head => new CharacterArmor(5040, 1, 0), + EquipSlot.Head => new CharacterArmor(5040, 1, StainIds.None), _ => armor, }; } @@ -270,7 +270,7 @@ public unsafe class FunModule : IDisposable private static void SetCrown(Span armor) { - var clown = new CharacterArmor(6117, 1, 0); + var clown = new CharacterArmor(6117, 1, StainIds.None); armor[0] = clown; armor[1] = clown; armor[2] = clown; @@ -285,15 +285,12 @@ public unsafe class FunModule : IDisposable return; var targetClan = (SubRace)((int)race * 2 - (int)customize.Clan % 2); - // TODO Female Hrothgar - if (race is Race.Hrothgar && customize.Gender is Gender.Female) - targetClan = targetClan is SubRace.Lost ? SubRace.Seawolf : SubRace.Hellsguard; _customizations.ChangeClan(ref customize, targetClan); } private void SetGender(ref CustomizeArray customize) { - if (!_codes.Enabled(CodeService.CodeFlag.SixtyThree) || customize.Race is Race.Hrothgar) // TODO Female Hrothgar + if (!_codes.Enabled(CodeService.CodeFlag.SixtyThree)) return; _customizations.ChangeGender(ref customize, customize.Gender is Gender.Male ? Gender.Female : Gender.Male); diff --git a/Glamourer/State/InternalStateEditor.cs b/Glamourer/State/InternalStateEditor.cs index eaf7c21..17072e7 100644 --- a/Glamourer/State/InternalStateEditor.cs +++ b/Glamourer/State/InternalStateEditor.cs @@ -152,11 +152,11 @@ public class InternalStateEditor( } /// Change a single piece of equipment including stain. - public bool ChangeEquip(ActorState state, EquipSlot slot, EquipItem item, StainId stain, StateSource source, out EquipItem oldItem, - out StainId oldStain, uint key = 0) + public bool ChangeEquip(ActorState state, EquipSlot slot, EquipItem item, StainIds stains, StateSource source, out EquipItem oldItem, + out StainIds oldStains, uint key = 0) { oldItem = state.ModelData.Item(slot); - oldStain = state.ModelData.Stain(slot); + oldStains = state.ModelData.Stain(slot); if (!state.CanUnlock(key)) return false; @@ -168,7 +168,7 @@ public class InternalStateEditor( return false; var old = oldItem; - var oldS = oldStain; + var oldS = oldStains; gPose.AddActionOnLeave(() => { if (old.Type == state.BaseData.Item(slot).Type) @@ -177,20 +177,20 @@ public class InternalStateEditor( } state.ModelData.SetItem(slot, item); - state.ModelData.SetStain(slot, stain); + state.ModelData.SetStain(slot, stains); state.Sources[slot, false] = source; state.Sources[slot, true] = source; return true; } /// Change only the stain of an equipment piece. - public bool ChangeStain(ActorState state, EquipSlot slot, StainId stain, StateSource source, out StainId oldStain, uint key = 0) + public bool ChangeStains(ActorState state, EquipSlot slot, StainIds stains, StateSource source, out StainIds oldStains, uint key = 0) { - oldStain = state.ModelData.Stain(slot); + oldStains = state.ModelData.Stain(slot); if (!state.CanUnlock(key)) return false; - state.ModelData.SetStain(slot, stain); + state.ModelData.SetStain(slot, stains); state.Sources[slot, true] = source; return true; } diff --git a/Glamourer/State/StateApplier.cs b/Glamourer/State/StateApplier.cs index 9c06ce5..bf0fc30 100644 --- a/Glamourer/State/StateApplier.cs +++ b/Glamourer/State/StateApplier.cs @@ -130,22 +130,22 @@ public class StateApplier( /// Change the stain of a single piece of armor or weapon. /// If the offhand is empty, the stain will be fixed to 0 to prevent crashes. /// - public void ChangeStain(ActorData data, EquipSlot slot, StainId stain) + public void ChangeStain(ActorData data, EquipSlot slot, StainIds stains) { var idx = slot.ToIndex(); switch (idx) { case < 10: foreach (var actor in data.Objects.Where(a => a.Model.IsHuman)) - _updateSlot.UpdateStain(actor.Model, slot, stain); + _updateSlot.UpdateStain(actor.Model, slot, stains); break; case 10: foreach (var actor in data.Objects.Where(a => a.Model.IsHuman)) - _weapon.LoadStain(actor, EquipSlot.MainHand, stain); + _weapon.LoadStain(actor, EquipSlot.MainHand, stains); break; case 11: foreach (var actor in data.Objects.Where(a => a.Model.IsHuman)) - _weapon.LoadStain(actor, EquipSlot.OffHand, stain); + _weapon.LoadStain(actor, EquipSlot.OffHand, stains); break; } } @@ -162,12 +162,12 @@ public class StateApplier( /// Apply a weapon to the appropriate slot. - public void ChangeWeapon(ActorData data, EquipSlot slot, EquipItem item, StainId stain) + public void ChangeWeapon(ActorData data, EquipSlot slot, EquipItem item, StainIds stains) { if (slot is EquipSlot.MainHand) - ChangeMainhand(data, item, stain); + ChangeMainhand(data, item, stains); else - ChangeOffhand(data, item, stain); + ChangeOffhand(data, item, stains); } /// @@ -186,19 +186,19 @@ public class StateApplier( /// /// Apply a weapon to the mainhand. If the weapon type has no associated offhand type, apply both. /// - public void ChangeMainhand(ActorData data, EquipItem weapon, StainId stain) + public void ChangeMainhand(ActorData data, EquipItem weapon, StainIds stains) { var slot = weapon.Type.ValidOffhand() == FullEquipType.Unknown ? EquipSlot.BothHand : EquipSlot.MainHand; foreach (var actor in data.Objects.Where(a => a.Model.IsHuman)) - _weapon.LoadWeapon(actor, slot, weapon.Weapon().With(stain)); + _weapon.LoadWeapon(actor, slot, weapon.Weapon().With(stains)); } /// Apply a weapon to the offhand. - public void ChangeOffhand(ActorData data, EquipItem weapon, StainId stain) + public void ChangeOffhand(ActorData data, EquipItem weapon, StainIds stains) { - stain = weapon.PrimaryId.Id == 0 ? 0 : stain; + stains = weapon.PrimaryId.Id == 0 ? StainIds.None : stains; foreach (var actor in data.Objects.Where(a => a.Model.IsHuman)) - _weapon.LoadWeapon(actor, EquipSlot.OffHand, weapon.Weapon().With(stain)); + _weapon.LoadWeapon(actor, EquipSlot.OffHand, weapon.Weapon().With(stains)); } /// Change a meta state. @@ -209,7 +209,7 @@ public class StateApplier( case MetaIndex.Wetness: { foreach (var actor in data.Objects.Where(a => a.IsCharacter)) - actor.AsCharacter->IsGPoseWet = value; + actor.IsGPoseWet = value; return; } case MetaIndex.HatState: diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index c20a69d..dccb283 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -90,22 +90,22 @@ public class StateEditor( } /// - public void ChangeEquip(object data, EquipSlot slot, EquipItem? item, StainId? stain, ApplySettings settings) + public void ChangeEquip(object data, EquipSlot slot, EquipItem? item, StainIds? stains, ApplySettings settings) { - switch (item.HasValue, stain.HasValue) + switch (item.HasValue, stains.HasValue) { case (false, false): return; case (true, false): ChangeItem(data, slot, item!.Value, settings); return; case (false, true): - ChangeStain(data, slot, stain!.Value, settings); + ChangeStains(data, slot, stains!.Value, settings); return; } var state = (ActorState)data; - if (!Editor.ChangeEquip(state, slot, item ?? state.ModelData.Item(slot), stain ?? state.ModelData.Stain(slot), settings.Source, - out var old, out var oldStain, settings.Key)) + if (!Editor.ChangeEquip(state, slot, item ?? state.ModelData.Item(slot), stains ?? state.ModelData.Stain(slot), settings.Source, + out var old, out var oldStains, settings.Key)) return; var type = slot.ToIndex() < 10 ? StateChangeType.Equip : StateChangeType.Weapon; @@ -115,25 +115,25 @@ public class StateEditor( item!.Value.Type != (slot is EquipSlot.MainHand ? state.BaseData.MainhandType : state.BaseData.OffhandType)); if (slot is EquipSlot.MainHand) - ApplyMainhandPeriphery(state, item, stain, settings); + ApplyMainhandPeriphery(state, item, stains, settings); Glamourer.Log.Verbose( - $"Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.ItemId}) to {item!.Value.Name} ({item.Value.ItemId}) and its stain from {oldStain.Id} to {stain!.Value.Id}. [Affecting {actors.ToLazyString("nothing")}.]"); - StateChanged.Invoke(type, settings.Source, state, actors, (old, item!.Value, slot)); - StateChanged.Invoke(StateChangeType.Stain, settings.Source, state, actors, (oldStain, stain!.Value, slot)); + $"Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.ItemId}) to {item!.Value.Name} ({item.Value.ItemId}) and its stain from {oldStains} to {stains!.Value}. [Affecting {actors.ToLazyString("nothing")}.]"); + StateChanged.Invoke(type, settings.Source, state, actors, (old, item!.Value, slot)); + StateChanged.Invoke(StateChangeType.Stains, settings.Source, state, actors, (oldStains, stains!.Value, slot)); } /// - public void ChangeStain(object data, EquipSlot slot, StainId stain, ApplySettings settings) + public void ChangeStains(object data, EquipSlot slot, StainIds stains, ApplySettings settings) { var state = (ActorState)data; - if (!Editor.ChangeStain(state, slot, stain, settings.Source, out var old, settings.Key)) + if (!Editor.ChangeStains(state, slot, stains, settings.Source, out var old, settings.Key)) return; var actors = Applier.ChangeStain(state, slot, settings.Source.RequiresChange()); Glamourer.Log.Verbose( - $"Set {slot.ToName()} stain in state {state.Identifier.Incognito(null)} from {old.Id} to {stain.Id}. [Affecting {actors.ToLazyString("nothing")}.]"); - StateChanged.Invoke(StateChangeType.Stain, settings.Source, state, actors, (old, stain, slot)); + $"Set {slot.ToName()} stain in state {state.Identifier.Incognito(null)} from {old} to {stains}. [Affecting {actors.ToLazyString("nothing")}.]"); + StateChanged.Invoke(StateChangeType.Stains, settings.Source, state, actors, (old, stains, slot)); } /// @@ -269,7 +269,7 @@ public class StateEditor( if (mergedDesign.Design.DoApplyStain(slot)) if (!settings.RespectManual || !state.Sources[slot, true].IsManual()) - Editor.ChangeStain(state, slot, mergedDesign.Design.DesignData.Stain(slot), + Editor.ChangeStains(state, slot, mergedDesign.Design.DesignData.Stain(slot), Source(slot.ToState(true)), out _, settings.Key); } @@ -277,7 +277,7 @@ public class StateEditor( { if (mergedDesign.Design.DoApplyStain(weaponSlot)) if (!settings.RespectManual || !state.Sources[weaponSlot, true].IsManual()) - Editor.ChangeStain(state, weaponSlot, mergedDesign.Design.DesignData.Stain(weaponSlot), + Editor.ChangeStains(state, weaponSlot, mergedDesign.Design.DesignData.Stain(weaponSlot), Source(weaponSlot.ToState(true)), out _, settings.Key); if (!mergedDesign.Design.DoApplyEquip(weaponSlot)) @@ -392,19 +392,19 @@ public class StateEditor( /// Apply offhand item and potentially gauntlets if configured. - private void ApplyMainhandPeriphery(ActorState state, EquipItem? newMainhand, StainId? newStain, ApplySettings settings) + private void ApplyMainhandPeriphery(ActorState state, EquipItem? newMainhand, StainIds? newStains, ApplySettings settings) { if (!Config.ChangeEntireItem || !settings.Source.IsManual()) return; var mh = newMainhand ?? state.ModelData.Item(EquipSlot.MainHand); var offhand = newMainhand != null ? Items.GetDefaultOffhand(mh) : state.ModelData.Item(EquipSlot.OffHand); - var stain = newStain ?? state.ModelData.Stain(EquipSlot.MainHand); + var stains = newStains ?? state.ModelData.Stain(EquipSlot.MainHand); if (offhand.Valid) - ChangeEquip(state, EquipSlot.OffHand, offhand, stain, settings); + ChangeEquip(state, EquipSlot.OffHand, offhand, stains, settings); if (mh is { Type: FullEquipType.Fists } && Items.ItemData.Tertiary.TryGetValue(mh.ItemId, out var gauntlets)) ChangeEquip(state, EquipSlot.Hands, newMainhand != null ? gauntlets : state.ModelData.Item(EquipSlot.Hands), - stain, settings); + stains, settings); } } diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index f3657a7..8b3b5e7 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -227,7 +227,7 @@ public class StateListener : IDisposable (_, armor) = _items.RestrictedGear.ResolveRestricted(armor, slot, customize.Race, customize.Gender); } - private void OnMovedEquipment((EquipSlot, uint, StainId)[] items) + private void OnMovedEquipment((EquipSlot, uint, StainIds)[] items) { _objects.Update(); var (identifier, objects) = _objects.PlayerData; @@ -250,14 +250,14 @@ public class StateListener : IDisposable && current.Weapon == changed.Weapon && !state.Sources[slot, false].IsFixed(); - var stainChanged = current.Stain == changed.Stain && !state.Sources[slot, true].IsFixed(); + var stainChanged = current.Stains == changed.Stains && !state.Sources[slot, true].IsFixed(); switch ((itemChanged, stainChanged)) { case (true, true): - _manager.ChangeEquip(state, slot, currentItem, current.Stain, ApplySettings.Game); + _manager.ChangeEquip(state, slot, currentItem, current.Stains, ApplySettings.Game); if (slot is EquipSlot.MainHand or EquipSlot.OffHand) - _applier.ChangeWeapon(objects, slot, currentItem, current.Stain); + _applier.ChangeWeapon(objects, slot, currentItem, current.Stains); else _applier.ChangeArmor(objects, slot, current.ToArmor(), !state.Sources[slot, false].IsFixed(), state.ModelData.IsHatVisible()); @@ -265,14 +265,14 @@ public class StateListener : IDisposable case (true, false): _manager.ChangeItem(state, slot, currentItem, ApplySettings.Game); if (slot is EquipSlot.MainHand or EquipSlot.OffHand) - _applier.ChangeWeapon(objects, slot, currentItem, model.Stain); + _applier.ChangeWeapon(objects, slot, currentItem, model.Stains); else - _applier.ChangeArmor(objects, slot, current.ToArmor(model.Stain), !state.Sources[slot, false].IsFixed(), + _applier.ChangeArmor(objects, slot, current.ToArmor(model.Stains), !state.Sources[slot, false].IsFixed(), state.ModelData.IsHatVisible()); break; case (false, true): - _manager.ChangeStain(state, slot, current.Stain, ApplySettings.Game); - _applier.ChangeStain(objects, slot, current.Stain); + _manager.ChangeStains(state, slot, current.Stains, ApplySettings.Game); + _applier.ChangeStain(objects, slot, current.Stains); break; } } @@ -308,7 +308,7 @@ public class StateListener : IDisposable apply = true; if (!state.Sources[slot, true].IsFixed()) - _manager.ChangeStain(state, slot, state.BaseData.Stain(slot), ApplySettings.Game); + _manager.ChangeStains(state, slot, state.BaseData.Stain(slot), ApplySettings.Game); else apply = true; break; @@ -332,7 +332,7 @@ public class StateListener : IDisposable else { if (weapon.Skeleton.Id != 0) - weapon = weapon.With(newWeapon.Stain); + weapon = weapon.With(newWeapon.Stains); // Force unlock if necessary. _manager.ChangeItem(state, slot, state.BaseData.Item(slot), ApplySettings.Game with { Key = state.Combination }); } @@ -341,7 +341,7 @@ public class StateListener : IDisposable // Fist Weapon Offhand hack. if (slot is EquipSlot.MainHand && weapon.Skeleton.Id is > 1600 and < 1651) _lastFistOffhand = new CharacterWeapon((PrimaryId)(weapon.Skeleton.Id + 50), weapon.Weapon, weapon.Variant, - weapon.Stain); + weapon.Stains); _funModule.ApplyFunToWeapon(actor, ref weapon, slot); } @@ -365,7 +365,7 @@ public class StateListener : IDisposable { var item = _items.Identify(slot, actorArmor.Set, actorArmor.Variant); state.BaseData.SetItem(EquipSlot.Head, item); - state.BaseData.SetStain(EquipSlot.Head, actorArmor.Stain); + state.BaseData.SetStain(EquipSlot.Head, actorArmor.Stains); return UpdateState.Change; } @@ -378,9 +378,9 @@ public class StateListener : IDisposable var baseData = state.BaseData.Armor(slot); var change = UpdateState.NoChange; - if (baseData.Stain != armor.Stain) + if (baseData.Stains != armor.Stains) { - state.BaseData.SetStain(slot, armor.Stain); + state.BaseData.SetStain(slot, armor.Stains); change = UpdateState.Change; } @@ -418,7 +418,7 @@ public class StateListener : IDisposable apply = true; if (!state.Sources[slot, true].IsFixed()) - _manager.ChangeStain(state, slot, state.BaseData.Stain(slot), ApplySettings.Game); + _manager.ChangeStains(state, slot, state.BaseData.Stain(slot), ApplySettings.Game); else apply = true; @@ -503,9 +503,9 @@ public class StateListener : IDisposable if (slot is EquipSlot.OffHand && weapon.Value == 0 && actor.GetMainhand().Skeleton.Id is > 1600 and < 1651) return UpdateState.NoChange; - if (baseData.Stain != weapon.Stain) + if (baseData.Stains != weapon.Stains) { - state.BaseData.SetStain(slot, weapon.Stain); + state.BaseData.SetStain(slot, weapon.Stains); change = UpdateState.Change; } diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index f057580..92cf6e5 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -117,7 +117,7 @@ public sealed class StateManager( if (!_humans.IsHuman((uint)actor.AsCharacter->CharacterData.ModelCharaId)) { ret.LoadNonHuman((uint)actor.AsCharacter->CharacterData.ModelCharaId, *(CustomizeArray*)&actor.AsCharacter->DrawData.CustomizeData, - (nint)(&actor.AsCharacter->DrawData.Head)); + (nint)Unsafe.AsPointer(ref actor.AsCharacter->DrawData.EquipmentModelIds[0])); return ret; } @@ -141,7 +141,7 @@ public sealed class StateManager( var head = ret.IsHatVisible() || ignoreHatState ? model.GetArmor(EquipSlot.Head) : actor.GetArmor(EquipSlot.Head); var headItem = Items.Identify(EquipSlot.Head, head.Set, head.Variant); ret.SetItem(EquipSlot.Head, headItem); - ret.SetStain(EquipSlot.Head, head.Stain); + ret.SetStain(EquipSlot.Head, head.Stains); // The other slots can be used from the draw object. foreach (var slot in EquipSlotExtensions.EqdpSlots.Skip(1)) @@ -149,7 +149,7 @@ public sealed class StateManager( var armor = model.GetArmor(slot); var item = Items.Identify(slot, armor.Set, armor.Variant); ret.SetItem(slot, item); - ret.SetStain(slot, armor.Stain); + ret.SetStain(slot, armor.Stains); } // Weapons use the draw objects of the weapons, but require the game object either way. @@ -171,7 +171,7 @@ public sealed class StateManager( var armor = actor.GetArmor(slot); var item = Items.Identify(slot, armor.Set, armor.Variant); ret.SetItem(slot, item); - ret.SetStain(slot, armor.Stain); + ret.SetStain(slot, armor.Stains); } main = actor.GetMainhand(); @@ -187,13 +187,13 @@ public sealed class StateManager( var mainItem = Items.Identify(EquipSlot.MainHand, main.Skeleton, main.Weapon, main.Variant); var offItem = Items.Identify(EquipSlot.OffHand, off.Skeleton, off.Weapon, off.Variant, mainItem.Type); ret.SetItem(EquipSlot.MainHand, mainItem); - ret.SetStain(EquipSlot.MainHand, main.Stain); + ret.SetStain(EquipSlot.MainHand, main.Stains); ret.SetItem(EquipSlot.OffHand, offItem); - ret.SetStain(EquipSlot.OffHand, off.Stain); + ret.SetStain(EquipSlot.OffHand, off.Stains); // Wetness can technically only be set in GPose or via external tools. // It is only available in the game object. - ret.SetIsWet(actor.AsCharacter->IsGPoseWet); + ret.SetIsWet(actor.IsGPoseWet); // Weapon visibility could technically be inferred from the weapon draw objects, // but since we use hat visibility from the game object we can also use weapon visibility from it. @@ -214,7 +214,7 @@ public sealed class StateManager( offhand.Variant = mainhand.Variant; offhand.Weapon = mainhand.Weapon; ret.SetItem(EquipSlot.Hands, gauntlets); - ret.SetStain(EquipSlot.Hands, mainhand.Stain); + ret.SetStain(EquipSlot.Hands, mainhand.Stains); } /// Turn an actor human. @@ -414,7 +414,9 @@ public sealed class StateManager( public void ReapplyState(Actor actor, ActorState state, bool forceRedraw, StateSource source) { var data = Applier.ApplyAll(state, - forceRedraw || !actor.Model.IsHuman || CustomizeArray.Compare(actor.Model.GetCustomize(), state.ModelData.Customize).RequiresRedraw(), false); + forceRedraw + || !actor.Model.IsHuman + || CustomizeArray.Compare(actor.Model.GetCustomize(), state.ModelData.Customize).RequiresRedraw(), false); StateChanged.Invoke(StateChangeType.Reapply, source, state, data, null); } diff --git a/Glamourer/State/WorldSets.cs b/Glamourer/State/WorldSets.cs index eca0988..6a497ea 100644 --- a/Glamourer/State/WorldSets.cs +++ b/Glamourer/State/WorldSets.cs @@ -22,7 +22,7 @@ public class WorldSets [(Gender.Male, Race.AuRa)] = FunEquipSet.Group.FullSetWithoutHat(0257, 2), [(Gender.Female, Race.AuRa)] = FunEquipSet.Group.FullSetWithoutHat(0258, 2), [(Gender.Male, Race.Hrothgar)] = FunEquipSet.Group.FullSetWithoutHat(0597, 1), - [(Gender.Female, Race.Hrothgar)] = FunEquipSet.Group.FullSetWithoutHat(0000, 0), // TODO Hrothgar Female + [(Gender.Female, Race.Hrothgar)] = FunEquipSet.Group.FullSetWithoutHat(0829, 1), [(Gender.Male, Race.Viera)] = FunEquipSet.Group.FullSetWithoutHat(0744, 1), [(Gender.Female, Race.Viera)] = FunEquipSet.Group.FullSetWithoutHat(0581, 1), }; diff --git a/Glamourer/Unlocks/CustomizeUnlockManager.cs b/Glamourer/Unlocks/CustomizeUnlockManager.cs index b63a98e..a204a1c 100644 --- a/Glamourer/Unlocks/CustomizeUnlockManager.cs +++ b/Glamourer/Unlocks/CustomizeUnlockManager.cs @@ -1,4 +1,4 @@ -using Dalamud; +using Dalamud.Game; using Dalamud.Hooking; using Dalamud.Plugin.Services; using Dalamud.Utility; @@ -8,6 +8,7 @@ using Glamourer.GameData; using Glamourer.Events; using Glamourer.Services; using Lumina.Excel.GeneratedSheets; +using Penumbra.GameData; using Penumbra.GameData.Enums; namespace Glamourer.Unlocks; @@ -127,8 +128,8 @@ public class CustomizeUnlockManager : IDisposable, ISavable } private delegate void SetUnlockLinkValueDelegate(nint uiState, uint data, byte value); - - [Signature("48 83 EC ?? 8B C2 44 8B D2", DetourName = nameof(SetUnlockLinkValueDetour))] + + [Signature(Sigs.SetUnlockLinkValue, DetourName = nameof(SetUnlockLinkValueDetour))] private readonly Hook _setUnlockLinkValueHook = null!; private void SetUnlockLinkValueDetour(nint uiState, uint data, byte value) diff --git a/Glamourer/Unlocks/FavoriteManager.cs b/Glamourer/Unlocks/FavoriteManager.cs index 33242c9..de22ea8 100644 --- a/Glamourer/Unlocks/FavoriteManager.cs +++ b/Glamourer/Unlocks/FavoriteManager.cs @@ -1,4 +1,4 @@ -using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.ImGuiNotification; using Glamourer.Services; using Newtonsoft.Json; using OtterGui.Classes; diff --git a/Glamourer/Unlocks/ItemUnlockManager.cs b/Glamourer/Unlocks/ItemUnlockManager.cs index de35335..0b95b94 100644 --- a/Glamourer/Unlocks/ItemUnlockManager.cs +++ b/Glamourer/Unlocks/ItemUnlockManager.cs @@ -144,8 +144,7 @@ public class ItemUnlockManager : ISavable, IDisposable, IReadOnlyDictionary(mirageManager->PrismBoxItemIds, 800); + var span = mirageManager->PrismBoxItemIds; foreach (var item in span) changes |= AddItem(item, time); } @@ -154,10 +153,9 @@ public class ItemUnlockManager : ISavable, IDisposable, IReadOnlyDictionaryGlamourPlatesSpan) + foreach (var plate in mirageManager->GlamourPlates) { - // TODO: Make independent from hardcoded value - var span = new ReadOnlySpan(plate.ItemIds, 12); + var span = plate.ItemIds; foreach (var item in span) changes |= AddItem(item, time); } @@ -176,8 +174,8 @@ public class ItemUnlockManager : ISavable, IDisposable, IReadOnlyDictionaryGetInventorySlot(_currentInventoryIndex++); if (item != null) { - changes |= AddItem(item->ItemID, time); - changes |= AddItem(item->GlamourID, time); + changes |= AddItem(item->ItemId, time); + changes |= AddItem(item->GlamourId, time); } } else diff --git a/Glamourer/Unlocks/UnlockDictionaryHelpers.cs b/Glamourer/Unlocks/UnlockDictionaryHelpers.cs index 28f5793..edc9472 100644 --- a/Glamourer/Unlocks/UnlockDictionaryHelpers.cs +++ b/Glamourer/Unlocks/UnlockDictionaryHelpers.cs @@ -1,4 +1,4 @@ -using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.ImGuiNotification; using OtterGui.Classes; namespace Glamourer.Unlocks; diff --git a/OtterGui b/OtterGui index fd79128..c2738e1 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit fd791285606d49a7644762ea0b4dc2bbb1368eac +Subproject commit c2738e1d42974cddbe5a31238c6ed236a831d17d diff --git a/Penumbra.GameData b/Penumbra.GameData index 6aeae34..8ec296d 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 6aeae346332a255b7575ccfca554afb0f3cf1494 +Subproject commit 8ec296d1f8113ae2ba509527749cd3e8f54debbf diff --git a/Penumbra.String b/Penumbra.String index caa58c5..f04abba 160000 --- a/Penumbra.String +++ b/Penumbra.String @@ -1 +1 @@ -Subproject commit caa58c5c92710e69ce07b9d736ebe2d228cb4488 +Subproject commit f04abbabedf5e757c5cbb970f3e513fef23e53cf diff --git a/repo.json b/repo.json index 3798270..c5ef399 100644 --- a/repo.json +++ b/repo.json @@ -21,7 +21,7 @@ "TestingAssemblyVersion": "1.2.3.3", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", - "DalamudApiLevel": 9, + "DalamudApiLevel": 10, "IsHide": "False", "IsTestingExclusive": "False", "DownloadCount": 1,