diff --git a/Glamourer/Designs/Design.cs b/Glamourer/Designs/Design.cs index b8dc0a2..05ee948 100644 --- a/Glamourer/Designs/Design.cs +++ b/Glamourer/Designs/Design.cs @@ -106,6 +106,7 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn ["Tags"] = JArray.FromObject(Tags), ["WriteProtected"] = WriteProtected(), ["Equipment"] = SerializeEquipment(), + ["Bonus"] = SerializeBonusItems(), ["Customize"] = SerializeCustomize(), ["Parameters"] = SerializeParameters(), ["Materials"] = SerializeMaterials(), @@ -171,6 +172,7 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn design.SetWriteProtected(json["WriteProtected"]?.ToObject() ?? false); LoadCustomize(customizations, json["Customize"], design, design.Name, true, false); LoadEquip(items, json["Equipment"], design, design.Name, true); + LoadBonus(items, design, json["Bonus"]); LoadMods(json["Mods"], design); LoadParameters(json["Parameters"], design, design.Name); LoadMaterials(json["Materials"], design, design.Name); diff --git a/Glamourer/Designs/DesignBase.cs b/Glamourer/Designs/DesignBase.cs index be12737..06e55d7 100644 --- a/Glamourer/Designs/DesignBase.cs +++ b/Glamourer/Designs/DesignBase.cs @@ -228,6 +228,7 @@ public class DesignBase { ["FileVersion"] = FileVersion, ["Equipment"] = SerializeEquipment(), + ["Bonus"] = SerializeBonusItems(), ["Customize"] = SerializeCustomize(), ["Parameters"] = SerializeParameters(), ["Materials"] = SerializeMaterials(), @@ -279,7 +280,7 @@ public class DesignBase var item = _designData.BonusItem(slot); ret[slot.ToString()] = new JObject() { - ["BonusId"] = item.ModelId.Id, + ["BonusId"] = item.Id.Id, ["Apply"] = DoApplyBonusItem(slot), }; } @@ -424,19 +425,44 @@ public class DesignBase LoadEquip(items, json["Equipment"], ret, "Temporary Design", true); LoadParameters(json["Parameters"], ret, "Temporary Design"); LoadMaterials(json["Materials"], ret, "Temporary Design"); + LoadBonus(items, ret, json["Bonus"]); return ret; } + protected static void LoadBonus(ItemManager items, DesignBase design, JToken? json) + { + if (json is not JObject obj) + { + design.Application.BonusItem = 0; + return; + } + + foreach (var slot in BonusExtensions.AllFlags) + { + var itemJson = json[slot.ToString()] as JObject; + if (itemJson == null) + { + design.Application.BonusItem &= ~slot; + design.GetDesignDataRef().SetBonusItem(slot, BonusItem.Empty(slot)); + continue; + } + + design.SetApplyBonusItem(slot, itemJson["Apply"]?.ToObject() ?? false); + var id = itemJson["BonusId"]?.ToObject() ?? 0; + var item = items.Resolve(slot, id); + design.GetDesignDataRef().SetBonusItem(slot, item); + } + } + protected static void LoadParameters(JToken? parameters, DesignBase design, string name) { if (parameters == null) { - design.Application.Parameters = 0; + design.Application.Parameters = 0; design.GetDesignDataRef().Parameters = default; return; } - foreach (var flag in CustomizeParameterExtensions.ValueFlags) { if (!TryGetToken(flag, out var token)) @@ -492,7 +518,7 @@ public class DesignBase return true; } - design.Application.Parameters &= ~flag; + design.Application.Parameters &= ~flag; design.GetDesignDataRef().Parameters[flag] = CustomizeParameterValue.Zero; return false; } @@ -523,32 +549,15 @@ public class DesignBase return; } - 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); - var crest = item?["Crest"]?.ToObject() ?? false; - var apply = item?["Apply"]?.ToObject() ?? false; - var applyStain = item?["ApplyStain"]?.ToObject() ?? false; - var applyCrest = item?["ApplyCrest"]?.ToObject() ?? false; - return (id, stain, crest, apply, applyStain, applyCrest); - } - - void PrintWarning(string msg) - { - if (msg.Length > 0 && name != "Temporary Design") - Glamourer.Messager.NotificationMessage($"{msg} ({name})", NotificationType.Warning); - } - foreach (var slot in EquipSlotExtensions.EqdpSlots) { - var (id, stain, crest, apply, applyStain, applyCrest) = ParseItem(slot, equip[slot.ToString()]); + var (id, stains, crest, apply, applyStain, applyCrest) = ParseItem(slot, equip[slot.ToString()]); PrintWarning(items.ValidateItem(slot, id, out var item, allowUnknown)); - PrintWarning(items.ValidateStain(stain, out stain, allowUnknown)); + PrintWarning(items.ValidateStain(stains, out stains, allowUnknown)); var crestSlot = slot.ToCrestFlag(); design._designData.SetItem(slot, item); - design._designData.SetStain(slot, stain); + design._designData.SetStain(slot, stains); design._designData.SetCrest(crestSlot, crest); design.SetApplyEquip(slot, apply); design.SetApplyStain(slot, applyStain); @@ -556,21 +565,21 @@ public class DesignBase } { - var (id, stain, crest, apply, applyStain, applyCrest) = ParseItem(EquipSlot.MainHand, equip[EquipSlot.MainHand.ToString()]); + var (id, stains, crest, apply, applyStain, applyCrest) = ParseItem(EquipSlot.MainHand, equip[EquipSlot.MainHand.ToString()]); if (id == ItemManager.NothingId(EquipSlot.MainHand)) id = items.DefaultSword.ItemId; - var (idOff, stainOff, crestOff, applyOff, applyStainOff, applyCrestOff) = + var (idOff, stainsOff, crestOff, applyOff, applyStainOff, applyCrestOff) = ParseItem(EquipSlot.OffHand, equip[EquipSlot.OffHand.ToString()]); if (id == ItemManager.NothingId(EquipSlot.OffHand)) id = ItemManager.NothingId(FullEquipType.Shield); PrintWarning(items.ValidateWeapons(id, idOff, out var main, out var off, allowUnknown)); - PrintWarning(items.ValidateStain(stain, out stain, allowUnknown)); - PrintWarning(items.ValidateStain(stainOff, out stainOff, allowUnknown)); + PrintWarning(items.ValidateStain(stains, out stains, allowUnknown)); + PrintWarning(items.ValidateStain(stainsOff, out stainsOff, allowUnknown)); design._designData.SetItem(EquipSlot.MainHand, main); design._designData.SetItem(EquipSlot.OffHand, off); - design._designData.SetStain(EquipSlot.MainHand, stain); - design._designData.SetStain(EquipSlot.OffHand, stainOff); + design._designData.SetStain(EquipSlot.MainHand, stains); + design._designData.SetStain(EquipSlot.OffHand, stainsOff); design._designData.SetCrest(CrestFlag.MainHand, crest); design._designData.SetCrest(CrestFlag.OffHand, crestOff); design.SetApplyEquip(EquipSlot.MainHand, apply); @@ -591,6 +600,24 @@ public class DesignBase metaValue = QuadBool.FromJObject(equip["Visor"], "IsToggled", "Apply", QuadBool.NullFalse); design.SetApplyMeta(MetaIndex.VisorState, metaValue.Enabled); design._designData.SetVisor(metaValue.ForcedValue); + return; + + void PrintWarning(string msg) + { + if (msg.Length > 0 && name != "Temporary Design") + Glamourer.Messager.NotificationMessage($"{msg} ({name})", NotificationType.Warning); + } + + static (CustomItemId, StainIds, bool, bool, bool, bool) ParseItem(EquipSlot slot, JToken? item) + { + var id = item?["ItemId"]?.ToObject() ?? ItemManager.NothingId(slot).Id; + var stains = StainIds.ParseFromObject(item as JObject); + var crest = item?["Crest"]?.ToObject() ?? false; + var apply = item?["Apply"]?.ToObject() ?? false; + var applyStain = item?["ApplyStain"]?.ToObject() ?? false; + var applyCrest = item?["ApplyCrest"]?.ToObject() ?? false; + return (id, stains, crest, apply, applyStain, applyCrest); + } } protected static void LoadCustomize(CustomizeService customizations, JToken? json, DesignBase design, string name, bool forbidNonHuman, @@ -671,12 +698,12 @@ public class DesignBase { _designData = DesignBase64Migration.MigrateBase64(items, humans, base64, out var equipFlags, out var customizeFlags, out var writeProtected, out var applyMeta); - Application.Equip = equipFlags; - ApplyCustomize = customizeFlags; + Application.Equip = equipFlags; + ApplyCustomize = customizeFlags; Application.Parameters = 0; - Application.Crest = 0; - Application.Meta = applyMeta; - Application.BonusItem = 0; + Application.Crest = 0; + Application.Meta = applyMeta; + Application.BonusItem = 0; SetWriteProtected(writeProtected); CustomizeSet = SetCustomizationSet(customize); } diff --git a/Glamourer/Designs/DesignConverter.cs b/Glamourer/Designs/DesignConverter.cs index 21172fe..c3235ab 100644 --- a/Glamourer/Designs/DesignConverter.cs +++ b/Glamourer/Designs/DesignConverter.cs @@ -214,7 +214,7 @@ public class DesignConverter( foreach (var (key, value) in materials.Values) { var idx = MaterialValueIndex.FromKey(key); - if (idx.RowIndex >= LegacyColorTable.NumUsedRows) + if (idx.RowIndex >= ColorTable.NumUsedRows) continue; if (idx.MaterialIndex >= MaterialService.MaterialsPerModel) continue; diff --git a/Glamourer/Designs/DesignData.cs b/Glamourer/Designs/DesignData.cs index 7fb2f72..0a52861 100644 --- a/Glamourer/Designs/DesignData.cs +++ b/Glamourer/Designs/DesignData.cs @@ -101,7 +101,7 @@ public unsafe struct DesignData 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 ), + 11 => EquipItem.FromIds(_itemIds[11], _iconIds[11], *(PrimaryId*)(ptr + EquipmentByteSize + 8), *(SecondaryId*)(ptr + EquipmentByteSize + 10), *(Variant*)(ptr + EquipmentByteSize + 12), _typeOffhand, name: _nameOffhand ), _ => new EquipItem(), // @formatter:on }; @@ -176,13 +176,15 @@ public unsafe struct DesignData _nameMainhand = item.Name; _equipmentBytes[EquipmentByteSize + 2] = (byte)item.SecondaryId.Id; _equipmentBytes[EquipmentByteSize + 3] = (byte)(item.SecondaryId.Id >> 8); + _equipmentBytes[EquipmentByteSize + 4] = item.Variant.Id; _typeMainhand = item.Type; return true; case 11: - _nameOffhand = item.Name; - _equipmentBytes[EquipmentByteSize + 2] = (byte)item.SecondaryId.Id; - _equipmentBytes[EquipmentByteSize + 3] = (byte)(item.SecondaryId.Id >> 8); - _typeOffhand = item.Type; + _nameOffhand = item.Name; + _equipmentBytes[EquipmentByteSize + 10] = (byte)item.SecondaryId.Id; + _equipmentBytes[EquipmentByteSize + 11] = (byte)(item.SecondaryId.Id >> 8); + _equipmentBytes[EquipmentByteSize + 12] = item.Variant.Id; + _typeOffhand = item.Type; return true; } @@ -212,18 +214,18 @@ public unsafe struct DesignData => slot.ToIndex() switch { // @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), + 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 }; @@ -322,6 +324,13 @@ public unsafe struct DesignData SetItem(EquipSlot.OffHand, ItemManager.NothingItem(FullEquipType.Shield)); SetStain(EquipSlot.OffHand, StainIds.None); SetCrest(CrestFlag.OffHand, false); + SetDefaultBonusItems(); + } + + public void SetDefaultBonusItems() + { + foreach (var slot in BonusExtensions.AllFlags) + SetBonusItem(slot, Penumbra.GameData.Structs.BonusItem.Empty(slot)); } diff --git a/Glamourer/Designs/DesignEditor.cs b/Glamourer/Designs/DesignEditor.cs index 08cb241..4a104ca 100644 --- a/Glamourer/Designs/DesignEditor.cs +++ b/Glamourer/Designs/DesignEditor.cs @@ -165,9 +165,21 @@ public class DesignEditor( } } + /// public void ChangeBonusItem(object data, BonusItemFlag slot, BonusItem item, ApplySettings settings = default) { - + var design = (Design)data; + if (!Items.IsBonusItemValid(slot, item.Id, out item)) + return; + + var oldItem = design.DesignData.BonusItem(slot); + if (!design.GetDesignDataRef().SetBonusItem(slot, item)) + return; + + design.LastEdit = DateTimeOffset.UtcNow; + SaveService.QueueSave(design); + Glamourer.Log.Debug($"Set {slot} bonus item to {item}."); + DesignChanged.Invoke(DesignChanged.Type.BonusItem, design, (oldItem, item, slot)); } /// diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index 725d562..97058a9 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -356,7 +356,7 @@ public sealed class DesignManager : DesignEditor design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); Glamourer.Log.Debug($"Set applying of {slot} bonus item to {value}."); - DesignChanged.Invoke(DesignChanged.Type.ApplyBonus, design, slot); + DesignChanged.Invoke(DesignChanged.Type.ApplyBonusItem, design, slot); } /// Change whether to apply a specific stain. diff --git a/Glamourer/Events/DesignChanged.cs b/Glamourer/Events/DesignChanged.cs index 1588a96..199c7d4 100644 --- a/Glamourer/Events/DesignChanged.cs +++ b/Glamourer/Events/DesignChanged.cs @@ -62,6 +62,9 @@ public sealed class DesignChanged() /// An existing design had an equipment piece changed. Data is the old value, the new value and the slot [(EquipItem, EquipItem, EquipSlot)]. Equip, + /// An existing design had a bonus item changed. Data is the old value, the new value and the slot [(BonusItem, BonusItem, BonusItemFlag)]. + BonusItem, + /// An existing design had its weapons changed. Data is the old mainhand, the old offhand, the new mainhand, the new offhand (if any) and the new gauntlets (if any). [(EquipItem, EquipItem, EquipItem, EquipItem?, EquipItem?)]. Weapon, @@ -90,7 +93,7 @@ public sealed class DesignChanged() ApplyEquip, /// An existing design changed whether a specific bonus item is applied. Data is the slot of the item [BonusItemFlag]. - ApplyBonus, + ApplyBonusItem, /// An existing design changed whether a specific stain is applied. Data is the slot of the equipment [EquipSlot]. ApplyStain, diff --git a/Glamourer/GameData/CustomizeParameterData.cs b/Glamourer/GameData/CustomizeParameterData.cs index f10289f..09ce65a 100644 --- a/Glamourer/GameData/CustomizeParameterData.cs +++ b/Glamourer/GameData/CustomizeParameterData.cs @@ -12,7 +12,9 @@ public struct CustomizeParameterData public Vector3 HairSpecular; public Vector3 HairHighlight; public Vector3 LeftEye; + public float LeftScleraIntensity; public Vector3 RightEye; + public float RightScleraIntensity; public Vector3 FeatureColor; public float FacePaintUvMultiplier; public float FacePaintUvOffset; @@ -33,7 +35,9 @@ public struct CustomizeParameterData CustomizeParameterFlag.HairSpecular => new CustomizeParameterValue(HairSpecular), CustomizeParameterFlag.HairHighlight => new CustomizeParameterValue(HairHighlight), CustomizeParameterFlag.LeftEye => new CustomizeParameterValue(LeftEye), + CustomizeParameterFlag.LeftScleraIntensity => new CustomizeParameterValue(LeftScleraIntensity), CustomizeParameterFlag.RightEye => new CustomizeParameterValue(RightEye), + CustomizeParameterFlag.RightScleraIntensity => new CustomizeParameterValue(RightScleraIntensity), CustomizeParameterFlag.FeatureColor => new CustomizeParameterValue(FeatureColor), CustomizeParameterFlag.DecalColor => new CustomizeParameterValue(DecalColor), CustomizeParameterFlag.FacePaintUvMultiplier => new CustomizeParameterValue(FacePaintUvMultiplier), @@ -57,7 +61,9 @@ public struct CustomizeParameterData CustomizeParameterFlag.HairSpecular => SetIfDifferent(ref HairSpecular, value.InternalTriple), CustomizeParameterFlag.HairHighlight => SetIfDifferent(ref HairHighlight, value.InternalTriple), CustomizeParameterFlag.LeftEye => SetIfDifferent(ref LeftEye, value.InternalTriple), + CustomizeParameterFlag.LeftScleraIntensity => SetIfDifferent(ref LeftScleraIntensity, value.Single), CustomizeParameterFlag.RightEye => SetIfDifferent(ref RightEye, value.InternalTriple), + CustomizeParameterFlag.RightScleraIntensity => SetIfDifferent(ref RightScleraIntensity, value.Single), CustomizeParameterFlag.FeatureColor => SetIfDifferent(ref FeatureColor, value.InternalTriple), CustomizeParameterFlag.DecalColor => SetIfDifferent(ref DecalColor, value.InternalQuadruple), CustomizeParameterFlag.FacePaintUvMultiplier => SetIfDifferent(ref FacePaintUvMultiplier, value.Single), @@ -67,7 +73,7 @@ public struct CustomizeParameterData } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - public readonly void Apply(ref CustomizeParameter parameters, CustomizeParameterFlag flags = CustomizeParameterExtensions.All) + public readonly unsafe void Apply(ref CustomizeParameter parameters, CustomizeParameterFlag flags = CustomizeParameterExtensions.All) { parameters.SkinColor = (flags & (CustomizeParameterFlag.SkinDiffuse | CustomizeParameterFlag.MuscleTone)) switch { @@ -77,20 +83,20 @@ public struct CustomizeParameterData _ => new CustomizeParameterValue(SkinDiffuse, MuscleTone).XivQuadruple, }; - parameters.LeftColor = (flags & (CustomizeParameterFlag.LeftEye | CustomizeParameterFlag.FacePaintUvMultiplier)) switch + parameters.LeftColor = (flags & (CustomizeParameterFlag.LeftEye | CustomizeParameterFlag.LeftScleraIntensity)) switch { - 0 => parameters.LeftColor, - CustomizeParameterFlag.LeftEye => new CustomizeParameterValue(LeftEye, parameters.LeftColor.W).XivQuadruple, - CustomizeParameterFlag.FacePaintUvMultiplier => parameters.LeftColor with { W = FacePaintUvMultiplier }, - _ => new CustomizeParameterValue(LeftEye, FacePaintUvMultiplier).XivQuadruple, + 0 => parameters.LeftColor, + CustomizeParameterFlag.LeftEye => new CustomizeParameterValue(LeftEye, parameters.LeftColor.W).XivQuadruple, + CustomizeParameterFlag.LeftScleraIntensity => parameters.LeftColor with { W = LeftScleraIntensity }, + _ => new CustomizeParameterValue(LeftEye, LeftScleraIntensity).XivQuadruple, }; - parameters.RightColor = (flags & (CustomizeParameterFlag.RightEye | CustomizeParameterFlag.FacePaintUvOffset)) switch + parameters.RightColor = (flags & (CustomizeParameterFlag.RightEye | CustomizeParameterFlag.RightScleraIntensity)) switch { - 0 => parameters.RightColor, - CustomizeParameterFlag.RightEye => new CustomizeParameterValue(RightEye, parameters.RightColor.W).XivQuadruple, - CustomizeParameterFlag.FacePaintUvOffset => parameters.RightColor with { W = FacePaintUvOffset }, - _ => new CustomizeParameterValue(RightEye, FacePaintUvOffset).XivQuadruple, + 0 => parameters.RightColor, + CustomizeParameterFlag.RightEye => new CustomizeParameterValue(RightEye, parameters.RightColor.W).XivQuadruple, + CustomizeParameterFlag.RightScleraIntensity => parameters.RightColor with { W = RightScleraIntensity }, + _ => new CustomizeParameterValue(RightEye, RightScleraIntensity).XivQuadruple, }; if (flags.HasFlag(CustomizeParameterFlag.SkinSpecular)) @@ -101,6 +107,10 @@ public struct CustomizeParameterData parameters.HairFresnelValue0 = new CustomizeParameterValue(HairSpecular).XivTriple; if (flags.HasFlag(CustomizeParameterFlag.HairHighlight)) parameters.MeshColor = new CustomizeParameterValue(HairHighlight).XivTriple; + if (flags.HasFlag(CustomizeParameterFlag.FacePaintUvMultiplier)) + GetUvMultiplierWrite(ref parameters) = FacePaintUvMultiplier; + if (flags.HasFlag(CustomizeParameterFlag.FacePaintUvOffset)) + GetUvOffsetWrite(ref parameters) = FacePaintUvOffset; if (flags.HasFlag(CustomizeParameterFlag.LipDiffuse)) parameters.LipColor = new CustomizeParameterValue(LipDiffuse).XivQuadruple; if (flags.HasFlag(CustomizeParameterFlag.FeatureColor)) @@ -115,7 +125,7 @@ public struct CustomizeParameterData } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - public readonly void ApplySingle(ref CustomizeParameter parameters, CustomizeParameterFlag flag) + public readonly unsafe void ApplySingle(ref CustomizeParameter parameters, CustomizeParameterFlag flag) { switch (flag) { @@ -150,19 +160,25 @@ public struct CustomizeParameterData parameters.OptionColor = new CustomizeParameterValue(FeatureColor).XivTriple; break; case CustomizeParameterFlag.FacePaintUvMultiplier: - parameters.LeftColor.W = FacePaintUvMultiplier; + GetUvMultiplierWrite(ref parameters) = FacePaintUvMultiplier; break; case CustomizeParameterFlag.FacePaintUvOffset: - parameters.RightColor.W = FacePaintUvOffset; + GetUvOffsetWrite(ref parameters) = FacePaintUvOffset; + break; + case CustomizeParameterFlag.LeftScleraIntensity: + parameters.LeftColor.W = LeftScleraIntensity; + break; + case CustomizeParameterFlag.RightScleraIntensity: + parameters.RightColor.W = RightScleraIntensity; break; } } - public static CustomizeParameterData FromParameters(in CustomizeParameter parameter, in DecalParameters decal) + public static unsafe CustomizeParameterData FromParameters(in CustomizeParameter parameter, in DecalParameters decal) => new() { - FacePaintUvOffset = parameter.RightColor.W, - FacePaintUvMultiplier = parameter.LeftColor.W, + FacePaintUvOffset = GetUvOffset(parameter), + FacePaintUvMultiplier = GetUvMultiplier(parameter), MuscleTone = parameter.SkinColor.W, SkinDiffuse = new CustomizeParameterValue(parameter.SkinColor).InternalTriple, SkinSpecular = new CustomizeParameterValue(parameter.SkinFresnelValue0).InternalTriple, @@ -171,12 +187,14 @@ public struct CustomizeParameterData HairSpecular = new CustomizeParameterValue(parameter.HairFresnelValue0).InternalTriple, HairHighlight = new CustomizeParameterValue(parameter.MeshColor).InternalTriple, LeftEye = new CustomizeParameterValue(parameter.LeftColor).InternalTriple, + LeftScleraIntensity = new CustomizeParameterValue(parameter.LeftColor.W).Single, RightEye = new CustomizeParameterValue(parameter.RightColor).InternalTriple, + RightScleraIntensity = new CustomizeParameterValue(parameter.RightColor.W).Single, FeatureColor = new CustomizeParameterValue(parameter.OptionColor).InternalTriple, DecalColor = FromParameter(decal), }; - public static CustomizeParameterValue FromParameter(in CustomizeParameter parameter, CustomizeParameterFlag flag) + public static unsafe CustomizeParameterValue FromParameter(in CustomizeParameter parameter, CustomizeParameterFlag flag) => flag switch { CustomizeParameterFlag.SkinDiffuse => new CustomizeParameterValue(parameter.SkinColor), @@ -189,8 +207,8 @@ public struct CustomizeParameterData CustomizeParameterFlag.LeftEye => new CustomizeParameterValue(parameter.LeftColor), CustomizeParameterFlag.RightEye => new CustomizeParameterValue(parameter.RightColor), CustomizeParameterFlag.FeatureColor => new CustomizeParameterValue(parameter.OptionColor), - CustomizeParameterFlag.FacePaintUvMultiplier => new CustomizeParameterValue(parameter.LeftColor.W), - CustomizeParameterFlag.FacePaintUvOffset => new CustomizeParameterValue(parameter.RightColor.W), + CustomizeParameterFlag.FacePaintUvMultiplier => new CustomizeParameterValue(GetUvMultiplier(parameter)), + CustomizeParameterFlag.FacePaintUvOffset => new CustomizeParameterValue(GetUvOffset(parameter)), _ => CustomizeParameterValue.Zero, }; @@ -223,4 +241,41 @@ public struct CustomizeParameterData val = @new; return true; } + + + private static unsafe float GetUvOffset(in CustomizeParameter parameter) + { + // TODO CS Update + fixed (CustomizeParameter* ptr = ¶meter) + { + return ((float*)ptr)[23]; + } + } + + private static unsafe ref float GetUvOffsetWrite(ref CustomizeParameter parameter) + { + // TODO CS Update + fixed (CustomizeParameter* ptr = ¶meter) + { + return ref ((float*)ptr)[23]; + } + } + + private static unsafe float GetUvMultiplier(in CustomizeParameter parameter) + { + // TODO CS Update + fixed (CustomizeParameter* ptr = ¶meter) + { + return ((float*)ptr)[15]; + } + } + + private static unsafe ref float GetUvMultiplierWrite(ref CustomizeParameter parameter) + { + // TODO CS Update + fixed (CustomizeParameter* ptr = ¶meter) + { + return ref ((float*)ptr)[15]; + } + } } diff --git a/Glamourer/GameData/CustomizeParameterFlag.cs b/Glamourer/GameData/CustomizeParameterFlag.cs index 59a3511..aabcdae 100644 --- a/Glamourer/GameData/CustomizeParameterFlag.cs +++ b/Glamourer/GameData/CustomizeParameterFlag.cs @@ -16,17 +16,24 @@ public enum CustomizeParameterFlag : ushort FacePaintUvMultiplier = 0x0400, FacePaintUvOffset = 0x0800, DecalColor = 0x1000, + LeftScleraIntensity = 0x2000, + RightScleraIntensity = 0x4000, } public static class CustomizeParameterExtensions { - public const CustomizeParameterFlag All = (CustomizeParameterFlag)0x1FFF; + // Speculars are not available anymore. + public const CustomizeParameterFlag All = (CustomizeParameterFlag)0x1FDB; public const CustomizeParameterFlag RgbTriples = All & ~(RgbaQuadruples | Percentages | Values); public const CustomizeParameterFlag RgbaQuadruples = CustomizeParameterFlag.DecalColor | CustomizeParameterFlag.LipDiffuse; - public const CustomizeParameterFlag Percentages = CustomizeParameterFlag.MuscleTone; + + public const CustomizeParameterFlag Percentages = CustomizeParameterFlag.MuscleTone + | CustomizeParameterFlag.LeftScleraIntensity + | CustomizeParameterFlag.RightScleraIntensity; + public const CustomizeParameterFlag Values = CustomizeParameterFlag.FacePaintUvOffset | CustomizeParameterFlag.FacePaintUvMultiplier; public static readonly IReadOnlyList AllFlags = [.. Enum.GetValues()]; @@ -60,6 +67,8 @@ public static class CustomizeParameterExtensions CustomizeParameterFlag.FacePaintUvMultiplier => "Multiplier for Face Paint", CustomizeParameterFlag.FacePaintUvOffset => "Offset of Face Paint", CustomizeParameterFlag.DecalColor => "Face Paint Color", + CustomizeParameterFlag.LeftScleraIntensity => "Left Sclera Intensity", + CustomizeParameterFlag.RightScleraIntensity => "Right Sclera Intensity", _ => string.Empty, }; } diff --git a/Glamourer/Gui/Equipment/EquipmentDrawer.cs b/Glamourer/Gui/Equipment/EquipmentDrawer.cs index afd3fe5..e65981e 100644 --- a/Glamourer/Gui/Equipment/EquipmentDrawer.cs +++ b/Glamourer/Gui/Equipment/EquipmentDrawer.cs @@ -158,22 +158,22 @@ public class EquipmentDrawer } } - public bool DrawAllStain(out StainId ret, bool locked) + public bool DrawAllStain(out StainIds ret, bool locked) { using var disabled = ImRaii.Disabled(locked); var change = _stainCombo.Draw("Dye All Slots", Stain.None.RgbaColor, string.Empty, false, false, MouseWheelType.None); - ret = Stain.None.RowIndex; + ret = StainIds.None; if (change) if (_stainData.TryGetValue(_stainCombo.CurrentSelection.Key, out var stain)) - ret = stain.RowIndex; + ret = StainIds.All(stain.RowIndex); else if (_stainCombo.CurrentSelection.Key == Stain.None.RowIndex) - ret = Stain.None.RowIndex; + ret = StainIds.None; if (!locked) { if (ImGui.IsItemClicked(ImGuiMouseButton.Right) && _config.DeleteDesignModifier.IsActive()) { - ret = Stain.None.RowIndex; + ret = StainIds.None; change = true; } @@ -420,7 +420,7 @@ public class EquipmentDrawer private void DrawBonusItemNormal(in BonusDrawData bonusDrawData) { - ImGui.Dummy(_iconSize with { Y = ImUtf8.FrameHeight }); + bonusDrawData.CurrentItem.DrawIcon(_textures, _iconSize, bonusDrawData.Slot); var right = ImGui.IsItemClicked(ImGuiMouseButton.Right); var left = ImGui.IsItemClicked(ImGuiMouseButton.Left); ImGui.SameLine(); diff --git a/Glamourer/Gui/Materials/AdvancedDyePopup.cs b/Glamourer/Gui/Materials/AdvancedDyePopup.cs index 3160bcb..173109b 100644 --- a/Glamourer/Gui/Materials/AdvancedDyePopup.cs +++ b/Glamourer/Gui/Materials/AdvancedDyePopup.cs @@ -51,6 +51,8 @@ public sealed unsafe class AdvancedDyePopup( private void DrawButton(MaterialValueIndex index) { + // TODO fix when working + return; if (!config.UseAdvancedDyes) return; @@ -193,11 +195,11 @@ public sealed unsafe class AdvancedDyePopup( DrawWindow(textures); } - private void DrawTable(MaterialValueIndex materialIndex, in LegacyColorTable table) + private void DrawTable(MaterialValueIndex materialIndex, in ColorTable table) { using var disabled = ImRaii.Disabled(_state.IsLocked); _anyChanged = false; - for (byte i = 0; i < LegacyColorTable.NumUsedRows; ++i) + for (byte i = 0; i < ColorTable.NumUsedRows; ++i) { var index = materialIndex with { RowIndex = i }; ref var row = ref table[i]; @@ -208,7 +210,7 @@ public sealed unsafe class AdvancedDyePopup( DrawAllRow(materialIndex, table); } - private void DrawAllRow(MaterialValueIndex materialIndex, in LegacyColorTable table) + private void DrawAllRow(MaterialValueIndex materialIndex, in ColorTable table) { using var id = ImRaii.PushId(100); var buttonSize = new Vector2(ImGui.GetFrameHeight()); @@ -245,11 +247,11 @@ public sealed unsafe class AdvancedDyePopup( ImGui.SameLine(0, spacing); if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.UndoAlt.ToIconString(), buttonSize, "Reset this table to game state.", !_anyChanged, true)) - for (byte i = 0; i < LegacyColorTable.NumUsedRows; ++i) + for (byte i = 0; i < ColorTable.NumUsedRows; ++i) stateManager.ResetMaterialValue(_state, materialIndex with { RowIndex = i }, ApplySettings.Game); } - private void DrawRow(ref LegacyColorTable.Row row, MaterialValueIndex index, in LegacyColorTable table) + private void DrawRow(ref ColorTable.Row row, MaterialValueIndex index, in ColorTable table) { using var id = ImRaii.PushId(index.RowIndex); var changed = _state.Materials.TryGetValue(index, out var value); diff --git a/Glamourer/Gui/Materials/ColorRowClipboard.cs b/Glamourer/Gui/Materials/ColorRowClipboard.cs index 4d99018..4213cc5 100644 --- a/Glamourer/Gui/Materials/ColorRowClipboard.cs +++ b/Glamourer/Gui/Materials/ColorRowClipboard.cs @@ -5,14 +5,14 @@ namespace Glamourer.Gui.Materials; public static class ColorRowClipboard { - private static ColorRow _row; - private static LegacyColorTable _table; + private static ColorRow _row; + private static ColorTable _table; public static bool IsSet { get; private set; } public static bool IsTableSet { get; private set; } - public static LegacyColorTable Table + public static ColorTable Table { get => _table; set diff --git a/Glamourer/Gui/Materials/MaterialDrawer.cs b/Glamourer/Gui/Materials/MaterialDrawer.cs index 26432e9..e7a061f 100644 --- a/Glamourer/Gui/Materials/MaterialDrawer.cs +++ b/Glamourer/Gui/Materials/MaterialDrawer.cs @@ -175,9 +175,9 @@ public class MaterialDrawer(DesignManager _designManager, Configuration _config) { _newRowIdx += 1; ImGui.SetNextItemWidth(ImGui.CalcTextSize("Row #0000").X); - if (ImGui.DragInt("##Row", ref _newRowIdx, 0.01f, 1, LegacyColorTable.NumUsedRows, "Row #%i")) + if (ImGui.DragInt("##Row", ref _newRowIdx, 0.01f, 1, ColorTable.NumUsedRows, "Row #%i")) { - _newRowIdx = Math.Clamp(_newRowIdx, 1, LegacyColorTable.NumUsedRows); + _newRowIdx = Math.Clamp(_newRowIdx, 1, ColorTable.NumUsedRows); _newKey = _newKey with { RowIndex = (byte)(_newRowIdx - 1) }; } diff --git a/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs b/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs index 0a29314..c875cf1 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs @@ -9,6 +9,7 @@ using OtterGui; using OtterGui.Raii; using Penumbra.GameData.Enums; using Penumbra.GameData.Gui.Debug; +using FFXIVClientStructs.FFXIV.Shader; namespace Glamourer.Gui.Tabs.DebugTab; @@ -94,7 +95,8 @@ public class ActiveStatePanel(StateManager _stateManager, ObjectManager _objectM foreach (var type in Enum.GetValues()) { - PrintRow(type.ToDefaultName(), state.BaseData.Customize[type].Value, state.ModelData.Customize[type].Value, state.Sources[type]); + PrintRow(type.ToDefaultName(), state.BaseData.Customize[type].Value, state.ModelData.Customize[type].Value, + state.Sources[type]); ImGui.TableNextRow(); } diff --git a/Glamourer/Gui/Tabs/DebugTab/AdvancedCustomizationDrawer.cs b/Glamourer/Gui/Tabs/DebugTab/AdvancedCustomizationDrawer.cs new file mode 100644 index 0000000..4d6a2cd --- /dev/null +++ b/Glamourer/Gui/Tabs/DebugTab/AdvancedCustomizationDrawer.cs @@ -0,0 +1,135 @@ +using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; +using Glamourer.Interop; +using ImGuiNET; +using OtterGui.Raii; +using OtterGui.Text; +using Penumbra.GameData.Gui.Debug; + +namespace Glamourer.Gui.Tabs.DebugTab; + +public unsafe class AdvancedCustomizationDrawer(ObjectManager objects) : IGameDataDrawer +{ + public string Label + => "Advanced Customizations"; + + public bool Disabled + => false; + + public void Draw() + { + var (player, data) = objects.PlayerData; + if (!data.Valid) + { + ImUtf8.Text("Invalid player."u8); + return; + } + + var model = data.Objects[0].Model; + if (!model.IsHuman) + { + ImUtf8.Text("Invalid model."u8); + return; + } + + DrawCBuffer("Customize"u8, model.AsHuman->CustomizeParameterCBuffer, 0); + DrawCBuffer("Decal"u8, model.AsHuman->DecalColorCBuffer, 1); + DrawCBuffer("Unk1"u8, *(ConstantBuffer**)((byte*)model.AsHuman + 0xBA0), 2); + DrawCBuffer("Unk2"u8, *(ConstantBuffer**)((byte*)model.AsHuman + 0xBA8), 3); + } + + + private static void DrawCBuffer(ReadOnlySpan label, ConstantBuffer* cBuffer, int type) + { + using var tree = ImUtf8.TreeNode(label); + if (!tree) + return; + + if (cBuffer == null) + { + ImUtf8.Text("Invalid CBuffer."u8); + return; + } + + ImUtf8.Text($"{cBuffer->ByteSize / 4}"); + ImUtf8.Text($"{cBuffer->Flags}"); + ImUtf8.Text($"0x{(ulong)cBuffer:X}"); + var parameters = (float*)cBuffer->UnsafeSourcePointer; + if (parameters == null) + { + ImUtf8.Text("No Parameters."u8); + return; + } + + var start = parameters; + using (ImUtf8.Group()) + { + for (var end = start + cBuffer->ByteSize / 4; parameters < end; parameters += 2) + DrawParameters(parameters, type, (int)(parameters - start)); + } + + ImGui.SameLine(0, 50 * ImUtf8.GlobalScale); + parameters = start + 1; + using (ImUtf8.Group()) + { + for (var end = start + cBuffer->ByteSize / 4; parameters < end; parameters += 2) + DrawParameters(parameters, type, (int)(parameters - start)); + } + } + + private static void DrawParameters(float* param, int type, int idx) + { + using var id = ImUtf8.PushId((nint)param); + ImUtf8.TextFrameAligned($"{idx:D2}: "); + ImUtf8.SameLineInner(); + ImGui.SetNextItemWidth(200 * ImUtf8.GlobalScale); + if (TryGetKnown(type, idx, out var known)) + { + ImUtf8.DragScalar(known, ref *param, float.MinValue, float.MaxValue, 0.01f); + } + else + { + using var color = ImRaii.PushColor(ImGuiCol.Text, ImGui.GetColorU32(ImGuiCol.TextDisabled)); + ImUtf8.DragScalar($"+0x{idx * 4:X2}", ref *param, float.MinValue, float.MaxValue, 0.01f); + } + } + + private static bool TryGetKnown(int type, int idx, out ReadOnlySpan text) + { + if (type == 0) + text = idx switch + { + 0 => "Diffuse.R"u8, + 1 => "Diffuse.G"u8, + 2 => "Diffuse.B"u8, + 3 => "Muscle Tone"u8, + 8 => "Lipstick.R"u8, + 9 => "Lipstick.G"u8, + 10 => "Lipstick.B"u8, + 11 => "Lipstick.Opacity"u8, + 12 => "Hair.R"u8, + 13 => "Hair.G"u8, + 14 => "Hair.B"u8, + 15 => "Facepaint.Offset"u8, + 20 => "Highlight.R"u8, + 21 => "Highlight.G"u8, + 22 => "Highlight.B"u8, + 23 => "Facepaint.Multiplier"u8, + 24 => "LeftEye.R"u8, + 25 => "LeftEye.G"u8, + 26 => "LeftEye.B"u8, + 27 => "LeftSclera"u8, + 28 => "RightEye.R"u8, + 29 => "RightEye.G"u8, + 30 => "RightEye.B"u8, + 31 => "RightSclera"u8, + 32 => "Feature.R"u8, + 33 => "Feature.G"u8, + 34 => "Feature.B"u8, + _ => [], + }; + else + text = []; + + return text.Length > 0; + } +} diff --git a/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs b/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs index 3dce124..90282e8 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs @@ -38,7 +38,8 @@ public class DebugTabHeader(string label, params IGameDataDrawer[] subTrees) provider.GetRequiredService(), provider.GetRequiredService(), provider.GetRequiredService(), - provider.GetRequiredService() + provider.GetRequiredService(), + provider.GetRequiredService() ); public static DebugTabHeader CreateGameData(IServiceProvider provider) diff --git a/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs b/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs index 9749ce6..d1624f6 100644 --- a/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs +++ b/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs @@ -1,5 +1,6 @@ using Dalamud.Game.Text.SeStringHandling; using Dalamud.Interface.Utility; +using Glamourer.Designs; using Glamourer.GameData; using Glamourer.Interop; using Glamourer.Services; @@ -12,23 +13,23 @@ using ImGuiClip = OtterGui.ImGuiClip; namespace Glamourer.Gui.Tabs.UnlocksTab; -public class UnlockOverview +public class UnlockOverview( + ItemManager items, + CustomizeService customizations, + ItemUnlockManager itemUnlocks, + CustomizeUnlockManager customizeUnlocks, + PenumbraChangedItemTooltip tooltip, + TextureService textures, + CodeService codes, + JobService jobs, + FavoriteManager favorites) { - private readonly ItemManager _items; - private readonly ItemUnlockManager _itemUnlocks; - private readonly CustomizeService _customizations; - private readonly CustomizeUnlockManager _customizeUnlocks; - private readonly PenumbraChangedItemTooltip _tooltip; - private readonly TextureService _textures; - private readonly CodeService _codes; - private readonly JobService _jobs; - private readonly FavoriteManager _favorites; - private static readonly Vector4 UnavailableTint = new(0.3f, 0.3f, 0.3f, 1.0f); private FullEquipType _selected1 = FullEquipType.Unknown; private SubRace _selected2 = SubRace.Unknown; private Gender _selected3 = Gender.Unknown; + private BonusItemFlag _selected4 = BonusItemFlag.Unknown; private void DrawSelector() { @@ -38,7 +39,7 @@ public class UnlockOverview foreach (var type in Enum.GetValues()) { - if (type.IsOffhandType() || !_items.ItemData.ByType.TryGetValue(type, out var items) || items.Count == 0) + if (type.IsOffhandType() || !items.ItemData.ByType.TryGetValue(type, out var value) || value.Count == 0) continue; if (ImGui.Selectable(type.ToName(), _selected1 == type)) @@ -46,12 +47,21 @@ public class UnlockOverview _selected1 = type; _selected2 = SubRace.Unknown; _selected3 = Gender.Unknown; + _selected4 = BonusItemFlag.Unknown; } } + if (ImGui.Selectable("Bonus Items", _selected4 == BonusItemFlag.Glasses)) + { + _selected1 = FullEquipType.Unknown; + _selected2 = SubRace.Unknown; + _selected3 = Gender.Unknown; + _selected4 = BonusItemFlag.Glasses; + } + foreach (var (clan, gender) in CustomizeManager.AllSets()) { - if (_customizations.Manager.GetSet(clan, gender).HairStyles.Count == 0) + if (customizations.Manager.GetSet(clan, gender).HairStyles.Count == 0) continue; if (ImGui.Selectable($"{(gender is Gender.Male ? '♂' : '♀')} {clan.ToShortName()} Hair & Paint", @@ -60,25 +70,11 @@ public class UnlockOverview _selected1 = FullEquipType.Unknown; _selected2 = clan; _selected3 = gender; + _selected4 = BonusItemFlag.Unknown; } } } - public UnlockOverview(ItemManager items, CustomizeService customizations, ItemUnlockManager itemUnlocks, - CustomizeUnlockManager customizeUnlocks, PenumbraChangedItemTooltip tooltip, TextureService textures, CodeService codes, - JobService jobs, FavoriteManager favorites) - { - _items = items; - _customizations = customizations; - _itemUnlocks = itemUnlocks; - _customizeUnlocks = customizeUnlocks; - _tooltip = tooltip; - _textures = textures; - _codes = codes; - _jobs = jobs; - _favorites = favorites; - } - public void Draw() { using var color = ImRaii.PushColor(ImGuiCol.Border, ImGui.GetColorU32(ImGuiCol.TableBorderStrong)); @@ -97,11 +93,13 @@ public class UnlockOverview DrawItems(); else if (_selected2 is not SubRace.Unknown && _selected3 is not Gender.Unknown) DrawCustomizations(); + else if (_selected4 is not BonusItemFlag.Unknown) + DrawBonusItems(); } private void DrawCustomizations() { - var set = _customizations.Manager.GetSet(_selected2, _selected3); + var set = customizations.Manager.GetSet(_selected2, _selected3); var spacing = IconSpacing; using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing); @@ -111,16 +109,16 @@ public class UnlockOverview var counter = 0; foreach (var customize in set.HairStyles.Concat(set.FacePaints)) { - if (!_customizeUnlocks.Unlockable.TryGetValue(customize, out var unlockData)) + if (!customizeUnlocks.Unlockable.TryGetValue(customize, out var unlockData)) continue; - var unlocked = _customizeUnlocks.IsUnlocked(customize, out var time); - var icon = _customizations.Manager.GetIcon(customize.IconId); + var unlocked = customizeUnlocks.IsUnlocked(customize, out var time); + var icon = customizations.Manager.GetIcon(customize.IconId); 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); + unlocked || codes.Enabled(CodeService.CodeFlag.Shirts) ? Vector4.One : UnavailableTint); - if (_favorites.Contains(_selected3, _selected2, customize.Index, customize.Value)) + 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); @@ -147,24 +145,90 @@ public class UnlockOverview } } + private void DrawBonusItems() + { + var spacing = IconSpacing; + using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing); + var iconSize = ImGuiHelpers.ScaledVector2(64); + var iconsPerRow = IconsPerRow(iconSize.X, spacing.X); + var numRows = (items.DictBonusItems.Count + iconsPerRow - 1) / iconsPerRow; + var numVisibleRows = (int)(Math.Ceiling(ImGui.GetContentRegionAvail().Y / (iconSize.Y + spacing.Y)) + 0.5f) + 1; + + var skips = ImGuiClip.GetNecessarySkips(iconSize.Y + spacing.Y); + var start = skips * iconsPerRow; + var end = Math.Min(numVisibleRows * iconsPerRow + skips * iconsPerRow, items.DictBonusItems.Count); + var counter = 0; + + foreach (var item in items.DictBonusItems.Values.Skip(start).Take(end - start)) + { + DrawItem(item); + if (counter != iconsPerRow - 1) + { + ImGui.SameLine(); + ++counter; + } + else + { + counter = 0; + } + } + + if (ImGui.GetCursorPosX() != 0) + ImGui.NewLine(); + var remainder = numRows - numVisibleRows - skips; + if (remainder > 0) + ImGuiClip.DrawEndDummy(remainder, iconSize.Y + spacing.Y); + + void DrawItem(BonusItem item) + { + // TODO check unlocks + var unlocked = true; + if (!textures.TryLoadIcon(item.Icon.Id, out var iconHandle)) + return; + + 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); + if (favorites.Contains(item)) + ImGui.GetWindowDrawList().AddRect(ImGui.GetItemRectMin(), ImGui.GetItemRectMax(), ColorId.FavoriteStarOn.Value(), + 2 * ImGuiHelpers.GlobalScale, ImDrawFlags.RoundCornersAll, 4 * ImGuiHelpers.GlobalScale); + + // TODO handle clicking + if (ImGui.IsItemHovered()) + { + using var tt = ImRaii.Tooltip(); + if (size.X >= iconSize.X && size.Y >= iconSize.Y) + ImGui.Image(icon, size); + ImGui.TextUnformatted(item.Name); + ImGui.TextUnformatted($"{item.Slot.ToName()}"); + ImGui.TextUnformatted($"{item.ModelId.Id}-{item.Variant.Id}"); + // TODO + ImGui.TextUnformatted("Always Unlocked"); // : $"Unlocked on {time:g}" : "Not Unlocked."); + // TODO + //tooltip.CreateTooltip(item, string.Empty, false); + } + } + } + private void DrawItems() { - if (!_items.ItemData.ByType.TryGetValue(_selected1, out var items)) + if (!items.ItemData.ByType.TryGetValue(_selected1, out var value)) return; var spacing = IconSpacing; using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing); var iconSize = ImGuiHelpers.ScaledVector2(64); var iconsPerRow = IconsPerRow(iconSize.X, spacing.X); - var numRows = (items.Count + iconsPerRow - 1) / iconsPerRow; + var numRows = (value.Count + iconsPerRow - 1) / iconsPerRow; var numVisibleRows = (int)(Math.Ceiling(ImGui.GetContentRegionAvail().Y / (iconSize.Y + spacing.Y)) + 0.5f) + 1; var skips = ImGuiClip.GetNecessarySkips(iconSize.Y + spacing.Y); - var end = Math.Min(numVisibleRows * iconsPerRow + skips * iconsPerRow, items.Count); + var end = Math.Min(numVisibleRows * iconsPerRow + skips * iconsPerRow, value.Count); var counter = 0; for (var idx = skips * iconsPerRow; idx < end; ++idx) { - DrawItem(items[idx]); + DrawItem(value[idx]); if (counter != iconsPerRow - 1) { ImGui.SameLine(); @@ -185,23 +249,23 @@ public class UnlockOverview void DrawItem(EquipItem item) { - var unlocked = _itemUnlocks.IsUnlocked(item.Id, out var time); - if (!_textures.TryLoadIcon(item.IconId.Id, out var iconHandle)) + var unlocked = itemUnlocks.IsUnlocked(item.Id, out var time); + if (!textures.TryLoadIcon(item.IconId.Id, out var iconHandle)) return; 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); - if (_favorites.Contains(item)) + 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); if (ImGui.IsItemClicked()) Glamourer.Messager.Chat.Print(new SeStringBuilder().AddItemLink(item.ItemId.Id, false).BuiltString); - if (ImGui.IsItemClicked(ImGuiMouseButton.Right) && _tooltip.Player(out var state)) - _tooltip.ApplyItem(state, item); + if (ImGui.IsItemClicked(ImGuiMouseButton.Right) && tooltip.Player(out var state)) + tooltip.ApplyItem(state, item); if (ImGui.IsItemHovered()) { @@ -213,7 +277,7 @@ public class UnlockOverview ImGui.TextUnformatted($"{item.Type.ToName()} ({slot.ToName()})"); if (item.Type.ValidOffhand().IsOffhandType()) ImGui.TextUnformatted( - $"{item.Weapon()}{(_items.ItemData.TryGetValue(item.ItemId, EquipSlot.OffHand, out var offhand) ? $" | {offhand.Weapon()}" : string.Empty)}"); + $"{item.Weapon()}{(items.ItemData.TryGetValue(item.ItemId, EquipSlot.OffHand, out var offhand) ? $" | {offhand.Weapon()}" : string.Empty)}"); else ImGui.TextUnformatted(slot is EquipSlot.MainHand ? $"{item.Weapon()}" : $"{item.Armor()}"); ImGui.TextUnformatted( @@ -221,17 +285,17 @@ public class UnlockOverview if (item.Level.Value <= 1) { - if (item.JobRestrictions.Id <= 1 || item.JobRestrictions.Id >= _jobs.AllJobGroups.Count) + if (item.JobRestrictions.Id <= 1 || item.JobRestrictions.Id >= jobs.AllJobGroups.Count) ImGui.TextUnformatted("For Everyone"); else - ImGui.TextUnformatted($"For all {_jobs.AllJobGroups[item.JobRestrictions.Id].Name}"); + ImGui.TextUnformatted($"For all {jobs.AllJobGroups[item.JobRestrictions.Id].Name}"); } else { - if (item.JobRestrictions.Id <= 1 || item.JobRestrictions.Id >= _jobs.AllJobGroups.Count) + if (item.JobRestrictions.Id <= 1 || item.JobRestrictions.Id >= jobs.AllJobGroups.Count) ImGui.TextUnformatted($"For Everyone of at least Level {item.Level}"); else - ImGui.TextUnformatted($"For all {_jobs.AllJobGroups[item.JobRestrictions.Id].Name} of at least Level {item.Level}"); + ImGui.TextUnformatted($"For all {jobs.AllJobGroups[item.JobRestrictions.Id].Name} of at least Level {item.Level}"); } if (item.Flags.HasFlag(ItemFlags.IsDyable1)) @@ -240,7 +304,7 @@ public class UnlockOverview ImGui.TextUnformatted("Tradable"); if (item.Flags.HasFlag(ItemFlags.IsCrestWorthy)) ImGui.TextUnformatted("Can apply Crest"); - _tooltip.CreateTooltip(item, string.Empty, false); + tooltip.CreateTooltip(item, string.Empty, false); } } } diff --git a/Glamourer/Gui/UiHelpers.cs b/Glamourer/Gui/UiHelpers.cs index 88f51f5..8499728 100644 --- a/Glamourer/Gui/UiHelpers.cs +++ b/Glamourer/Gui/UiHelpers.cs @@ -29,8 +29,30 @@ public static class UiHelpers if (empty) { var (bgColor, tint) = isEmpty - ? (ImGui.GetColorU32(ImGuiCol.FrameBg), new Vector4(0.1f, 0.1f, 0.1f, 0.5f)) - : (ImGui.GetColorU32(ImGuiCol.FrameBgActive), new Vector4(0.3f, 0.3f, 0.3f, 0.8f)); + ? (ImGui.GetColorU32(ImGuiCol.FrameBg), Vector4.One) + : (ImGui.GetColorU32(ImGuiCol.FrameBgActive), new Vector4(0.3f, 0.3f, 0.3f, 1f)); + var pos = ImGui.GetCursorScreenPos(); + ImGui.GetWindowDrawList().AddRectFilled(pos, pos + size, bgColor, 5 * ImGuiHelpers.GlobalScale); + if (ptr != nint.Zero) + ImGui.Image(ptr, size, Vector2.Zero, Vector2.One, tint); + else + ImGui.Dummy(size); + } + else + { + ImGuiUtil.HoverIcon(ptr, textureSize, size); + } + } + + public static void DrawIcon(this BonusItem item, TextureService textures, Vector2 size, BonusItemFlag slot) + { + var isEmpty = item.ModelId.Id == 0; + var (ptr, textureSize, empty) = textures.GetIcon(item, slot); + if (empty) + { + var (bgColor, tint) = isEmpty + ? (ImGui.GetColorU32(ImGuiCol.FrameBg), Vector4.One) + : (ImGui.GetColorU32(ImGuiCol.FrameBgActive), new Vector4(0.3f, 0.3f, 0.3f, 1f)); var pos = ImGui.GetCursorScreenPos(); ImGui.GetWindowDrawList().AddRectFilled(pos, pos + size, bgColor, 5 * ImGuiHelpers.GlobalScale); if (ptr != nint.Zero) diff --git a/Glamourer/Interop/Material/DirectXService.cs b/Glamourer/Interop/Material/DirectXService.cs index 6d9c71b..3316491 100644 --- a/Glamourer/Interop/Material/DirectXService.cs +++ b/Glamourer/Interop/Material/DirectXService.cs @@ -15,13 +15,13 @@ namespace Glamourer.Interop.Material; public unsafe class DirectXService(IFramework framework) : IService { private readonly object _lock = new(); - private readonly ConcurrentDictionary _textures = []; + private readonly ConcurrentDictionary _textures = []; /// Generate a color table the way the game does inside the original texture, and release the original. /// The original texture that will be replaced with a new one. /// The input color table. /// Success or failure. - public bool ReplaceColorTable(Texture** original, in LegacyColorTable colorTable) + public bool ReplaceColorTable(Texture** original, in ColorTable colorTable) { if (original == null) return false; @@ -38,7 +38,7 @@ public unsafe class DirectXService(IFramework framework) : IService if (texture.IsInvalid) return false; - fixed (LegacyColorTable* ptr = &colorTable) + fixed (ColorTable* ptr = &colorTable) { if (!texture.Texture->InitializeContents(ptr)) return false; @@ -51,7 +51,7 @@ public unsafe class DirectXService(IFramework framework) : IService return true; } - public bool TryGetColorTable(Texture* texture, out LegacyColorTable table) + public bool TryGetColorTable(Texture* texture, out ColorTable table) { if (_textures.TryGetValue((nint)texture, out var p) && framework.LastUpdateUTC == p.Update) { @@ -73,7 +73,7 @@ public unsafe class DirectXService(IFramework framework) : IService /// A pointer to the internal texture struct containing the GPU handle. /// The returned color table. /// Whether the table could be fetched. - private static bool TextureColorTable(Texture* texture, out LegacyColorTable table) + private static bool TextureColorTable(Texture* texture, out ColorTable table) { if (texture == null) { @@ -114,7 +114,7 @@ public unsafe class DirectXService(IFramework framework) : IService } /// Turn a mapped texture into a color table. - private static LegacyColorTable GetTextureData(ID3D11Texture2D1 resource, MappedSubresource map) + private static ColorTable GetTextureData(ID3D11Texture2D1 resource, MappedSubresource map) { var desc = resource.Description1; @@ -133,14 +133,14 @@ public unsafe class DirectXService(IFramework framework) : IService /// The height of the texture. (Needs to be 16). /// The stride in the texture data. /// - private static LegacyColorTable ReadTexture(nint data, int length, int height, int pitch) + private static ColorTable ReadTexture(nint data, int length, int height, int pitch) { // Check that the data has sufficient dimension and size. var expectedSize = sizeof(Half) * MaterialService.TextureWidth * height * 4; - if (length < expectedSize || sizeof(LegacyColorTable) != expectedSize || height != MaterialService.TextureHeight) + if (length < expectedSize || sizeof(ColorTable) != expectedSize || height != MaterialService.TextureHeight) return default; - var ret = new LegacyColorTable(); + var ret = new ColorTable(); var target = (byte*)&ret; // If the stride is the same as in the table, just copy. if (pitch == MaterialService.TextureWidth) diff --git a/Glamourer/Interop/Material/LiveColorTablePreviewer.cs b/Glamourer/Interop/Material/LiveColorTablePreviewer.cs index aa4c358..a9f3a74 100644 --- a/Glamourer/Interop/Material/LiveColorTablePreviewer.cs +++ b/Glamourer/Interop/Material/LiveColorTablePreviewer.cs @@ -13,11 +13,11 @@ public sealed unsafe class LiveColorTablePreviewer : IService, IDisposable private readonly DirectXService _directXService; public MaterialValueIndex LastValueIndex { get; private set; } = MaterialValueIndex.Invalid; - public LegacyColorTable LastOriginalColorTable { get; private set; } + public ColorTable LastOriginalColorTable { get; private set; } private MaterialValueIndex _valueIndex = MaterialValueIndex.Invalid; private ObjectIndex _lastObjectIndex = ObjectIndex.AnyIndex; private ObjectIndex _objectIndex = ObjectIndex.AnyIndex; - private LegacyColorTable _originalColorTable; + private ColorTable _originalColorTable; public LiveColorTablePreviewer(global::Penumbra.GameData.Interop.ObjectManager objects, IFramework framework, DirectXService directXService) { @@ -78,7 +78,7 @@ public sealed unsafe class LiveColorTablePreviewer : IService, IDisposable } else { - for (var i = 0; i < LegacyColorTable.NumUsedRows; ++i) + for (var i = 0; i < ColorTable.NumUsedRows; ++i) { table[i].Diffuse = diffuse; table[i].Emissive = emissive; @@ -92,7 +92,7 @@ public sealed unsafe class LiveColorTablePreviewer : IService, IDisposable _objectIndex = ObjectIndex.AnyIndex; } - public void OnHover(MaterialValueIndex index, ObjectIndex objectIndex, LegacyColorTable table) + public void OnHover(MaterialValueIndex index, ObjectIndex objectIndex, ColorTable table) { if (_valueIndex.DrawObject is not MaterialValueIndex.DrawObjectType.Invalid) return; diff --git a/Glamourer/Interop/Material/MaterialManager.cs b/Glamourer/Interop/Material/MaterialManager.cs index 3e984c8..5f2b553 100644 --- a/Glamourer/Interop/Material/MaterialManager.cs +++ b/Glamourer/Interop/Material/MaterialManager.cs @@ -40,6 +40,8 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable private void OnPrepareColorSet(CharacterBase* characterBase, MaterialResourceHandle* material, ref StainId stain, ref nint ret) { + // TODO fix when working + return; if (!_config.UseAdvancedDyes) return; @@ -76,7 +78,7 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable /// Update and apply the glamourer state of an actor according to the application sources when updated by the game. private void UpdateMaterialValues(ActorState state, ReadOnlySpan<(uint Key, MaterialValueState Value)> values, CharacterWeapon drawData, - ref LegacyColorTable colorTable) + ref ColorTable colorTable) { var deleteList = _deleteList.Value!; deleteList.Clear(); diff --git a/Glamourer/Interop/Material/MaterialService.cs b/Glamourer/Interop/Material/MaterialService.cs index 4c8706c..b2626be 100644 --- a/Glamourer/Interop/Material/MaterialService.cs +++ b/Glamourer/Interop/Material/MaterialService.cs @@ -9,11 +9,11 @@ namespace Glamourer.Interop.Material; public static unsafe class MaterialService { - public const int TextureWidth = 4; - public const int TextureHeight = LegacyColorTable.NumUsedRows; - public const int MaterialsPerModel = 4; + public const int TextureWidth = 8; + public const int TextureHeight = ColorTable.NumUsedRows; + public const int MaterialsPerModel = 10; - public static bool GenerateNewColorTable(in LegacyColorTable colorTable, out Texture* texture) + public static bool GenerateNewColorTable(in ColorTable colorTable, out Texture* texture) { var textureSize = stackalloc int[2]; textureSize[0] = TextureWidth; @@ -24,7 +24,7 @@ public static unsafe class MaterialService if (texture == null) return false; - fixed (LegacyColorTable* ptr = &colorTable) + fixed (ColorTable* ptr = &colorTable) { return texture->InitializeContents(ptr); } @@ -53,7 +53,7 @@ public static unsafe class MaterialService /// The model slot. /// The material slot in the model. /// A pointer to the color table or null. - public static LegacyColorTable* GetMaterialColorTable(Model model, int modelSlot, byte materialSlot) + public static ColorTable* GetMaterialColorTable(Model model, int modelSlot, byte materialSlot) { if (!model.IsCharacterBase) return null; @@ -66,6 +66,6 @@ public static unsafe class MaterialService if (material == null || material->ColorTable == null) return null; - return (LegacyColorTable*)material->ColorTable; + return (ColorTable*)material->ColorTable; } } diff --git a/Glamourer/Interop/Material/MaterialValueIndex.cs b/Glamourer/Interop/Material/MaterialValueIndex.cs index 254675e..8101c05 100644 --- a/Glamourer/Interop/Material/MaterialValueIndex.cs +++ b/Glamourer/Interop/Material/MaterialValueIndex.cs @@ -149,7 +149,7 @@ public readonly record struct MaterialValueIndex( => materialIndex < MaterialService.MaterialsPerModel; public static bool ValidateRow(byte rowIndex) - => rowIndex < LegacyColorTable.NumUsedRows; + => rowIndex < ColorTable.NumUsedRows; private static uint ToKey(DrawObjectType type, byte slotIndex, byte materialIndex, byte rowIndex) { diff --git a/Glamourer/Interop/Material/MaterialValueManager.cs b/Glamourer/Interop/Material/MaterialValueManager.cs index ae08c71..897f1bf 100644 --- a/Glamourer/Interop/Material/MaterialValueManager.cs +++ b/Glamourer/Interop/Material/MaterialValueManager.cs @@ -21,7 +21,7 @@ public struct ColorRow(Vector3 diffuse, Vector3 specular, Vector3 emissive, floa public float SpecularStrength = specularStrength; public float GlossStrength = glossStrength; - public ColorRow(in LegacyColorTable.Row row) + public ColorRow(in ColorTable.Row row) : this(Root(row.Diffuse), Root(row.Specular), Root(row.Emissive), row.SpecularStrength, row.GlossStrength) { } @@ -44,7 +44,7 @@ public struct ColorRow(Vector3 diffuse, Vector3 specular, Vector3 emissive, floa private static float Root(float value) => value < 0 ? MathF.Sqrt(-value) : MathF.Sqrt(value); - public readonly bool Apply(ref LegacyColorTable.Row row) + public readonly bool Apply(ref ColorTable.Row row) { var ret = false; var d = Square(Diffuse); diff --git a/Glamourer/Interop/Material/PrepareColorSet.cs b/Glamourer/Interop/Material/PrepareColorSet.cs index 3866d74..cdbff11 100644 --- a/Glamourer/Interop/Material/PrepareColorSet.cs +++ b/Glamourer/Interop/Material/PrepareColorSet.cs @@ -56,7 +56,7 @@ public sealed unsafe class PrepareColorSet } public static bool TryGetColorTable(CharacterBase* characterBase, MaterialResourceHandle* material, StainIds stainIds, - out LegacyColorTable table) + out ColorTable table) { if (material->ColorTable == null) { @@ -64,7 +64,7 @@ public sealed unsafe class PrepareColorSet return false; } - var newTable = *(LegacyColorTable*)material->ColorTable; + var newTable = *(ColorTable*)material->ColorTable; // TODO //if (stainIds.Stain1.Id != 0 || stainIds.Stain2.Id != 0) // characterBase->ReadStainingTemplate(material, stainId.Id, (Half*)(&newTable)); @@ -73,7 +73,7 @@ public sealed unsafe class PrepareColorSet } /// Assumes the actor is valid. - public static bool TryGetColorTable(Actor actor, MaterialValueIndex index, out LegacyColorTable table) + public static bool TryGetColorTable(Actor actor, MaterialValueIndex index, out ColorTable table) { var idx = index.SlotIndex * MaterialService.MaterialsPerModel + index.MaterialIndex; if (!index.TryGetModel(actor, out var model)) diff --git a/Glamourer/Interop/WeaponService.cs b/Glamourer/Interop/WeaponService.cs index 5ab2a40..b0bdd19 100644 --- a/Glamourer/Interop/WeaponService.cs +++ b/Glamourer/Interop/WeaponService.cs @@ -13,7 +13,6 @@ public unsafe class WeaponService : IDisposable private readonly WeaponLoading _event; private readonly ThreadLocal _inUpdate = new(() => false); - private readonly delegate* unmanaged[Stdcall] _original; diff --git a/Glamourer/Services/TextureService.cs b/Glamourer/Services/TextureService.cs index 99436e4..e08ab44 100644 --- a/Glamourer/Services/TextureService.cs +++ b/Glamourer/Services/TextureService.cs @@ -23,6 +23,21 @@ public sealed class TextureService(IUiBuilder uiBuilder, IDataManager dataManage : (nint.Zero, Vector2.Zero, true); } + public (nint, Vector2, bool) GetIcon(BonusItem item, BonusItemFlag slot) + { + if (item.Icon.Id != 0 && TryLoadIcon(item.Icon.Id, out var ret)) + return (ret.ImGuiHandle, new Vector2(ret.Width, ret.Height), false); + + var idx = slot.ToIndex(); + if (idx == uint.MaxValue) + return (nint.Zero, Vector2.Zero, true); + + idx += 12; + return idx < 13 && _slotIcons[idx] != null + ? (_slotIcons[idx]!.ImGuiHandle, new Vector2(_slotIcons[idx]!.Width, _slotIcons[idx]!.Height), true) + : (nint.Zero, Vector2.Zero, true); + } + public void Dispose() { for (var i = 0; i < _slotIcons.Length; ++i) @@ -34,9 +49,9 @@ public sealed class TextureService(IUiBuilder uiBuilder, IDataManager dataManage private static IDalamudTextureWrap?[] CreateSlotIcons(IUiBuilder uiBuilder) { - var ret = new IDalamudTextureWrap?[12]; + var ret = new IDalamudTextureWrap?[13]; - using var uldWrapper = uiBuilder.LoadUld("ui/uld/ArmouryBoard.uld"); + using var uldWrapper = uiBuilder.LoadUld("ui/uld/Character.uld"); if (!uldWrapper.Valid) { @@ -44,33 +59,37 @@ public sealed class TextureService(IUiBuilder uiBuilder, IDataManager dataManage return ret; } - SetIcon(EquipSlot.Head, 1); - SetIcon(EquipSlot.Body, 2); - SetIcon(EquipSlot.Hands, 3); - SetIcon(EquipSlot.Legs, 5); - SetIcon(EquipSlot.Feet, 6); - SetIcon(EquipSlot.Ears, 8); - SetIcon(EquipSlot.Neck, 9); - SetIcon(EquipSlot.Wrists, 10); - SetIcon(EquipSlot.RFinger, 11); - SetIcon(EquipSlot.MainHand, 0); - SetIcon(EquipSlot.OffHand, 7); + SetIcon(EquipSlot.Head, 19); + SetIcon(EquipSlot.Body, 20); + SetIcon(EquipSlot.Hands, 21); + SetIcon(EquipSlot.Legs, 23); + SetIcon(EquipSlot.Feet, 24); + SetIcon(EquipSlot.Ears, 25); + SetIcon(EquipSlot.Neck, 26); + SetIcon(EquipSlot.Wrists, 27); + SetIcon(EquipSlot.RFinger, 28); + SetIcon(EquipSlot.MainHand, 17); + SetIcon(EquipSlot.OffHand, 18); + Set(BonusItemFlag.Glasses.ToName(), (int) BonusItemFlag.Glasses.ToIndex() + 12, 55); ret[EquipSlot.LFinger.ToIndex()] = ret[EquipSlot.RFinger.ToIndex()]; return ret; - void SetIcon(EquipSlot slot, int index) + void Set(string name, int slot, int index) { try { - ret[slot.ToIndex()] = uldWrapper.LoadTexturePart("ui/uld/ArmouryBoard_hr1.tex", index)!; + ret[slot] = uldWrapper.LoadTexturePart("ui/uld/Character_hr1.tex", index)!; } catch (Exception ex) { - Glamourer.Log.Error($"Could not get empty slot texture for {slot.ToName()}, icon will be left empty. " + Glamourer.Log.Error($"Could not get empty slot texture for {name}, icon will be left empty. " + $"This may be because of incompatible mods affecting your character screen interface:\n{ex}"); - ret[slot.ToIndex()] = null; + ret[slot] = null; } } + + void SetIcon(EquipSlot slot, int index) + => Set(slot.ToName(), (int)slot.ToIndex(), index); } } diff --git a/Glamourer/State/StateApplier.cs b/Glamourer/State/StateApplier.cs index a4a1df4..2af6437 100644 --- a/Glamourer/State/StateApplier.cs +++ b/Glamourer/State/StateApplier.cs @@ -306,6 +306,8 @@ public class StateApplier( public unsafe void ChangeMaterialValue(ActorData data, MaterialValueIndex index, ColorRow? value, bool force) { + // TODO fix when working + return; if (!force && !_config.UseAdvancedDyes) return; @@ -338,6 +340,8 @@ public class StateApplier( public unsafe void ChangeMaterialValues(ActorData data, in StateMaterialManager materials, bool force) { + // TODO: fix when working + return; if (!force && !_config.UseAdvancedDyes) return; @@ -383,6 +387,11 @@ public class StateApplier( ChangeCustomize(actors, state.ModelData.Customize); foreach (var slot in EquipSlotExtensions.EqdpSlots) ChangeArmor(actors, slot, state.ModelData.Armor(slot), !state.Sources[slot, false].IsIpc(), state.ModelData.IsHatVisible()); + foreach (var slot in BonusExtensions.AllFlags) + { + var item = state.ModelData.BonusItem(slot); + ChangeBonusItem(actors, slot, item.ModelId, item.Variant); + } var mainhandActors = state.ModelData.MainhandType != state.BaseData.MainhandType ? actors.OnlyGPose() : actors; ChangeMainhand(mainhandActors, state.ModelData.Item(EquipSlot.MainHand), state.ModelData.Stain(EquipSlot.MainHand)); diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index c39fd31..c627564 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -175,7 +175,7 @@ public class StateEditor( var @new = state.ModelData.Parameters[flag]; var actors = Applier.ChangeParameters(state, flag, settings.Source.RequiresChange()); Glamourer.Log.Verbose( - $"Set {flag} crest in state {state.Identifier.Incognito(null)} from {old} to {@new}. [Affecting {actors.ToLazyString("nothing")}.]"); + $"Set {flag} in state {state.Identifier.Incognito(null)} from {old} to {@new}. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.Parameter, settings.Source, state, actors, (old, @new, flag)); } diff --git a/Glamourer/State/StateIndex.cs b/Glamourer/State/StateIndex.cs index a569499..e1c4ea0 100644 --- a/Glamourer/State/StateIndex.cs +++ b/Glamourer/State/StateIndex.cs @@ -110,7 +110,9 @@ public readonly record struct StateIndex(int Value) : IEqualityOperators new StateIndex(ParamHairSpecular), CustomizeParameterFlag.HairHighlight => new StateIndex(ParamHairHighlight), CustomizeParameterFlag.LeftEye => new StateIndex(ParamLeftEye), + CustomizeParameterFlag.LeftScleraIntensity => new StateIndex(ParamLeftScleraIntensity), CustomizeParameterFlag.RightEye => new StateIndex(ParamRightEye), + CustomizeParameterFlag.RightScleraIntensity => new StateIndex(ParamRightScleraIntensity), CustomizeParameterFlag.FeatureColor => new StateIndex(ParamFeatureColor), CustomizeParameterFlag.FacePaintUvMultiplier => new StateIndex(ParamFacePaintUvMultiplier), CustomizeParameterFlag.FacePaintUvOffset => new StateIndex(ParamFacePaintUvOffset), @@ -199,8 +201,10 @@ public readonly record struct StateIndex(int Value) : IEqualityOperators CustomizeParameterFlag.HairSpecular, ParamHairHighlight => CustomizeParameterFlag.HairHighlight, ParamLeftEye => CustomizeParameterFlag.LeftEye, + ParamLeftScleraIntensity => CustomizeParameterFlag.LeftScleraIntensity, ParamRightEye => CustomizeParameterFlag.RightEye, + ParamRightScleraIntensity => CustomizeParameterFlag.RightScleraIntensity, ParamFeatureColor => CustomizeParameterFlag.FeatureColor, ParamFacePaintUvMultiplier => CustomizeParameterFlag.FacePaintUvMultiplier, ParamFacePaintUvOffset => CustomizeParameterFlag.FacePaintUvOffset, @@ -320,103 +326,6 @@ public readonly record struct StateIndex(int Value) : IEqualityOperators -1, }; - public object? GetValue(in DesignData data) - { - return Value switch - { - EquipHead => data.Item(EquipSlot.Head), - EquipBody => data.Item(EquipSlot.Body), - EquipHands => data.Item(EquipSlot.Hands), - EquipLegs => data.Item(EquipSlot.Legs), - EquipFeet => data.Item(EquipSlot.Feet), - EquipEars => data.Item(EquipSlot.Ears), - EquipNeck => data.Item(EquipSlot.Neck), - EquipWrist => data.Item(EquipSlot.Wrists), - EquipRFinger => data.Item(EquipSlot.RFinger), - EquipLFinger => data.Item(EquipSlot.LFinger), - EquipMainhand => data.Item(EquipSlot.MainHand), - EquipOffhand => data.Item(EquipSlot.OffHand), - - StainHead => data.Stain(EquipSlot.Head), - StainBody => data.Stain(EquipSlot.Body), - StainHands => data.Stain(EquipSlot.Hands), - StainLegs => data.Stain(EquipSlot.Legs), - StainFeet => data.Stain(EquipSlot.Feet), - StainEars => data.Stain(EquipSlot.Ears), - StainNeck => data.Stain(EquipSlot.Neck), - StainWrist => data.Stain(EquipSlot.Wrists), - StainRFinger => data.Stain(EquipSlot.RFinger), - StainLFinger => data.Stain(EquipSlot.LFinger), - StainMainhand => data.Stain(EquipSlot.MainHand), - StainOffhand => data.Stain(EquipSlot.OffHand), - - CustomizeRace => data.Customize[CustomizeIndex.Race], - CustomizeGender => data.Customize[CustomizeIndex.Gender], - CustomizeBodyType => data.Customize[CustomizeIndex.BodyType], - CustomizeHeight => data.Customize[CustomizeIndex.Height], - CustomizeClan => data.Customize[CustomizeIndex.Clan], - CustomizeFace => data.Customize[CustomizeIndex.Face], - CustomizeHairstyle => data.Customize[CustomizeIndex.Hairstyle], - CustomizeHighlights => data.Customize[CustomizeIndex.Highlights], - CustomizeSkinColor => data.Customize[CustomizeIndex.SkinColor], - CustomizeEyeColorRight => data.Customize[CustomizeIndex.EyeColorRight], - CustomizeHairColor => data.Customize[CustomizeIndex.HairColor], - CustomizeHighlightsColor => data.Customize[CustomizeIndex.HighlightsColor], - CustomizeFacialFeature1 => data.Customize[CustomizeIndex.FacialFeature1], - CustomizeFacialFeature2 => data.Customize[CustomizeIndex.FacialFeature2], - CustomizeFacialFeature3 => data.Customize[CustomizeIndex.FacialFeature3], - CustomizeFacialFeature4 => data.Customize[CustomizeIndex.FacialFeature4], - CustomizeFacialFeature5 => data.Customize[CustomizeIndex.FacialFeature5], - CustomizeFacialFeature6 => data.Customize[CustomizeIndex.FacialFeature6], - CustomizeFacialFeature7 => data.Customize[CustomizeIndex.FacialFeature7], - CustomizeLegacyTattoo => data.Customize[CustomizeIndex.LegacyTattoo], - CustomizeTattooColor => data.Customize[CustomizeIndex.TattooColor], - CustomizeEyebrows => data.Customize[CustomizeIndex.Eyebrows], - CustomizeEyeColorLeft => data.Customize[CustomizeIndex.EyeColorLeft], - CustomizeEyeShape => data.Customize[CustomizeIndex.EyeShape], - CustomizeSmallIris => data.Customize[CustomizeIndex.SmallIris], - CustomizeNose => data.Customize[CustomizeIndex.Nose], - CustomizeJaw => data.Customize[CustomizeIndex.Jaw], - CustomizeMouth => data.Customize[CustomizeIndex.Mouth], - CustomizeLipstick => data.Customize[CustomizeIndex.Lipstick], - CustomizeLipColor => data.Customize[CustomizeIndex.LipColor], - CustomizeMuscleMass => data.Customize[CustomizeIndex.MuscleMass], - CustomizeTailShape => data.Customize[CustomizeIndex.TailShape], - CustomizeBustSize => data.Customize[CustomizeIndex.BustSize], - CustomizeFacePaint => data.Customize[CustomizeIndex.FacePaint], - CustomizeFacePaintReversed => data.Customize[CustomizeIndex.FacePaintReversed], - CustomizeFacePaintColor => data.Customize[CustomizeIndex.FacePaintColor], - - MetaWetness => data.GetMeta(MetaIndex.Wetness), - MetaHatState => data.GetMeta(MetaIndex.HatState), - MetaVisorState => data.GetMeta(MetaIndex.VisorState), - MetaWeaponState => data.GetMeta(MetaIndex.WeaponState), - MetaModelId => data.ModelId, - - CrestHead => data.Crest(CrestFlag.Head), - CrestBody => data.Crest(CrestFlag.Body), - CrestOffhand => data.Crest(CrestFlag.OffHand), - - ParamSkinDiffuse => data.Parameters[CustomizeParameterFlag.SkinDiffuse], - ParamMuscleTone => data.Parameters[CustomizeParameterFlag.MuscleTone], - ParamSkinSpecular => data.Parameters[CustomizeParameterFlag.SkinSpecular], - ParamLipDiffuse => data.Parameters[CustomizeParameterFlag.LipDiffuse], - ParamHairDiffuse => data.Parameters[CustomizeParameterFlag.HairDiffuse], - ParamHairSpecular => data.Parameters[CustomizeParameterFlag.HairSpecular], - ParamHairHighlight => data.Parameters[CustomizeParameterFlag.HairHighlight], - ParamLeftEye => data.Parameters[CustomizeParameterFlag.LeftEye], - ParamRightEye => data.Parameters[CustomizeParameterFlag.RightEye], - ParamFeatureColor => data.Parameters[CustomizeParameterFlag.FeatureColor], - ParamFacePaintUvMultiplier => data.Parameters[CustomizeParameterFlag.FacePaintUvMultiplier], - ParamFacePaintUvOffset => data.Parameters[CustomizeParameterFlag.FacePaintUvOffset], - ParamDecalColor => data.Parameters[CustomizeParameterFlag.DecalColor], - - BonusItemGlasses => data.BonusItem(BonusItemFlag.Glasses), - - _ => null, - }; - } - private static string GetName(EquipFlag flag) { var slot = flag.ToSlot(out var stain); diff --git a/Penumbra.GameData b/Penumbra.GameData index 8928015..491b619 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 8928015f38f951810a9a6fbb44fb4a0cb9a712dd +Subproject commit 491b61916951b7192bb2354d725363340ea153b5