Initial Update for multiple stains, some facewear support, and API X

This commit is contained in:
Ottermandias 2024-07-11 14:21:25 +02:00
parent c1d9af2dd0
commit 7caf6cc08a
90 changed files with 654 additions and 537 deletions

View file

@ -1,5 +1,5 @@
using Dalamud.Game.ClientState.Objects.Enums; using Dalamud.Game.ClientState.Objects.Enums;
using Dalamud.Interface.Internal.Notifications; using Dalamud.Interface.ImGuiNotification;
using Glamourer.Designs; using Glamourer.Designs;
using Glamourer.Designs.Special; using Glamourer.Designs.Special;
using Glamourer.Events; using Glamourer.Events;

View file

@ -1,4 +1,4 @@
using Dalamud.Interface.Internal.Notifications; using Dalamud.Interface.ImGuiNotification;
using Glamourer.Designs; using Glamourer.Designs;
using Glamourer.Interop; using Glamourer.Interop;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;

View file

@ -1,6 +1,6 @@
using Dalamud.Configuration; using Dalamud.Configuration;
using Dalamud.Game.ClientState.Keys; using Dalamud.Game.ClientState.Keys;
using Dalamud.Interface.Internal.Notifications; using Dalamud.Interface.ImGuiNotification;
using Glamourer.Designs; using Glamourer.Designs;
using Glamourer.Gui; using Glamourer.Gui;
using Glamourer.Gui.Tabs.DesignTab; using Glamourer.Gui.Tabs.DesignTab;

View file

@ -1,4 +1,4 @@
using Dalamud.Interface.Internal.Notifications; using Dalamud.Interface.ImGuiNotification;
using Glamourer.Automation; using Glamourer.Automation;
using Glamourer.Designs.Links; using Glamourer.Designs.Links;
using Glamourer.Interop.Material; using Glamourer.Interop.Material;

View file

@ -1,4 +1,4 @@
using Dalamud.Interface.Internal.Notifications; using Dalamud.Interface.ImGuiNotification;
using Glamourer.GameData; using Glamourer.GameData;
using Glamourer.Interop.Material; using Glamourer.Interop.Material;
using Glamourer.Services; using Glamourer.Services;
@ -257,10 +257,10 @@ public class DesignBase
foreach (var slot in EquipSlotExtensions.EqdpSlots.Prepend(EquipSlot.OffHand).Prepend(EquipSlot.MainHand)) foreach (var slot in EquipSlotExtensions.EqdpSlots.Prepend(EquipSlot.OffHand).Prepend(EquipSlot.MainHand))
{ {
var item = _designData.Item(slot); var item = _designData.Item(slot);
var stain = _designData.Stain(slot); var stains = _designData.Stain(slot);
var crestSlot = slot.ToCrestFlag(); var crestSlot = slot.ToCrestFlag();
var crest = _designData.Crest(crestSlot); 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"); ret["Hat"] = new QuadBool(_designData.IsHatVisible(), DoApplyMeta(MetaIndex.HatState)).ToJObject("Show", "Apply");
@ -274,16 +274,15 @@ public class DesignBase
return ret; return ret;
static JObject Serialize(CustomItemId id, StainId stain, bool crest, bool apply, bool applyStain, bool applyCrest) static JObject Serialize(CustomItemId id, StainIds stains, bool crest, bool apply, bool applyStain, bool applyCrest)
=> new() => stains.AddToObject(new JObject
{ {
["ItemId"] = id.Id, ["ItemId"] = id.Id,
["Stain"] = stain.Id,
["Crest"] = crest, ["Crest"] = crest,
["Apply"] = apply, ["Apply"] = apply,
["ApplyStain"] = applyStain, ["ApplyStain"] = applyStain,
["ApplyCrest"] = applyCrest, ["ApplyCrest"] = applyCrest,
}; });
} }
protected JObject SerializeCustomize() protected JObject SerializeCustomize()
@ -522,7 +521,7 @@ public class DesignBase
return; 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<ulong>() ?? ItemManager.NothingId(slot).Id; var id = item?["ItemId"]?.ToObject<ulong>() ?? ItemManager.NothingId(slot).Id;
var stain = (StainId)(item?["Stain"]?.ToObject<byte>() ?? 0); var stain = (StainId)(item?["Stain"]?.ToObject<byte>() ?? 0);

View file

@ -1,5 +1,4 @@
using Glamourer.Services; using Glamourer.Services;
using Glamourer.State;
using OtterGui; using OtterGui;
using Penumbra.GameData.DataContainers; using Penumbra.GameData.DataContainers;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
@ -117,7 +116,7 @@ public class DesignBase64Migration
} }
data.SetItem(slot, item); data.SetItem(slot, item);
data.SetStain(slot, mdl.Stain); data.SetStain(slot, mdl.Stains);
} }
var main = cur[0].Skeleton.Id == 0 var main = cur[0].Skeleton.Id == 0
@ -130,7 +129,7 @@ public class DesignBase64Migration
} }
data.SetItem(EquipSlot.MainHand, main); data.SetItem(EquipSlot.MainHand, main);
data.SetStain(EquipSlot.MainHand, cur[0].Stain); data.SetStain(EquipSlot.MainHand, cur[0].Stains);
EquipItem off; EquipItem off;
// Fist weapon hack // Fist weapon hack
@ -141,7 +140,7 @@ public class DesignBase64Migration
if (gauntlet.Valid) if (gauntlet.Valid)
{ {
data.SetItem(EquipSlot.Hands, gauntlet); data.SetItem(EquipSlot.Hands, gauntlet);
data.SetStain(EquipSlot.Hands, cur[0].Stain); data.SetStain(EquipSlot.Hands, cur[0].Stains);
} }
} }
else else
@ -158,7 +157,7 @@ public class DesignBase64Migration
} }
data.SetItem(EquipSlot.OffHand, off); data.SetItem(EquipSlot.OffHand, off);
data.SetStain(EquipSlot.OffHand, cur[1].Stain); data.SetStain(EquipSlot.OffHand, cur[1].Stains);
return data; return data;
} }
} }

View file

@ -1,5 +1,5 @@
using Dalamud.Interface; using Dalamud.Interface;
using Dalamud.Interface.Internal.Notifications; using Dalamud.Interface.ImGuiNotification;
using Dalamud.Interface.Utility.Raii; using Dalamud.Interface.Utility.Raii;
using Glamourer.Gui; using Glamourer.Gui;
using Glamourer.Services; using Glamourer.Services;

View file

@ -176,7 +176,7 @@ public class DesignConverter(
return System.Convert.ToBase64String(compressed); return System.Convert.ToBase64String(compressed);
} }
public IEnumerable<(EquipSlot Slot, EquipItem Item, StainId Stain)> FromDrawData(IReadOnlyList<CharacterArmor> armors, public IEnumerable<(EquipSlot Slot, EquipItem Item, StainIds Stains)> FromDrawData(IReadOnlyList<CharacterArmor> armors,
CharacterWeapon mainhand, CharacterWeapon offhand, bool skipWarnings) CharacterWeapon mainhand, CharacterWeapon offhand, bool skipWarnings)
{ {
if (armors.Count != 10) if (armors.Count != 10)
@ -194,7 +194,7 @@ public class DesignConverter(
item = ItemManager.NothingItem(slot); 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); var mh = _items.Identify(EquipSlot.MainHand, mainhand.Skeleton, mainhand.Weapon, mainhand.Variant);
@ -204,7 +204,7 @@ public class DesignConverter(
mh = _items.DefaultSword; 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); var oh = _items.Identify(EquipSlot.OffHand, offhand.Skeleton, offhand.Weapon, offhand.Variant, mh.Type);
if (!skipWarnings && !oh.Valid) if (!skipWarnings && !oh.Valid)
@ -215,7 +215,7 @@ public class DesignConverter(
oh = ItemManager.NothingItem(FullEquipType.Shield); 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, private static void ComputeMaterials(DesignMaterialManager manager, in StateMaterialManager materials,

View file

@ -9,6 +9,8 @@ namespace Glamourer.Designs;
public unsafe struct DesignData public unsafe struct DesignData
{ {
public const int EquipmentByteSize = 10 * CharacterArmor.Size;
private string _nameHead = string.Empty; private string _nameHead = string.Empty;
private string _nameBody = string.Empty; private string _nameBody = string.Empty;
private string _nameHands = string.Empty; private string _nameHands = string.Empty;
@ -21,15 +23,14 @@ public unsafe struct DesignData
private string _nameLFinger = string.Empty; private string _nameLFinger = string.Empty;
private string _nameMainhand = string.Empty; private string _nameMainhand = string.Empty;
private string _nameOffhand = string.Empty; private string _nameOffhand = string.Empty;
private string _nameFaceWear = string.Empty;
private fixed uint _itemIds[12]; private fixed uint _itemIds[12];
private fixed ushort _iconIds[12]; private fixed uint _iconIds[12];
private fixed byte _equipmentBytes[48]; private fixed byte _equipmentBytes[EquipmentByteSize + 16];
public CustomizeParameterData Parameters; public CustomizeParameterData Parameters;
public CustomizeArray Customize = CustomizeArray.Default; public CustomizeArray Customize = CustomizeArray.Default;
public uint ModelId; public uint ModelId;
public CrestFlag CrestVisibility; public CrestFlag CrestVisibility;
private SecondaryId _secondaryMainhand;
private SecondaryId _secondaryOffhand;
private FullEquipType _typeMainhand; private FullEquipType _typeMainhand;
private FullEquipType _typeOffhand; private FullEquipType _typeOffhand;
private byte _states; private byte _states;
@ -50,12 +51,19 @@ public unsafe struct DesignData
|| name.IsContained(_nameRFinger) || name.IsContained(_nameRFinger)
|| name.IsContained(_nameLFinger) || name.IsContained(_nameLFinger)
|| name.IsContained(_nameMainhand) || 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(); 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) public readonly bool Crest(CrestFlag slot)
@ -69,24 +77,29 @@ public unsafe struct DesignData
=> _typeOffhand; => _typeOffhand;
public readonly EquipItem Item(EquipSlot slot) public readonly EquipItem Item(EquipSlot slot)
=> slot.ToIndex() switch {
fixed (byte* ptr = _equipmentBytes)
{ {
return slot.ToIndex() switch
{
// @formatter:off // @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 ), 0 => EquipItem.FromIds(_itemIds[ 0], _iconIds[ 0], ((CharacterArmor*)ptr)[0].Set, 0, ((CharacterArmor*)ptr)[0].Variant, 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 ), 1 => EquipItem.FromIds(_itemIds[ 1], _iconIds[ 1], ((CharacterArmor*)ptr)[1].Set, 0, ((CharacterArmor*)ptr)[1].Variant, 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 ), 2 => EquipItem.FromIds(_itemIds[ 2], _iconIds[ 2], ((CharacterArmor*)ptr)[2].Set, 0, ((CharacterArmor*)ptr)[2].Variant, 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 ), 3 => EquipItem.FromIds(_itemIds[ 3], _iconIds[ 3], ((CharacterArmor*)ptr)[3].Set, 0, ((CharacterArmor*)ptr)[3].Variant, 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 ), 4 => EquipItem.FromIds(_itemIds[ 4], _iconIds[ 4], ((CharacterArmor*)ptr)[4].Set, 0, ((CharacterArmor*)ptr)[4].Variant, 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 ), 5 => EquipItem.FromIds(_itemIds[ 5], _iconIds[ 5], ((CharacterArmor*)ptr)[5].Set, 0, ((CharacterArmor*)ptr)[5].Variant, 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 ), 6 => EquipItem.FromIds(_itemIds[ 6], _iconIds[ 6], ((CharacterArmor*)ptr)[6].Set, 0, ((CharacterArmor*)ptr)[6].Variant, 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 ), 7 => EquipItem.FromIds(_itemIds[ 7], _iconIds[ 7], ((CharacterArmor*)ptr)[7].Set, 0, ((CharacterArmor*)ptr)[7].Variant, 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 ), 8 => EquipItem.FromIds(_itemIds[ 8], _iconIds[ 8], ((CharacterArmor*)ptr)[8].Set, 0, ((CharacterArmor*)ptr)[8].Variant, 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 ), 9 => EquipItem.FromIds(_itemIds[ 9], _iconIds[ 9], ((CharacterArmor*)ptr)[9].Set, 0, ((CharacterArmor*)ptr)[9].Variant, 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), 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((ItemId)_itemIds[11], (IconId)_iconIds[11], (PrimaryId)(_equipmentBytes[44] | (_equipmentBytes[45] << 8)), _secondaryOffhand, (Variant)_equipmentBytes[46], _typeOffhand, name: _nameOffhand ), 11 => EquipItem.FromIds(_itemIds[11], _iconIds[11], *(PrimaryId*)(ptr + EquipmentByteSize + 4), *(SecondaryId*)(ptr + EquipmentByteSize + 2), *(Variant*)(ptr + EquipmentByteSize + 4), _typeOffhand, name: _nameOffhand ),
_ => new EquipItem(), _ => new EquipItem(),
// @formatter:on // @formatter:on
}; };
}
}
public readonly CharacterArmor Armor(EquipSlot slot) public readonly CharacterArmor Armor(EquipSlot slot)
{ {
@ -113,8 +126,8 @@ public unsafe struct DesignData
{ {
fixed (byte* ptr = _equipmentBytes) fixed (byte* ptr = _equipmentBytes)
{ {
var armorPtr = (CharacterArmor*)ptr; var weaponPtr = (CharacterWeapon*)(ptr + EquipmentByteSize);
return slot is EquipSlot.MainHand ? armorPtr[10].ToWeapon(_secondaryMainhand) : armorPtr[11].ToWeapon(_secondaryOffhand); return weaponPtr[slot is EquipSlot.MainHand ? 0 : 1];
} }
} }
@ -124,11 +137,11 @@ public unsafe struct DesignData
if (index > 11) if (index > 11)
return false; return false;
_itemIds[index] = item.ItemId.Id; _itemIds[index] = item.ItemId.Id;
_iconIds[index] = item.IconId.Id; _iconIds[index] = item.IconId.Id;
_equipmentBytes[4 * index + 0] = (byte)item.PrimaryId.Id; _equipmentBytes[CharacterArmor.Size * index + 0] = (byte)item.PrimaryId.Id;
_equipmentBytes[4 * index + 1] = (byte)(item.PrimaryId.Id >> 8); _equipmentBytes[CharacterArmor.Size * index + 1] = (byte)(item.PrimaryId.Id >> 8);
_equipmentBytes[4 * index + 2] = item.Variant.Id; _equipmentBytes[CharacterArmor.Size * index + 2] = item.Variant.Id;
switch (index) switch (index)
{ {
// @formatter:off // @formatter:off
@ -144,36 +157,40 @@ public unsafe struct DesignData
case 9: _nameLFinger = item.Name; return true; case 9: _nameLFinger = item.Name; return true;
// @formatter:on // @formatter:on
case 10: case 10:
_nameMainhand = item.Name; _nameMainhand = item.Name;
_secondaryMainhand = item.SecondaryId; _equipmentBytes[EquipmentByteSize + 2] = (byte)item.SecondaryId.Id;
_typeMainhand = item.Type; _equipmentBytes[EquipmentByteSize + 3] = (byte)(item.SecondaryId.Id >> 8);
_typeMainhand = item.Type;
return true; return true;
case 11: case 11:
_nameOffhand = item.Name; _nameOffhand = item.Name;
_secondaryOffhand = item.SecondaryId; _equipmentBytes[EquipmentByteSize + 2] = (byte)item.SecondaryId.Id;
_typeOffhand = item.Type; _equipmentBytes[EquipmentByteSize + 3] = (byte)(item.SecondaryId.Id >> 8);
_typeOffhand = item.Type;
return true; return true;
} }
return true; return true;
} }
public bool SetStain(EquipSlot slot, StainId stain) public bool SetStain(EquipSlot slot, StainIds stains)
=> slot.ToIndex() switch => slot.ToIndex() switch
{ {
0 => SetIfDifferent(ref _equipmentBytes[3], stain.Id), // @formatter:off
1 => SetIfDifferent(ref _equipmentBytes[7], stain.Id), 0 => SetIfDifferent(ref _equipmentBytes[0 * CharacterArmor.Size + 3], stains.Stain1.Id) || SetIfDifferent(ref _equipmentBytes[0 * CharacterArmor.Size + 4], stains.Stain2.Id),
2 => SetIfDifferent(ref _equipmentBytes[11], stain.Id), 1 => SetIfDifferent(ref _equipmentBytes[1 * CharacterArmor.Size + 3], stains.Stain1.Id) || SetIfDifferent(ref _equipmentBytes[1 * CharacterArmor.Size + 4], stains.Stain2.Id),
3 => SetIfDifferent(ref _equipmentBytes[15], stain.Id), 2 => SetIfDifferent(ref _equipmentBytes[2 * CharacterArmor.Size + 3], stains.Stain1.Id) || SetIfDifferent(ref _equipmentBytes[2 * CharacterArmor.Size + 4], stains.Stain2.Id),
4 => SetIfDifferent(ref _equipmentBytes[19], stain.Id), 3 => SetIfDifferent(ref _equipmentBytes[3 * CharacterArmor.Size + 3], stains.Stain1.Id) || SetIfDifferent(ref _equipmentBytes[3 * CharacterArmor.Size + 4], stains.Stain2.Id),
5 => SetIfDifferent(ref _equipmentBytes[23], stain.Id), 4 => SetIfDifferent(ref _equipmentBytes[4 * CharacterArmor.Size + 3], stains.Stain1.Id) || SetIfDifferent(ref _equipmentBytes[4 * CharacterArmor.Size + 4], stains.Stain2.Id),
6 => SetIfDifferent(ref _equipmentBytes[27], stain.Id), 5 => SetIfDifferent(ref _equipmentBytes[5 * CharacterArmor.Size + 3], stains.Stain1.Id) || SetIfDifferent(ref _equipmentBytes[5 * CharacterArmor.Size + 4], stains.Stain2.Id),
7 => SetIfDifferent(ref _equipmentBytes[31], stain.Id), 6 => SetIfDifferent(ref _equipmentBytes[6 * CharacterArmor.Size + 3], stains.Stain1.Id) || SetIfDifferent(ref _equipmentBytes[6 * CharacterArmor.Size + 4], stains.Stain2.Id),
8 => SetIfDifferent(ref _equipmentBytes[35], stain.Id), 7 => SetIfDifferent(ref _equipmentBytes[7 * CharacterArmor.Size + 3], stains.Stain1.Id) || SetIfDifferent(ref _equipmentBytes[7 * CharacterArmor.Size + 4], stains.Stain2.Id),
9 => SetIfDifferent(ref _equipmentBytes[39], stain.Id), 8 => SetIfDifferent(ref _equipmentBytes[8 * CharacterArmor.Size + 3], stains.Stain1.Id) || SetIfDifferent(ref _equipmentBytes[8 * CharacterArmor.Size + 4], stains.Stain2.Id),
10 => SetIfDifferent(ref _equipmentBytes[43], stain.Id), 9 => SetIfDifferent(ref _equipmentBytes[9 * CharacterArmor.Size + 3], stains.Stain1.Id) || SetIfDifferent(ref _equipmentBytes[9 * CharacterArmor.Size + 4], stains.Stain2.Id),
11 => SetIfDifferent(ref _equipmentBytes[47], stain.Id), 10 => SetIfDifferent(ref _equipmentBytes[EquipmentByteSize + 6], stains.Stain1.Id) || SetIfDifferent(ref _equipmentBytes[EquipmentByteSize + 7], stains.Stain2.Id),
_ => false, 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) public bool SetCrest(CrestFlag slot, bool visible)
@ -260,15 +277,15 @@ public unsafe struct DesignData
foreach (var slot in EquipSlotExtensions.EqdpSlots) foreach (var slot in EquipSlotExtensions.EqdpSlots)
{ {
SetItem(slot, ItemManager.NothingItem(slot)); SetItem(slot, ItemManager.NothingItem(slot));
SetStain(slot, 0); SetStain(slot, StainIds.None);
SetCrest(slot.ToCrestFlag(), false); SetCrest(slot.ToCrestFlag(), false);
} }
SetItem(EquipSlot.MainHand, items.DefaultSword); SetItem(EquipSlot.MainHand, items.DefaultSword);
SetStain(EquipSlot.MainHand, 0); SetStain(EquipSlot.MainHand, StainIds.None);
SetCrest(CrestFlag.MainHand, false); SetCrest(CrestFlag.MainHand, false);
SetItem(EquipSlot.OffHand, ItemManager.NothingItem(FullEquipType.Shield)); SetItem(EquipSlot.OffHand, ItemManager.NothingItem(FullEquipType.Shield));
SetStain(EquipSlot.OffHand, 0); SetStain(EquipSlot.OffHand, StainIds.None);
SetCrest(CrestFlag.OffHand, false); SetCrest(CrestFlag.OffHand, false);
} }
@ -291,21 +308,22 @@ public unsafe struct DesignData
MemoryUtility.MemSet(ptr, 0, 10 * 4); MemoryUtility.MemSet(ptr, 0, 10 * 4);
} }
fixed (ushort* ptr = _iconIds) fixed (uint* ptr = _iconIds)
{ {
MemoryUtility.MemSet(ptr, 0, 10 * 2); MemoryUtility.MemSet(ptr, 0, 10 * 2);
} }
_nameHead = string.Empty; _nameHead = string.Empty;
_nameBody = string.Empty; _nameBody = string.Empty;
_nameHands = string.Empty; _nameHands = string.Empty;
_nameLegs = string.Empty; _nameLegs = string.Empty;
_nameFeet = string.Empty; _nameFeet = string.Empty;
_nameEars = string.Empty; _nameEars = string.Empty;
_nameNeck = string.Empty; _nameNeck = string.Empty;
_nameWrists = string.Empty; _nameWrists = string.Empty;
_nameRFinger = string.Empty; _nameRFinger = string.Empty;
_nameLFinger = string.Empty; _nameLFinger = string.Empty;
_nameFaceWear = string.Empty;
return true; return true;
} }
@ -322,7 +340,7 @@ public unsafe struct DesignData
public readonly byte[] GetEquipmentBytes() public readonly byte[] GetEquipmentBytes()
{ {
var ret = new byte[40]; var ret = new byte[80];
fixed (byte* retPtr = ret, inPtr = _equipmentBytes) fixed (byte* retPtr = ret, inPtr = _equipmentBytes)
{ {
MemoryUtility.MemCpyUnchecked(retPtr, inPtr, ret.Length); MemoryUtility.MemCpyUnchecked(retPtr, inPtr, ret.Length);
@ -343,8 +361,8 @@ public unsafe struct DesignData
{ {
fixed (byte* dataPtr = _equipmentBytes) fixed (byte* dataPtr = _equipmentBytes)
{ {
var data = new Span<byte>(dataPtr, 40); var data = new Span<byte>(dataPtr, 80);
return Convert.TryFromBase64String(base64, data, out var written) && written == 40; return Convert.TryFromBase64String(base64, data, out var written) && written == 80;
} }
} }

View file

@ -3,7 +3,6 @@ using Glamourer.Events;
using Glamourer.GameData; using Glamourer.GameData;
using Glamourer.Interop.Material; using Glamourer.Interop.Material;
using Glamourer.Services; using Glamourer.Services;
using Glamourer.State;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs; using Penumbra.GameData.Structs;
@ -167,29 +166,29 @@ public class DesignEditor(
} }
/// <inheritdoc/> /// <inheritdoc/>
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; var design = (Design)data;
if (Items.ValidateStain(stain, out var _, false).Length > 0) if (Items.ValidateStain(stains, out var _, false).Length > 0)
return; return;
var oldStain = design.DesignData.Stain(slot); var oldStain = design.DesignData.Stain(slot);
if (!design.GetDesignDataRef().SetStain(slot, stain)) if (!design.GetDesignDataRef().SetStain(slot, stains))
return; return;
design.LastEdit = DateTimeOffset.UtcNow; design.LastEdit = DateTimeOffset.UtcNow;
SaveService.QueueSave(design); SaveService.QueueSave(design);
Glamourer.Log.Debug($"Set stain of {slot} equipment piece to {stain.Id}."); Glamourer.Log.Debug($"Set stain of {slot} equipment piece to {stains}.");
DesignChanged.Invoke(DesignChanged.Type.Stain, design, (oldStain, stain, slot)); DesignChanged.Invoke(DesignChanged.Type.Stain, design, (oldStain, stains, slot));
} }
/// <inheritdoc/> /// <inheritdoc/>
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) if (item.HasValue)
ChangeItem(data, slot, item.Value, _); ChangeItem(data, slot, item.Value, _);
if (stain.HasValue) if (stains.HasValue)
ChangeStain(data, slot, stain.Value, _); ChangeStains(data, slot, stains.Value, _);
} }
/// <inheritdoc/> /// <inheritdoc/>

View file

@ -1,4 +1,4 @@
using Dalamud.Interface.Internal.Notifications; using Dalamud.Interface.ImGuiNotification;
using Glamourer.Events; using Glamourer.Events;
using Glamourer.Services; using Glamourer.Services;
using Newtonsoft.Json; using Newtonsoft.Json;

View file

@ -65,11 +65,11 @@ public interface IDesignEditor
=> ChangeEquip(data, slot, item, null, settings); => ChangeEquip(data, slot, item, null, settings);
/// <summary> Change the stain for any equipment piece. </summary> /// <summary> Change the stain for any equipment piece. </summary>
public void ChangeStain(object data, EquipSlot slot, StainId stain, ApplySettings settings = default) public void ChangeStains(object data, EquipSlot slot, StainIds stains, ApplySettings settings = default)
=> ChangeEquip(data, slot, null, stain, settings); => ChangeEquip(data, slot, null, stains, settings);
/// <summary> Change an equipment piece and its stain at the same time. </summary> /// <summary> Change an equipment piece and its stain at the same time. </summary>
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);
/// <summary> Change the crest visibility for any equipment piece. </summary> /// <summary> Change the crest visibility for any equipment piece. </summary>
public void ChangeCrest(object data, CrestFlag slot, bool crest, ApplySettings settings = default); public void ChangeCrest(object data, CrestFlag slot, bool crest, ApplySettings settings = default);

View file

@ -1,7 +1,8 @@
using Dalamud.Interface.Internal.Notifications; using Dalamud.Interface.ImGuiNotification;
using OtterGui; using OtterGui;
using OtterGui.Classes; using OtterGui.Classes;
using OtterGui.Services; using OtterGui.Services;
using Notification = OtterGui.Classes.Notification;
namespace Glamourer.Designs.Links; namespace Glamourer.Designs.Links;

View file

@ -1,4 +1,4 @@
using Dalamud.Interface.Internal.Notifications; using Dalamud.Interface.ImGuiNotification;
using Glamourer.Gui; using Glamourer.Gui;
using Glamourer.Services; using Glamourer.Services;
using Newtonsoft.Json; using Newtonsoft.Json;

View file

@ -11,7 +11,7 @@ namespace Glamourer.Events;
/// </list> /// </list>
/// </summary> /// </summary>
public sealed class MovedEquipment() public sealed class MovedEquipment()
: EventWrapper<(EquipSlot, uint, StainId)[], MovedEquipment.Priority>(nameof(MovedEquipment)) : EventWrapper<(EquipSlot, uint, StainIds)[], MovedEquipment.Priority>(nameof(MovedEquipment))
{ {
public enum Priority public enum Priority
{ {

View file

@ -1,4 +1,5 @@
using Dalamud.Interface.Internal; using Dalamud.Interface.Textures;
using Dalamud.Interface.Textures.TextureWraps;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using OtterGui.Classes; using OtterGui.Classes;
using OtterGui.Services; using OtterGui.Services;
@ -32,8 +33,8 @@ public class CustomizeManager : IAsyncDataContainer
} }
/// <summary> Get specific icons. </summary> /// <summary> Get specific icons. </summary>
public IDalamudTextureWrap GetIcon(uint id) public ISharedImmediateTexture GetIcon(uint id)
=> _icons.LoadIcon(id)!; => _icons.TextureProvider.GetFromGameIcon(id);
/// <summary> Iterate over all supported genders and clans. </summary> /// <summary> Iterate over all supported genders and clans. </summary>
public static IEnumerable<(SubRace Clan, Gender Gender)> AllSets() 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) public CustomizeManager(ITextureProvider textures, IDataManager gameData, IPluginLog log, NpcCustomizeSet npcCustomizeSet)
{ {
_icons = new IconStorage(textures, gameData); _icons = new TextureCache(gameData, textures);
var stopwatch = new Stopwatch(); var stopwatch = new Stopwatch();
var tmpTask = Task.Run(() => var tmpTask = Task.Run(() =>
{ {
stopwatch.Start(); stopwatch.Start();
@ -72,7 +73,7 @@ public class CustomizeManager : IAsyncDataContainer
public bool Finished public bool Finished
=> Awaiter.IsCompletedSuccessfully; => Awaiter.IsCompletedSuccessfully;
private readonly IconStorage _icons; private readonly TextureCache _icons;
private static readonly int ListSize = Clans.Count * Genders.Count; private static readonly int ListSize = Clans.Count * Genders.Count;
private readonly CustomizeSet[] _customizationSets = new CustomizeSet[ListSize]; private readonly CustomizeSet[] _customizationSets = new CustomizeSet[ListSize];

View file

@ -1,4 +1,5 @@
using Dalamud; using Dalamud;
using Dalamud.Game;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using Dalamud.Utility; using Dalamud.Utility;
using Lumina.Excel; using Lumina.Excel;
@ -13,11 +14,11 @@ namespace Glamourer.GameData;
internal class CustomizeSetFactory( internal class CustomizeSetFactory(
IDataManager _gameData, IDataManager _gameData,
IPluginLog _log, IPluginLog _log,
IconStorage _icons, TextureCache _icons,
NpcCustomizeSet _npcCustomizeSet, NpcCustomizeSet _npcCustomizeSet,
ColorParameters _colors) 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)) : this(gameData, log, icons, npcCustomizeSet, new ColorParameters(gameData, log))
{ } { }
@ -87,7 +88,8 @@ internal class CustomizeSetFactory(
var npcCustomizations = new HashSet<(CustomizeIndex, CustomizeValue)>(); var npcCustomizations = new HashSet<(CustomizeIndex, CustomizeValue)>();
_npcCustomizeSet.Awaiter.Wait(); _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) foreach (var customizeIndex in customizeIndices)
{ {
@ -346,10 +348,6 @@ internal class CustomizeSetFactory(
/// <summary> Set the availability of options according to actual availability. </summary> /// <summary> Set the availability of options according to actual availability. </summary>
private static void SetAvailability(CustomizeSet set, CharaMakeParams row) 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(true, CustomizeIndex.Height);
Set(set.Faces.Count > 0, CustomizeIndex.Face); Set(set.Faces.Count > 0, CustomizeIndex.Face);
Set(true, CustomizeIndex.Hairstyle); Set(true, CustomizeIndex.Hairstyle);

View file

@ -80,25 +80,9 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList<NpcData>
// Event NPCs have a reference to NpcEquip but also contain the appearance in their own row. // 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. // Prefer the NpcEquip reference if it is set, otherwise use the own.
if (row.NpcEquip.Row != 0 && row.NpcEquip.Value is { } equip) if (row.NpcEquip.Row != 0 && row.NpcEquip.Value is { } equip)
{
ApplyNpcEquip(ref ret, equip); ApplyNpcEquip(ref ret, equip);
}
else else
{ ApplyNpcEquip(ref ret, row);
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;
}
list.Add(ret); list.Add(ret);
} }
@ -202,18 +186,36 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList<NpcData>
/// <summary> Apply equipment from a NpcEquip row. </summary> /// <summary> Apply equipment from a NpcEquip row. </summary>
private static void ApplyNpcEquip(ref NpcData data, NpcEquip row) private static void ApplyNpcEquip(ref NpcData data, NpcEquip row)
{ {
data.Set(0, row.ModelHead | (row.DyeHead.Row << 24)); data.Set(0, row.ModelHead | (row.DyeHead.Row << 24) | ((ulong)row.Dye2Head.Row << 32));
data.Set(1, row.ModelBody | (row.DyeBody.Row << 24)); data.Set(1, row.ModelBody | (row.DyeBody.Row << 24) | ((ulong)row.Dye2Body.Row << 32));
data.Set(2, row.ModelHands | (row.DyeHands.Row << 24)); data.Set(2, row.ModelHands | (row.DyeHands.Row << 24) | ((ulong)row.Dye2Hands.Row << 32));
data.Set(3, row.ModelLegs | (row.DyeLegs.Row << 24)); data.Set(3, row.ModelLegs | (row.DyeLegs.Row << 24) | ((ulong)row.Dye2Legs.Row << 32));
data.Set(4, row.ModelFeet | (row.DyeFeet.Row << 24)); data.Set(4, row.ModelFeet | (row.DyeFeet.Row << 24) | ((ulong)row.Dye2Feet.Row << 32));
data.Set(5, row.ModelEars | (row.DyeEars.Row << 24)); data.Set(5, row.ModelEars | (row.DyeEars.Row << 24) | ((ulong)row.Dye2Ears.Row << 32));
data.Set(6, row.ModelNeck | (row.DyeNeck.Row << 24)); data.Set(6, row.ModelNeck | (row.DyeNeck.Row << 24) | ((ulong)row.Dye2Neck.Row << 32));
data.Set(7, row.ModelWrists | (row.DyeWrists.Row << 24)); data.Set(7, row.ModelWrists | (row.DyeWrists.Row << 24) | ((ulong)row.Dye2Wrists.Row << 32));
data.Set(8, row.ModelRightRing | (row.DyeRightRing.Row << 24)); data.Set(8, row.ModelRightRing | (row.DyeRightRing.Row << 24) | ((ulong)row.Dye2RightRing.Row << 32));
data.Set(9, row.ModelLeftRing | (row.DyeLeftRing.Row << 24)); 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)); 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)); data.Offhand = new CharacterWeapon(row.ModelOffHand | ((ulong)row.DyeOffHand.Row << 48) | ((ulong)row.Dye2OffHand.Row << 56));
data.VisorToggled = row.Visor;
}
/// <summary> Apply equipment from a ENpcBase Row row. </summary>
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; data.VisorToggled = row.Visor;
} }

View file

@ -13,7 +13,7 @@ public unsafe struct NpcData
public CustomizeArray Customize; public CustomizeArray Customize;
/// <summary> The equipment appearance of the NPC, 10 * CharacterArmor. </summary> /// <summary> The equipment appearance of the NPC, 10 * CharacterArmor. </summary>
private fixed byte _equip[40]; private fixed byte _equip[CharacterArmor.Size * 10];
/// <summary> The mainhand weapon appearance of the NPC. </summary> /// <summary> The mainhand weapon appearance of the NPC. </summary>
public CharacterWeapon Mainhand; public CharacterWeapon Mainhand;
@ -54,36 +54,35 @@ public unsafe struct NpcData
{ {
sb.Append(span[i].Set.Id.ToString("D4")) sb.Append(span[i].Set.Id.ToString("D4"))
.Append('-') .Append('-')
.Append(span[i].Variant.Id.ToString("D3")) .Append(span[i].Variant.Id.ToString("D3"));
.Append('-') foreach (var stain in span[i].Stains)
.Append(span[i].Stain.Id.ToString("D3")) sb.Append('-').Append(stain.Id.ToString("D3"));
.Append(", ");
} }
sb.Append(Mainhand.Skeleton.Id.ToString("D4")) sb.Append(Mainhand.Skeleton.Id.ToString("D4"))
.Append('-') .Append('-')
.Append(Mainhand.Weapon.Id.ToString("D4")) .Append(Mainhand.Weapon.Id.ToString("D4"))
.Append('-') .Append('-')
.Append(Mainhand.Variant.Id.ToString("D3")) .Append(Mainhand.Variant.Id.ToString("D3"));
.Append('-') foreach (var stain in Mainhand.Stains)
.Append(Mainhand.Stain.Id.ToString("D4")) sb.Append('-').Append(stain.Id.ToString("D3"));
.Append(", ") sb.Append(", ")
.Append(Offhand.Skeleton.Id.ToString("D4")) .Append(Offhand.Skeleton.Id.ToString("D4"))
.Append('-') .Append('-')
.Append(Offhand.Weapon.Id.ToString("D4")) .Append(Offhand.Weapon.Id.ToString("D4"))
.Append('-') .Append('-')
.Append(Offhand.Variant.Id.ToString("D3")) .Append(Offhand.Variant.Id.ToString("D3"));
.Append('-') foreach (var stain in Mainhand.Stains)
.Append(Offhand.Stain.Id.ToString("D3")); sb.Append('-').Append(stain.Id.ToString("D3"));
return sb.ToString(); return sb.ToString();
} }
/// <summary> Set an equipment piece to a given value. </summary> /// <summary> Set an equipment piece to a given value. </summary>
internal void Set(int idx, uint value) internal void Set(int idx, ulong value)
{ {
fixed (byte* ptr = _equip) fixed (byte* ptr = _equip)
{ {
((uint*)ptr)[idx] = value; ((ulong*)ptr)[idx] = value;
} }
} }

View file

@ -28,7 +28,7 @@ public class Glamourer : IDalamudPlugin
private readonly ServiceManager _services; private readonly ServiceManager _services;
public Glamourer(DalamudPluginInterface pluginInterface) public Glamourer(IDalamudPluginInterface pluginInterface)
{ {
try try
{ {
@ -128,7 +128,7 @@ public class Glamourer : IDalamudPlugin
[ [
"Penumbra", "MareSynchronos", "CustomizePlus", "SimpleHeels", "VfxEditor", "heliosphere-plugin", "Ktisis", "Brio", "DynamicBridge", "Penumbra", "MareSynchronos", "CustomizePlus", "SimpleHeels", "VfxEditor", "heliosphere-plugin", "Ktisis", "Brio", "DynamicBridge",
]; ];
var plugins = _services.GetService<DalamudPluginInterface>().InstalledPlugins var plugins = _services.GetService<IDalamudPluginInterface>().InstalledPlugins
.GroupBy(p => p.InternalName) .GroupBy(p => p.InternalName)
.ToDictionary(g => g.Key, g => .ToDictionary(g => g.Key, g =>
{ {

View file

@ -49,6 +49,7 @@
<PropertyGroup> <PropertyGroup>
<DalamudLibPath>$(AppData)\XIVLauncher\addon\Hooks\dev\</DalamudLibPath> <DalamudLibPath>$(AppData)\XIVLauncher\addon\Hooks\dev\</DalamudLibPath>
<DalamudLibPath>H:\Projects\FFPlugins\Dalamud\bin\Release\</DalamudLibPath>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View file

@ -8,7 +8,7 @@
"AssemblyVersion": "9.0.0.1", "AssemblyVersion": "9.0.0.1",
"RepoUrl": "https://github.com/Ottermandias/Glamourer", "RepoUrl": "https://github.com/Ottermandias/Glamourer",
"ApplicableVersion": "any", "ApplicableVersion": "any",
"DalamudApiLevel": 9, "DalamudApiLevel": 10,
"ImageUrls": null, "ImageUrls": null,
"IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/master/images/icon.png" "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/master/images/icon.png"
} }

View file

@ -34,14 +34,13 @@ public partial class CustomizationDrawer
private void DrawGenderSelector() private void DrawGenderSelector()
{ {
using (var disabled = ImRaii.Disabled(_locked || _lockedRedraw)) using (ImRaii.Disabled(_locked || _lockedRedraw))
{ {
var icon = _customize.Gender switch var icon = _customize.Gender switch
{ {
Gender.Male when _customize.Race is Race.Hrothgar => FontAwesomeIcon.MarsDouble, Gender.Male => FontAwesomeIcon.Mars,
Gender.Male => FontAwesomeIcon.Mars, Gender.Female => FontAwesomeIcon.Venus,
Gender.Female => FontAwesomeIcon.Venus, _ => FontAwesomeIcon.Question,
_ => FontAwesomeIcon.Question,
}; };
if (ImGuiUtil.DrawDisabledButton(icon.ToIconString(), _framedIconSize, string.Empty, if (ImGuiUtil.DrawDisabledButton(icon.ToIconString(), _framedIconSize, string.Empty,
@ -56,7 +55,7 @@ public partial class CustomizationDrawer
private void DrawRaceCombo() private void DrawRaceCombo()
{ {
using (var disabled = ImRaii.Disabled(_locked || _lockedRedraw)) using (ImRaii.Disabled(_locked || _lockedRedraw))
{ {
ImGui.SetNextItemWidth(_raceSelectorWidth); ImGui.SetNextItemWidth(_raceSelectorWidth);
using (var combo = ImRaii.Combo("##subRaceCombo", _service.ClanName(_customize.Clan, _customize.Gender))) using (var combo = ImRaii.Combo("##subRaceCombo", _service.ClanName(_customize.Clan, _customize.Gender)))

View file

@ -29,10 +29,11 @@ public partial class CustomizationDrawer
npc = true; 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)) 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); ImGui.OpenPopup(IconSelectorPopup);
} }
@ -43,7 +44,8 @@ public partial class CustomizationDrawer
} }
} }
ImGuiUtil.HoverIconTooltip(icon, _iconSize); if (hasIcon)
ImGuiUtil.HoverIconTooltip(wrap!, _iconSize);
ImGui.SameLine(); ImGui.SameLine();
using (_ = ImRaii.Group()) using (_ = ImRaii.Group())
@ -83,8 +85,9 @@ public partial class CustomizationDrawer
using var frameColor = current == i using var frameColor = current == i
? ImRaii.PushColor(ImGuiCol.Button, Colors.SelectedRed) ? ImRaii.PushColor(ImGuiCol.Button, Colors.SelectedRed)
: ImRaii.PushColor(ImGuiCol.Button, ColorId.FavoriteStarOn.Value(), isFavorite); : 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); UpdateValue(custom.Value);
ImGui.CloseCurrentPopup(); ImGui.CloseCurrentPopup();
@ -96,8 +99,9 @@ public partial class CustomizationDrawer
else else
_favorites.TryAdd(_set.Gender, _set.Clan, _currentIndex, custom.Value); _favorites.TryAdd(_set.Gender, _set.Clan, _currentIndex, custom.Value);
ImGuiUtil.HoverIconTooltip(icon, _iconSize, if (hasIcon)
FavoriteManager.TypeAllowed(_currentIndex) ? "Right-Click to toggle favorite." : string.Empty); ImGuiUtil.HoverIconTooltip(wrap!, _iconSize,
FavoriteManager.TypeAllowed(_currentIndex) ? "Right-Click to toggle favorite." : string.Empty);
var text = custom.Value.ToString(); var text = custom.Value.ToString();
var textWidth = ImGui.CalcTextSize(text).X; var textWidth = ImGui.CalcTextSize(text).X;
@ -199,14 +203,17 @@ public partial class CustomizationDrawer
var icon = featureIdx == CustomizeIndex.LegacyTattoo var icon = featureIdx == CustomizeIndex.LegacyTattoo
? _legacyTattoo ?? _service.Manager.GetIcon(feature.IconId) ? _legacyTattoo ?? _service.Manager.GetIcon(feature.IconId)
: _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)) Vector4.Zero, enabled ? Vector4.One : _redTint))
{ {
_customize.Set(featureIdx, enabled ? CustomizeValue.Zero : CustomizeValue.Max); _customize.Set(featureIdx, enabled ? CustomizeValue.Zero : CustomizeValue.Max);
Changed |= _currentFlag; Changed |= _currentFlag;
} }
ImGuiUtil.HoverIconTooltip(icon, _iconSize); if (hasIcon)
ImGuiUtil.HoverIconTooltip(wrap!, _iconSize);
if (idx % 4 != 3) if (idx % 4 != 3)
ImGui.SameLine(); ImGui.SameLine();
} }

View file

@ -1,4 +1,6 @@
using Dalamud.Interface.Internal; using Dalamud.Interface.Internal;
using Dalamud.Interface.Textures;
using Dalamud.Interface.Textures.TextureWraps;
using Dalamud.Interface.Utility; using Dalamud.Interface.Utility;
using Dalamud.Plugin; using Dalamud.Plugin;
using Glamourer.GameData; using Glamourer.GameData;
@ -6,6 +8,7 @@ using Glamourer.Services;
using Glamourer.Unlocks; using Glamourer.Unlocks;
using ImGuiNET; using ImGuiNET;
using OtterGui; using OtterGui;
using OtterGui.Classes;
using OtterGui.Raii; using OtterGui.Raii;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs; using Penumbra.GameData.Structs;
@ -13,16 +16,15 @@ using Penumbra.GameData.Structs;
namespace Glamourer.Gui.Customization; namespace Glamourer.Gui.Customization;
public partial class CustomizationDrawer( public partial class CustomizationDrawer(
DalamudPluginInterface pi, TextureCache textureCache,
CustomizeService _service, CustomizeService _service,
CodeService _codes, CodeService _codes,
Configuration _config, Configuration _config,
FavoriteManager _favorites, FavoriteManager _favorites,
HeightService _heightService) HeightService _heightService)
: IDisposable
{ {
private readonly Vector4 _redTint = new(0.6f, 0.3f, 0.3f, 1f); private readonly Vector4 _redTint = new(0.6f, 0.3f, 0.3f, 1f);
private readonly IDalamudTextureWrap? _legacyTattoo = GetLegacyTattooIcon(pi); private readonly ISharedImmediateTexture? _legacyTattoo = GetLegacyTattooIcon(textureCache);
private Exception? _terminate; private Exception? _terminate;
@ -47,9 +49,6 @@ public partial class CustomizationDrawer(
private float _raceSelectorWidth; private float _raceSelectorWidth;
private bool _withApply; private bool _withApply;
public void Dispose()
=> _legacyTattoo?.Dispose();
public bool Draw(CustomizeArray current, bool locked, bool lockedRedraw) public bool Draw(CustomizeArray current, bool locked, bool lockedRedraw)
{ {
_withApply = false; _withApply = false;
@ -190,16 +189,6 @@ public partial class CustomizationDrawer(
_raceSelectorWidth = _inputIntSize + _comboSelectorSize - _framedIconSize.X; _raceSelectorWidth = _inputIntSize + _comboSelectorSize - _framedIconSize.X;
} }
private static IDalamudTextureWrap? GetLegacyTattooIcon(DalamudPluginInterface pi) private static ISharedImmediateTexture? GetLegacyTattooIcon(TextureCache icons)
{ => icons.TextureProvider.GetFromManifestResource(Assembly.GetExecutingAssembly(), "Glamourer.LegacyTattoo.raw");
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;
}
} }

View file

@ -23,8 +23,8 @@ public struct EquipDrawData(EquipSlot slot, in DesignData designData)
public readonly void SetItem(EquipItem item) public readonly void SetItem(EquipItem item)
=> _editor.ChangeItem(_object, Slot, item, ApplySettings.Manual); => _editor.ChangeItem(_object, Slot, item, ApplySettings.Manual);
public readonly void SetStain(StainId stain) public readonly void SetStains(StainIds stains)
=> _editor.ChangeStain(_object, Slot, stain, ApplySettings.Manual); => _editor.ChangeStains(_object, Slot, stains, ApplySettings.Manual);
public readonly void SetApplyItem(bool value) public readonly void SetApplyItem(bool value)
{ {
@ -40,10 +40,10 @@ public struct EquipDrawData(EquipSlot slot, in DesignData designData)
manager.ChangeApplyStain(design, Slot, value); manager.ChangeApplyStain(design, Slot, value);
} }
public EquipItem CurrentItem = designData.Item(slot); public EquipItem CurrentItem = designData.Item(slot);
public StainId CurrentStain = designData.Stain(slot); public StainIds CurrentStains = designData.Stain(slot);
public EquipItem GameItem = default; public EquipItem GameItem = default;
public StainId GameStain = default; public StainIds GameStains = default;
public bool CurrentApply; public bool CurrentApply;
public bool CurrentApplyStain; public bool CurrentApplyStain;
@ -69,7 +69,7 @@ public struct EquipDrawData(EquipSlot slot, in DesignData designData)
Locked = state.IsLocked, Locked = state.IsLocked,
DisplayApplication = false, DisplayApplication = false,
GameItem = state.BaseData.Item(slot), GameItem = state.BaseData.Item(slot),
GameStain = state.BaseData.Stain(slot), GameStains = state.BaseData.Stain(slot),
AllowRevert = true, AllowRevert = true,
}; };
} }

View file

@ -8,6 +8,7 @@ using Glamourer.Unlocks;
using ImGuiNET; using ImGuiNET;
using OtterGui; using OtterGui;
using OtterGui.Raii; using OtterGui.Raii;
using OtterGui.Text;
using OtterGui.Widgets; using OtterGui.Widgets;
using Penumbra.GameData.DataContainers; using Penumbra.GameData.DataContainers;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
@ -236,14 +237,18 @@ public class EquipmentDrawer
/// <summary> Draw an input for stain that can set arbitrary values instead of choosing valid stains. </summary> /// <summary> Draw an input for stain that can set arbitrary values instead of choosing valid stains. </summary>
private static void DrawStainArtisan(EquipDrawData data) private static void DrawStainArtisan(EquipDrawData data)
{ {
int stainId = data.CurrentStain.Id; foreach (var (stain, index) in data.CurrentStains.WithIndex())
ImGui.SetNextItemWidth(40 * ImGuiHelpers.GlobalScale); {
if (!ImGui.InputInt("##stain", ref stainId, 0, 0)) using var id = ImUtf8.PushId(index);
return; 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); var newStainId = (StainId)Math.Clamp(stainId, 0, byte.MaxValue);
if (newStainId != data.CurrentStain.Id) if (newStainId != stain.Id)
data.SetStain(newStainId); data.SetStains(data.CurrentStains.With(index, newStainId));
}
} }
/// <summary> Draw an input for armor that can set arbitrary values instead of choosing items. </summary> /// <summary> Draw an input for armor that can set arbitrary values instead of choosing items. </summary>
@ -441,19 +446,27 @@ public class EquipmentDrawer
private void DrawStain(in EquipDrawData data, bool small) private void DrawStain(in EquipDrawData data, bool small)
{ {
var found = _stainData.TryGetValue(data.CurrentStain, out var stain);
using var disabled = ImRaii.Disabled(data.Locked); using var disabled = ImRaii.Disabled(data.Locked);
var change = small var width = (_comboLength - ImUtf8.ItemInnerSpacing.X * (data.CurrentStains.Count - 1)) / data.CurrentStains.Count;
? _stainCombo.Draw($"##stain{data.Slot}", stain.RgbaColor, stain.Name, found, stain.Gloss) foreach (var (stainId, index) in data.CurrentStains.WithIndex())
: _stainCombo.Draw($"##stain{data.Slot}", stain.RgbaColor, stain.Name, found, stain.Gloss, _comboLength); {
if (change) using var id = ImUtf8.PushId(index);
if (_stainData.TryGetValue(_stainCombo.CurrentSelection.Key, out stain)) var found = _stainData.TryGetValue(stainId, out var stain);
data.SetStain(stain.RowIndex); var change = small
else if (_stainCombo.CurrentSelection.Key == Stain.None.RowIndex) ? _stainCombo.Draw($"##stain{data.Slot}", stain.RgbaColor, stain.Name, found, stain.Gloss)
data.SetStain(Stain.None.RowIndex); : _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)) if (change)
data.SetStain(newStain); 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) private void DrawItem(in EquipDrawData data, out string label, bool small, bool clear, bool open)

View file

@ -7,11 +7,11 @@ namespace Glamourer.Gui;
public class GlamourerWindowSystem : IDisposable public class GlamourerWindowSystem : IDisposable
{ {
private readonly WindowSystem _windowSystem = new("Glamourer"); private readonly WindowSystem _windowSystem = new("Glamourer");
private readonly UiBuilder _uiBuilder; private readonly IUiBuilder _uiBuilder;
private readonly MainWindow _ui; private readonly MainWindow _ui;
private readonly PenumbraChangedItemTooltip _penumbraTooltip; 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) Configuration config, UnlocksTab unlocksTab, GlamourerChangelog changelog, DesignQuickBar quick)
{ {
_uiBuilder = uiBuilder; _uiBuilder = uiBuilder;

View file

@ -1,4 +1,4 @@
using Dalamud.Interface.Internal.Notifications; using Dalamud.Interface.ImGuiNotification;
using Dalamud.Interface.Windowing; using Dalamud.Interface.Windowing;
using Dalamud.Plugin; using Dalamud.Plugin;
using Glamourer.Designs; using Glamourer.Designs;
@ -64,7 +64,7 @@ public class MainWindow : Window, IDisposable
public TabType SelectTab; 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, DebugTab debugTab, AutomationTab automation, UnlocksTab unlocks, TabSelected @event, MessagesTab messages, DesignQuickBar quickBar,
NpcTab npcs, MainWindowPosition position, PenumbraService penumbra) NpcTab npcs, MainWindowPosition position, PenumbraService penumbra)
: base("GlamourerMainWindow") : base("GlamourerMainWindow")

View file

@ -1,6 +1,6 @@
using Dalamud.Game.ClientState.Conditions; using Dalamud.Game.ClientState.Conditions;
using Dalamud.Interface; using Dalamud.Interface;
using Dalamud.Interface.Internal.Notifications; using Dalamud.Interface.ImGuiNotification;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.Game;
using Glamourer.Automation; using Glamourer.Automation;
@ -208,7 +208,7 @@ public class ActorPanel
var data = EquipDrawData.FromState(_stateManager, _state!, slot); var data = EquipDrawData.FromState(_stateManager, _state!, slot);
_equipmentDrawer.DrawEquip(data); _equipmentDrawer.DrawEquip(data);
if (usedAllStain) if (usedAllStain)
_stateManager.ChangeStain(_state, slot, newAllStain, ApplySettings.Manual); _stateManager.ChangeStains(_state, slot, newAllStain, ApplySettings.Manual);
} }
var mainhand = EquipDrawData.FromState(_stateManager, _state, EquipSlot.MainHand); var mainhand = EquipDrawData.FromState(_stateManager, _state, EquipSlot.MainHand);

View file

@ -87,8 +87,8 @@ public class ActiveStatePanel(StateManager _stateManager, ObjectManager _objectM
foreach (var slot in EquipSlotExtensions.EqdpSlots.Prepend(EquipSlot.OffHand).Prepend(EquipSlot.MainHand)) 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]); 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.BaseData.Stain(slot).ToString());
ImGuiUtil.DrawTableColumn(state.ModelData.Stain(slot).Id.ToString()); ImGuiUtil.DrawTableColumn(state.ModelData.Stain(slot).ToString());
ImGuiUtil.DrawTableColumn(state.Sources[slot, true].ToString()); ImGuiUtil.DrawTableColumn(state.Sources[slot, true].ToString());
} }

View file

@ -8,8 +8,10 @@ using Glamourer.Services;
using Glamourer.State; using Glamourer.State;
using ImGuiNET; using ImGuiNET;
using OtterGui; using OtterGui;
using Penumbra.GameData;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Gui.Debug; using Penumbra.GameData.Gui.Debug;
using Penumbra.GameData.Structs;
namespace Glamourer.Gui.Tabs.DebugTab; namespace Glamourer.Gui.Tabs.DebugTab;
@ -51,7 +53,7 @@ public unsafe class GlamourPlatePanel : IGameDataDrawer
using (ImRaii.Group()) using (ImRaii.Group())
{ {
ImGuiUtil.CopyOnClickSelectable($"0x{(ulong)manager:X}"); 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.TextUnformatted(manager == null ? "-" : manager->GlamourPlatesRequested.ToString());
ImGui.SameLine(); ImGui.SameLine();
if (ImGui.SmallButton("Request Update")) if (ImGui.SmallButton("Request Update"))
@ -67,13 +69,13 @@ public unsafe class GlamourPlatePanel : IGameDataDrawer
var (identifier, data) = _objects.PlayerData; var (identifier, data) = _objects.PlayerData;
var enabled = data.Valid && _state.GetOrCreate(identifier, data.Objects[0], out state); 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}"); using var tree = ImRaii.TreeNode($"Plate #{i + 1:D2}");
if (!tree) if (!tree)
continue; 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)) if (ImGuiUtil.DrawDisabledButton("Apply to Player", Vector2.Zero, string.Empty, !enabled))
{ {
var design = CreateDesign(plate); var design = CreateDesign(plate);
@ -90,12 +92,12 @@ public unsafe class GlamourPlatePanel : IGameDataDrawer
using (ImRaii.Group()) using (ImRaii.Group())
{ {
foreach (var (_, index) in EquipSlotExtensions.FullSlots.WithIndex()) 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<MirageManager*, void> _requestUpdate = null!; private readonly delegate* unmanaged<MirageManager*, void> _requestUpdate = null!;
public void RequestGlamour() public void RequestGlamour()
@ -126,7 +128,7 @@ public unsafe class GlamourPlatePanel : IGameDataDrawer
continue; continue;
design.GetDesignDataRef().SetItem(slot, item); design.GetDesignDataRef().SetItem(slot, item);
design.GetDesignDataRef().SetStain(slot, plate.StainIds[index]); design.GetDesignDataRef().SetStain(slot, StainIds.FromGlamourPlate(plate, index));
design.ApplyEquip |= slot.ToBothFlags(); design.ApplyEquip |= slot.ToBothFlags();
} }

View file

@ -43,8 +43,8 @@ public unsafe class InventoryPanel : IGameDataDrawer
} }
else else
{ {
ImGuiUtil.DrawTableColumn(item->ItemID.ToString()); ImGuiUtil.DrawTableColumn(item->ItemId.ToString());
ImGuiUtil.DrawTableColumn(item->GlamourID.ToString()); ImGuiUtil.DrawTableColumn(item->GlamourId.ToString());
ImGui.TableNextColumn(); ImGui.TableNextColumn();
ImGuiUtil.CopyOnClickSelectable($"0x{(ulong)item:X}"); ImGuiUtil.CopyOnClickSelectable($"0x{(ulong)item:X}");
} }

View file

@ -10,7 +10,7 @@ using OtterGui.Services;
namespace Glamourer.Gui.Tabs.DebugTab.IpcTester; namespace Glamourer.Gui.Tabs.DebugTab.IpcTester;
public class DesignIpcTester(DalamudPluginInterface pluginInterface) : IUiService public class DesignIpcTester(IDalamudPluginInterface pluginInterface) : IUiService
{ {
private Dictionary<Guid, string> _designs = []; private Dictionary<Guid, string> _designs = [];
private int _gameObjectIndex; private int _gameObjectIndex;

View file

@ -7,7 +7,7 @@ using Penumbra.GameData.Gui.Debug;
namespace Glamourer.Gui.Tabs.DebugTab.IpcTester; namespace Glamourer.Gui.Tabs.DebugTab.IpcTester;
public class IpcTesterPanel( public class IpcTesterPanel(
DalamudPluginInterface pluginInterface, IDalamudPluginInterface pluginInterface,
DesignIpcTester designs, DesignIpcTester designs,
ItemsIpcTester items, ItemsIpcTester items,
StateIpcTester state, StateIpcTester state,

View file

@ -11,7 +11,7 @@ using Penumbra.GameData.Structs;
namespace Glamourer.Gui.Tabs.DebugTab.IpcTester; namespace Glamourer.Gui.Tabs.DebugTab.IpcTester;
public class ItemsIpcTester(DalamudPluginInterface pluginInterface) : IUiService public class ItemsIpcTester(IDalamudPluginInterface pluginInterface) : IUiService
{ {
private int _gameObjectIndex; private int _gameObjectIndex;
private string _gameObjectName = string.Empty; private string _gameObjectName = string.Empty;
@ -40,12 +40,12 @@ public class ItemsIpcTester(DalamudPluginInterface pluginInterface) : IUiService
IpcTesterHelpers.DrawIntro(SetItem.Label); IpcTesterHelpers.DrawIntro(SetItem.Label);
if (ImGui.Button("Set##Idx")) 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); _flags);
IpcTesterHelpers.DrawIntro(SetItemName.Label); IpcTesterHelpers.DrawIntro(SetItemName.Label);
if (ImGui.Button("Set##Name")) 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); _flags);
} }

View file

@ -18,7 +18,7 @@ namespace Glamourer.Gui.Tabs.DebugTab.IpcTester;
public class StateIpcTester : IUiService, IDisposable public class StateIpcTester : IUiService, IDisposable
{ {
private readonly DalamudPluginInterface _pluginInterface; private readonly IDalamudPluginInterface _pluginInterface;
private int _gameObjectIndex; private int _gameObjectIndex;
private string _gameObjectName = string.Empty; private string _gameObjectName = string.Empty;
@ -41,7 +41,7 @@ public class StateIpcTester : IUiService, IDisposable
private int _numUnlocked; private int _numUnlocked;
public StateIpcTester(DalamudPluginInterface pluginInterface) public StateIpcTester(IDalamudPluginInterface pluginInterface)
{ {
_pluginInterface = pluginInterface; _pluginInterface = pluginInterface;
StateChanged = Api.IpcSubscribers.StateChangedWithType.Subscriber(_pluginInterface, OnStateChanged); StateChanged = Api.IpcSubscribers.StateChangedWithType.Subscriber(_pluginInterface, OnStateChanged);

View file

@ -5,6 +5,8 @@ using Glamourer.Interop.Structs;
using ImGuiNET; using ImGuiNET;
using OtterGui; using OtterGui;
using OtterGui.Raii; using OtterGui.Raii;
using OtterGui.Text;
using Penumbra.GameData.DataContainers;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Gui.Debug; using Penumbra.GameData.Gui.Debug;
using Penumbra.GameData.Interop; using Penumbra.GameData.Interop;
@ -18,7 +20,8 @@ public unsafe class ModelEvaluationPanel(
VisorService _visorService, VisorService _visorService,
UpdateSlotService _updateSlotService, UpdateSlotService _updateSlotService,
ChangeCustomizeService _changeCustomizeService, ChangeCustomizeService _changeCustomizeService,
CrestService _crestService) : IGameDataDrawer CrestService _crestService,
DictGlasses _glasses) : IGameDataDrawer
{ {
public string Label public string Label
=> "Model Evaluation"; => "Model Evaluation";
@ -177,7 +180,7 @@ public unsafe class ModelEvaluationPanel(
{ {
using var id = ImRaii.PushId("Wetness"); using var id = ImRaii.PushId("Wetness");
ImGuiUtil.DrawTableColumn("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 var modelString = model.IsCharacterBase
? $"{model.AsCharacterBase->SwimmingWetness:F4} Swimming\n" ? $"{model.AsCharacterBase->SwimmingWetness:F4} Swimming\n"
+ $"{model.AsCharacterBase->WeatherWetness:F4} Weather\n" + $"{model.AsCharacterBase->WeatherWetness:F4} Weather\n"
@ -190,13 +193,13 @@ public unsafe class ModelEvaluationPanel(
return; return;
if (ImGui.SmallButton("GPose On")) if (ImGui.SmallButton("GPose On"))
actor.AsCharacter->IsGPoseWet = true; actor.IsGPoseWet = true;
ImGui.SameLine(); ImGui.SameLine();
if (ImGui.SmallButton("GPose Off")) if (ImGui.SmallButton("GPose Off"))
actor.AsCharacter->IsGPoseWet = false; actor.IsGPoseWet = false;
ImGui.SameLine(); ImGui.SameLine();
if (ImGui.SmallButton("GPose Toggle")) if (ImGui.SmallButton("GPose Toggle"))
actor.AsCharacter->IsGPoseWet = !actor.AsCharacter->IsGPoseWet; actor.IsGPoseWet = !actor.IsGPoseWet;
} }
private void DrawEquip(Actor actor, Model model) private void DrawEquip(Actor actor, Model model)
@ -214,14 +217,39 @@ public unsafe class ModelEvaluationPanel(
if (ImGui.SmallButton("Change Piece")) if (ImGui.SmallButton("Change Piece"))
_updateSlotService.UpdateArmor(model, slot, _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(); ImGui.SameLine();
if (ImGui.SmallButton("Change Stain")) if (ImGui.SmallButton("Change Stain"))
_updateSlotService.UpdateStain(model, slot, 5); _updateSlotService.UpdateStain(model, slot, new StainIds(5, 7));
ImGui.SameLine(); ImGui.SameLine();
if (ImGui.SmallButton("Reset")) if (ImGui.SmallButton("Reset"))
_updateSlotService.UpdateSlot(model, slot, actor.GetArmor(slot)); _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) private void DrawCustomize(Actor actor, Model model)

View file

@ -1,5 +1,5 @@
using Dalamud.Interface; using Dalamud.Interface;
using Dalamud.Interface.Internal.Notifications; using Dalamud.Interface.ImGuiNotification;
using Glamourer.Designs; using Glamourer.Designs;
using Glamourer.Services; using Glamourer.Services;
using ImGuiNET; using ImGuiNET;

View file

@ -1,5 +1,5 @@
using Dalamud.Interface; using Dalamud.Interface;
using Dalamud.Interface.Internal.Notifications; using Dalamud.Interface.ImGuiNotification;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using Glamourer.Designs; using Glamourer.Designs;
using Glamourer.Events; using Glamourer.Events;

View file

@ -1,6 +1,6 @@
using Dalamud.Interface; using Dalamud.Interface;
using Dalamud.Interface.ImGuiFileDialog; using Dalamud.Interface.ImGuiFileDialog;
using Dalamud.Interface.Internal.Notifications; using Dalamud.Interface.ImGuiNotification;
using FFXIVClientStructs.FFXIV.Client.System.Framework; using FFXIVClientStructs.FFXIV.Client.System.Framework;
using Glamourer.Automation; using Glamourer.Automation;
using Glamourer.Designs; using Glamourer.Designs;
@ -106,7 +106,7 @@ public class DesignPanel
var data = EquipDrawData.FromDesign(_manager, _selector.Selected!, slot); var data = EquipDrawData.FromDesign(_manager, _selector.Selected!, slot);
_equipmentDrawer.DrawEquip(data); _equipmentDrawer.DrawEquip(data);
if (usedAllStain) if (usedAllStain)
_manager.ChangeStain(_selector.Selected, slot, newAllStain); _manager.ChangeStains(_selector.Selected, slot, newAllStain);
} }
var mainhand = EquipDrawData.FromDesign(_manager, _selector.Selected!, EquipSlot.MainHand); var mainhand = EquipDrawData.FromDesign(_manager, _selector.Selected!, EquipSlot.MainHand);
@ -453,7 +453,7 @@ public class DesignPanel
} }
private static unsafe string GetUserPath() private static unsafe string GetUserPath()
=> Framework.Instance()->UserPath; => Framework.Instance()->UserPathString;
private sealed class LockButton(DesignPanel panel) : Button private sealed class LockButton(DesignPanel panel) : Button

View file

@ -1,4 +1,4 @@
using Dalamud.Interface.Internal.Notifications; using Dalamud.Interface.ImGuiNotification;
using Dalamud.Interface.Utility; using Dalamud.Interface.Utility;
using Glamourer.Designs; using Glamourer.Designs;
using Glamourer.Interop; using Glamourer.Interop;

View file

@ -1,5 +1,5 @@
using Dalamud.Interface; using Dalamud.Interface;
using Dalamud.Interface.Internal.Notifications; using Dalamud.Interface.ImGuiNotification;
using Dalamud.Interface.Utility; using Dalamud.Interface.Utility;
using Dalamud.Utility; using Dalamud.Utility;
using Glamourer.Designs; using Glamourer.Designs;

View file

@ -1,5 +1,5 @@
using Dalamud.Interface; using Dalamud.Interface;
using Dalamud.Interface.Internal.Notifications; using Dalamud.Interface.ImGuiNotification;
using FFXIVClientStructs.FFXIV.Client.Game.Object; using FFXIVClientStructs.FFXIV.Client.Game.Object;
using Glamourer.Designs; using Glamourer.Designs;
using Glamourer.Gui.Customization; using Glamourer.Gui.Customization;

View file

@ -10,7 +10,6 @@ using Glamourer.Interop.PalettePlus;
using ImGuiNET; using ImGuiNET;
using OtterGui; using OtterGui;
using OtterGui.Raii; using OtterGui.Raii;
using OtterGui.Text;
using OtterGui.Widgets; using OtterGui.Widgets;
namespace Glamourer.Gui.Tabs.SettingsTab; namespace Glamourer.Gui.Tabs.SettingsTab;
@ -19,7 +18,7 @@ public class SettingsTab(
Configuration config, Configuration config,
DesignFileSystemSelector selector, DesignFileSystemSelector selector,
ContextMenuService contextMenuService, ContextMenuService contextMenuService,
UiBuilder uiBuilder, IUiBuilder uiBuilder,
GlamourerChangelog changelog, GlamourerChangelog changelog,
IKeyState keys, IKeyState keys,
DesignColorUi designColorUi, DesignColorUi designColorUi,

View file

@ -116,20 +116,20 @@ public class UnlockOverview
var unlocked = _customizeUnlocks.IsUnlocked(customize, out var time); var unlocked = _customizeUnlocks.IsUnlocked(customize, out var time);
var icon = _customizations.Manager.GetIcon(customize.IconId); var icon = _customizations.Manager.GetIcon(customize.IconId);
var hasIcon = icon.TryGetWrap(out var wrap, out _);
ImGui.Image(icon.ImGuiHandle, iconSize, Vector2.Zero, Vector2.One, 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(), ImGui.GetWindowDrawList().AddRect(ImGui.GetItemRectMin(), ImGui.GetItemRectMax(), ColorId.FavoriteStarOn.Value(),
12 * ImGuiHelpers.GlobalScale, ImDrawFlags.RoundCornersAll, 6 * ImGuiHelpers.GlobalScale); 12 * ImGuiHelpers.GlobalScale, ImDrawFlags.RoundCornersAll, 6 * ImGuiHelpers.GlobalScale);
if (ImGui.IsItemHovered()) if (hasIcon && ImGui.IsItemHovered())
{ {
using var tt = ImRaii.Tooltip(); 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) 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(unlockData.Name);
ImGui.TextUnformatted($"{customize.Index.ToDefaultName()} {customize.Value.Value}"); ImGui.TextUnformatted($"{customize.Index.ToDefaultName()} {customize.Value.Value}");
ImGui.TextUnformatted(unlocked ? $"Unlocked on {time:g}" : "Not unlocked."); 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)); 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)) if (_favorites.Contains(item))
ImGui.GetWindowDrawList().AddRect(ImGui.GetItemRectMin(), ImGui.GetItemRectMax(), ColorId.FavoriteStarOn.Value(), ImGui.GetWindowDrawList().AddRect(ImGui.GetItemRectMin(), ImGui.GetItemRectMax(), ColorId.FavoriteStarOn.Value(),
2 * ImGuiHelpers.GlobalScale, ImDrawFlags.RoundCornersAll, 4 * ImGuiHelpers.GlobalScale); 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}"); ImGui.TextUnformatted($"For all {_jobs.AllJobGroups[item.JobRestrictions.Id].Name} of at least Level {item.Level}");
} }
if (item.Flags.HasFlag(ItemFlags.IsDyable)) if (item.Flags.HasFlag(ItemFlags.IsDyable1))
ImGui.TextUnformatted("Dyable"); ImGui.TextUnformatted(item.Flags.HasFlag(ItemFlags.IsDyable2) ? "Dyable (2 Slots)" : "Dyable");
if (item.Flags.HasFlag(ItemFlags.IsTradable)) if (item.Flags.HasFlag(ItemFlags.IsTradable))
ImGui.TextUnformatted("Tradable"); ImGui.TextUnformatted("Tradable");
if (item.Flags.HasFlag(ItemFlags.IsCrestWorthy)) if (item.Flags.HasFlag(ItemFlags.IsCrestWorthy))

View file

@ -1,4 +1,5 @@
using Dalamud.Game.Text.SeStringHandling; using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Interface;
using Dalamud.Interface.Utility; using Dalamud.Interface.Utility;
using Glamourer.Events; using Glamourer.Events;
using Glamourer.Interop; using Glamourer.Interop;
@ -249,7 +250,7 @@ public class UnlockTable : Table<EquipItem>, IDisposable
=> 70 * ImGuiHelpers.GlobalScale; => 70 * ImGuiHelpers.GlobalScale;
public override int ToValue(EquipItem item) public override int ToValue(EquipItem item)
=> (int) item.Id.Id; => (int)item.Id.Id;
public ItemIdColumn() public ItemIdColumn()
: base(ComparisonMethod.Equal) : base(ComparisonMethod.Equal)
@ -378,13 +379,68 @@ public class UnlockTable : Table<EquipItem>, IDisposable
} }
} }
private sealed class DyableColumn : YesNoColumn<EquipItem>
{
public DyableColumn()
=> Tooltip = "Whether the item is dyable.";
protected override bool GetValue(EquipItem item) private sealed class DyableColumn : ColumnFlags<DyableColumn.Dyable, EquipItem>
=> item.Flags.HasFlag(ItemFlags.IsDyable); {
[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<EquipItem> private sealed class TradableColumn : YesNoColumn<EquipItem>

View file

@ -17,7 +17,7 @@ public unsafe class ChangeCustomizeService : EventWrapperRef2<Model, CustomizeAr
{ {
private readonly PenumbraReloaded _penumbraReloaded; private readonly PenumbraReloaded _penumbraReloaded;
private readonly IGameInteropProvider _interop; private readonly IGameInteropProvider _interop;
private readonly delegate* unmanaged[Stdcall]<Human*, byte*, bool, bool> _original; private readonly delegate* unmanaged<Human*, byte*, bool, bool> _original;
private readonly Post _postEvent = new(); private readonly Post _postEvent = new();

View file

@ -60,7 +60,7 @@ public sealed class CharaFile
return; return;
data.SetItem(slot, item); data.SetItem(slot, item);
data.SetStain(slot, dye); data.SetStain(slot, new StainIds(dye));
flags |= slot.ToFlag(); flags |= slot.ToFlag();
flags |= slot.ToStainFlag(); flags |= slot.ToStainFlag();
} }
@ -79,7 +79,7 @@ public sealed class CharaFile
return; return;
data.SetItem(slot, item); data.SetItem(slot, item);
data.SetStain(slot, dye); data.SetStain(slot, new StainIds(dye));
flags |= slot.ToFlag(); flags |= slot.ToFlag();
flags |= slot.ToStainFlag(); flags |= slot.ToStainFlag();
} }

View file

@ -61,7 +61,7 @@ public sealed class CmaFile
var armor = ((CharacterArmor*)ptr)[idx]; var armor = ((CharacterArmor*)ptr)[idx];
var item = items.Identify(slot, armor.Set, armor.Variant); var item = items.Identify(slot, armor.Set, armor.Variant);
data.SetItem(slot, item); data.SetItem(slot, item);
data.SetStain(slot, armor.Stain); data.SetStain(slot, armor.Stains);
} }
data.Customize.Read(ptr); data.Customize.Read(ptr);
@ -74,7 +74,7 @@ public sealed class CmaFile
if (mainhand == null) if (mainhand == null)
{ {
data.SetItem(EquipSlot.MainHand, items.DefaultSword); data.SetItem(EquipSlot.MainHand, items.DefaultSword);
data.SetStain(EquipSlot.MainHand, 0); data.SetStain(EquipSlot.MainHand, StainIds.None);
return; return;
} }
@ -85,7 +85,7 @@ public sealed class CmaFile
var item = items.Identify(EquipSlot.MainHand, set, type, variant); var item = items.Identify(EquipSlot.MainHand, set, type, variant);
data.SetItem(EquipSlot.MainHand, item.Valid ? item : items.DefaultSword); 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) private static void ParseOffHand(ItemManager items, JObject jObj, ref DesignData data)
@ -95,7 +95,7 @@ public sealed class CmaFile
if (offhand == null) if (offhand == null)
{ {
data.SetItem(EquipSlot.MainHand, defaultOffhand); 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; return;
} }
@ -106,6 +106,6 @@ public sealed class CmaFile
var item = items.Identify(EquipSlot.OffHand, set, type, variant, data.MainhandType); var item = items.Identify(EquipSlot.OffHand, set, type, variant, data.MainhandType);
data.SetItem(EquipSlot.OffHand, item.Valid ? item : defaultOffhand); 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));
} }
} }

View file

@ -20,7 +20,7 @@ public class ContextMenuService : IDisposable
private readonly ObjectManager _objects; private readonly ObjectManager _objects;
private readonly IGameGui _gameGui; private readonly IGameGui _gameGui;
private EquipItem _lastItem; private EquipItem _lastItem;
private StainId _lastStain; private readonly StainId[] _lastStains = new StainId[StainId.NumStains];
private readonly MenuItem _inventoryItem; 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) if (args.MenuType is ContextMenuType.Inventory)
{ {
var arg = (MenuTargetInventory)args.Target; var arg = (MenuTargetInventory)args.Target;
if (arg.TargetItem.HasValue && HandleItem(arg.TargetItem.Value.ItemId)) 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); args.AddMenuItem(_inventoryItem);
} }
} }
@ -77,7 +78,8 @@ public class ContextMenuService : IDisposable
if (HandleItem(*(ItemId*)(agent + ChatLogContextItemId))) if (HandleItem(*(ItemId*)(agent + ChatLogContextItemId)))
{ {
_lastStain = 0; for (var i = 0; i < _lastStains.Length; ++i)
_lastStains[i] = 0;
args.AddMenuItem(_inventoryItem); args.AddMenuItem(_inventoryItem);
} }
@ -96,7 +98,7 @@ public class ContextMenuService : IDisposable
public void Dispose() public void Dispose()
=> Disable(); => Disable();
private void OnClick(MenuItemClickedArgs _) private void OnClick(IMenuItemClickedArgs _)
{ {
var (id, playerData) = _objects.PlayerData; var (id, playerData) = _objects.PlayerData;
if (!playerData.Valid) if (!playerData.Valid)
@ -106,15 +108,15 @@ public class ContextMenuService : IDisposable
return; return;
var slot = _lastItem.Type.ToSlot(); 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()) if (!_lastItem.Type.ValidOffhand().IsOffhandType())
return; return;
if (_lastItem.PrimaryId.Id is > 1600 and < 1651 if (_lastItem.PrimaryId.Id is > 1600 and < 1651
&& _items.ItemData.TryGetValue(_lastItem.ItemId, EquipSlot.Hands, out var gauntlets)) && _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)) 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) private bool HandleItem(ItemId id)

View file

@ -4,6 +4,7 @@ using Dalamud.Utility.Signatures;
using FFXIVClientStructs.FFXIV.Client.Game.Character; using FFXIVClientStructs.FFXIV.Client.Game.Character;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using OtterGui.Classes; using OtterGui.Classes;
using Penumbra.GameData;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Interop; using Penumbra.GameData.Interop;
@ -30,9 +31,9 @@ public sealed unsafe class CrestService : EventWrapperRef3<Actor, CrestFlag, boo
{ {
interop.InitializeFromAttributes(this); interop.InitializeFromAttributes(this);
_humanSetFreeCompanyCrestVisibleOnSlot = _humanSetFreeCompanyCrestVisibleOnSlot =
interop.HookFromAddress<SetCrestDelegateIntern>(_humanVTable[96], HumanSetFreeCompanyCrestVisibleOnSlotDetour); interop.HookFromAddress<SetCrestDelegateIntern>(_humanVTable[109], HumanSetFreeCompanyCrestVisibleOnSlotDetour);
_weaponSetFreeCompanyCrestVisibleOnSlot = _weaponSetFreeCompanyCrestVisibleOnSlot =
interop.HookFromAddress<SetCrestDelegateIntern>(_weaponVTable[96], WeaponSetFreeCompanyCrestVisibleOnSlotDetour); interop.HookFromAddress<SetCrestDelegateIntern>(_weaponVTable[109], WeaponSetFreeCompanyCrestVisibleOnSlotDetour);
_humanSetFreeCompanyCrestVisibleOnSlot.Enable(); _humanSetFreeCompanyCrestVisibleOnSlot.Enable();
_weaponSetFreeCompanyCrestVisibleOnSlot.Enable(); _weaponSetFreeCompanyCrestVisibleOnSlot.Enable();
_crestChangeHook.Enable(); _crestChangeHook.Enable();
@ -63,7 +64,7 @@ public sealed unsafe class CrestService : EventWrapperRef3<Actor, CrestFlag, boo
private delegate void CrestChangeDelegate(Character* character, byte crestFlags); private delegate void CrestChangeDelegate(Character* character, byte crestFlags);
[Signature("E8 ?? ?? ?? ?? 48 8B 55 ?? 49 8B CE E8", DetourName = nameof(CrestChangeDetour))] [Signature(Sigs.CrestChange, DetourName = nameof(CrestChangeDetour))]
private readonly Hook<CrestChangeDelegate> _crestChangeHook = null!; private readonly Hook<CrestChangeDelegate> _crestChangeHook = null!;
private void CrestChangeDetour(Character* character, byte crestFlags) private void CrestChangeDetour(Character* character, byte crestFlags)
@ -96,8 +97,7 @@ public sealed unsafe class CrestService : EventWrapperRef3<Actor, CrestFlag, boo
if (!model.IsHuman) if (!model.IsHuman)
return false; return false;
var getter = (delegate* unmanaged<Human*, byte, byte>)((nint*)model.AsCharacterBase->VTable)[95]; return model.AsHuman->IsFreeCompanyCrestVisibleOnSlot(index) != 0;
return getter(model.AsHuman, index) != 0;
} }
case CrestType.Offhand: case CrestType.Offhand:
{ {
@ -105,8 +105,7 @@ public sealed unsafe class CrestService : EventWrapperRef3<Actor, CrestFlag, boo
if (!model.IsWeapon) if (!model.IsWeapon)
return false; return false;
var getter = (delegate* unmanaged<Weapon*, byte, byte>)((nint*)model.AsCharacterBase->VTable)[95]; return model.AsWeapon->IsFreeCompanyCrestVisibleOnSlot(index) != 0;
return getter(model.AsWeapon, index) != 0;
} }
} }
@ -117,10 +116,10 @@ public sealed unsafe class CrestService : EventWrapperRef3<Actor, CrestFlag, boo
private delegate void SetCrestDelegateIntern(DrawObject* drawObject, byte slot, byte visible); private delegate void SetCrestDelegateIntern(DrawObject* drawObject, byte slot, byte visible);
[Signature(global::Penumbra.GameData.Sigs.HumanVTable, ScanType = ScanType.StaticAddress)] [Signature(Sigs.HumanVTable, ScanType = ScanType.StaticAddress)]
private readonly nint* _humanVTable = null!; private readonly nint* _humanVTable = null!;
[Signature(global::Penumbra.GameData.Sigs.WeaponVTable, ScanType = ScanType.StaticAddress)] [Signature(Sigs.WeaponVTable, ScanType = ScanType.StaticAddress)]
private readonly nint* _weaponVTable = null!; private readonly nint* _weaponVTable = null!;
private readonly Hook<SetCrestDelegateIntern> _humanSetFreeCompanyCrestVisibleOnSlot; private readonly Hook<SetCrestDelegateIntern> _humanSetFreeCompanyCrestVisibleOnSlot;

View file

@ -1,5 +1,5 @@
using Dalamud.Interface.DragDrop; using Dalamud.Interface.DragDrop;
using Dalamud.Interface.Internal.Notifications; using Dalamud.Interface.ImGuiNotification;
using Glamourer.Designs; using Glamourer.Designs;
using Glamourer.Interop.CharaFile; using Glamourer.Interop.CharaFile;
using Glamourer.Services; using Glamourer.Services;

View file

@ -12,9 +12,9 @@ namespace Glamourer.Interop;
public sealed unsafe class InventoryService : IDisposable, IRequiredService public sealed unsafe class InventoryService : IDisposable, IRequiredService
{ {
private readonly MovedEquipment _movedItemsEvent; private readonly MovedEquipment _movedItemsEvent;
private readonly EquippedGearset _gearsetEvent; private readonly EquippedGearset _gearsetEvent;
private readonly List<(EquipSlot, uint, StainId)> _itemList = new(12); private readonly List<(EquipSlot, uint, StainIds)> _itemList = new(12);
public InventoryService(MovedEquipment movedItemsEvent, IGameInteropProvider interop, EquippedGearset gearsetEvent) public InventoryService(MovedEquipment movedItemsEvent, IGameInteropProvider interop, EquippedGearset gearsetEvent)
{ {
@ -60,56 +60,56 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService
if (glamourPlateId != 0) 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) if (item.ItemId == 0)
_itemList.Add((slot, 0, 0)); _itemList.Add((slot, 0, StainIds.None));
else if (glamourId != 0) else if (glamourId != 0)
_itemList.Add((slot, glamourId, glamourStain)); _itemList.Add((slot, glamourId, glamourStain));
else if (item.GlamourId != 0) else if (item.GlamourId != 0)
_itemList.Add((slot, item.GlamourId, item.Stain)); _itemList.Add((slot, item.GlamourId, StainIds.FromGearsetItem(item)));
else 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]; var plate = MirageManager.Instance()->GlamourPlates[glamourPlateId - 1];
Add(EquipSlot.MainHand, plate.ItemIds[0], plate.StainIds[0], ref entry->ItemsSpan[0]); Add(EquipSlot.MainHand, plate.ItemIds[0], StainIds.FromGlamourPlate(plate, 0), ref entry->Items[0]);
Add(EquipSlot.OffHand, plate.ItemIds[1], plate.StainIds[1], ref entry->ItemsSpan[1]); Add(EquipSlot.OffHand, plate.ItemIds[1], StainIds.FromGlamourPlate(plate, 1), ref entry->Items[1]);
Add(EquipSlot.Head, plate.ItemIds[2], plate.StainIds[2], ref entry->ItemsSpan[2]); Add(EquipSlot.Head, plate.ItemIds[2], StainIds.FromGlamourPlate(plate, 2), ref entry->Items[2]);
Add(EquipSlot.Body, plate.ItemIds[3], plate.StainIds[3], ref entry->ItemsSpan[3]); Add(EquipSlot.Body, plate.ItemIds[3], StainIds.FromGlamourPlate(plate, 3), ref entry->Items[3]);
Add(EquipSlot.Hands, plate.ItemIds[4], plate.StainIds[4], ref entry->ItemsSpan[5]); Add(EquipSlot.Hands, plate.ItemIds[4], StainIds.FromGlamourPlate(plate, 4), ref entry->Items[5]);
Add(EquipSlot.Legs, plate.ItemIds[5], plate.StainIds[5], ref entry->ItemsSpan[6]); Add(EquipSlot.Legs, plate.ItemIds[5], StainIds.FromGlamourPlate(plate, 5), ref entry->Items[6]);
Add(EquipSlot.Feet, plate.ItemIds[6], plate.StainIds[6], ref entry->ItemsSpan[7]); Add(EquipSlot.Feet, plate.ItemIds[6], StainIds.FromGlamourPlate(plate, 6), ref entry->Items[7]);
Add(EquipSlot.Ears, plate.ItemIds[7], plate.StainIds[7], ref entry->ItemsSpan[8]); Add(EquipSlot.Ears, plate.ItemIds[7], StainIds.FromGlamourPlate(plate, 7), ref entry->Items[8]);
Add(EquipSlot.Neck, plate.ItemIds[8], plate.StainIds[8], ref entry->ItemsSpan[9]); Add(EquipSlot.Neck, plate.ItemIds[8], StainIds.FromGlamourPlate(plate, 8), ref entry->Items[9]);
Add(EquipSlot.Wrists, plate.ItemIds[9], plate.StainIds[9], ref entry->ItemsSpan[10]); Add(EquipSlot.Wrists, plate.ItemIds[9], StainIds.FromGlamourPlate(plate, 9), ref entry->Items[10]);
Add(EquipSlot.RFinger, plate.ItemIds[10], plate.StainIds[10], ref entry->ItemsSpan[11]); Add(EquipSlot.RFinger, plate.ItemIds[10], StainIds.FromGlamourPlate(plate, 10), ref entry->Items[11]);
Add(EquipSlot.LFinger, plate.ItemIds[11], plate.StainIds[11], ref entry->ItemsSpan[12]); Add(EquipSlot.LFinger, plate.ItemIds[11], StainIds.FromGlamourPlate(plate, 11), ref entry->Items[12]);
} }
else else
{ {
void Add(EquipSlot slot, ref RaptureGearsetModule.GearsetItem item) void Add(EquipSlot slot, ref RaptureGearsetModule.GearsetItem item)
{ {
if (item.ItemID == 0) if (item.ItemId == 0)
_itemList.Add((slot, 0, 0)); _itemList.Add((slot, 0, StainIds.None));
else if (item.GlamourId != 0) else if (item.GlamourId != 0)
_itemList.Add((slot, item.GlamourId, item.Stain)); _itemList.Add((slot, item.GlamourId, StainIds.FromGearsetItem(item)));
else 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.MainHand, ref entry->Items[0]);
Add(EquipSlot.OffHand, ref entry->ItemsSpan[1]); Add(EquipSlot.OffHand, ref entry->Items[1]);
Add(EquipSlot.Head, ref entry->ItemsSpan[2]); Add(EquipSlot.Head, ref entry->Items[2]);
Add(EquipSlot.Body, ref entry->ItemsSpan[3]); Add(EquipSlot.Body, ref entry->Items[3]);
Add(EquipSlot.Hands, ref entry->ItemsSpan[5]); Add(EquipSlot.Hands, ref entry->Items[5]);
Add(EquipSlot.Legs, ref entry->ItemsSpan[6]); Add(EquipSlot.Legs, ref entry->Items[6]);
Add(EquipSlot.Feet, ref entry->ItemsSpan[7]); Add(EquipSlot.Feet, ref entry->Items[7]);
Add(EquipSlot.Ears, ref entry->ItemsSpan[8]); Add(EquipSlot.Ears, ref entry->Items[8]);
Add(EquipSlot.Neck, ref entry->ItemsSpan[9]); Add(EquipSlot.Neck, ref entry->Items[9]);
Add(EquipSlot.Wrists, ref entry->ItemsSpan[10]); Add(EquipSlot.Wrists, ref entry->Items[10]);
Add(EquipSlot.RFinger, ref entry->ItemsSpan[11]); Add(EquipSlot.RFinger, ref entry->Items[11]);
Add(EquipSlot.LFinger, ref entry->ItemsSpan[12]); Add(EquipSlot.LFinger, ref entry->Items[12]);
} }
_movedItemsEvent.Invoke(_itemList.ToArray()); _movedItemsEvent.Invoke(_itemList.ToArray());
@ -155,7 +155,7 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService
return ret; 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; tuple = default;
if (sourceContainer is not InventoryType.EquippedItems) if (sourceContainer is not InventoryType.EquippedItems)
@ -165,12 +165,12 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService
if (slot is EquipSlot.Unknown) if (slot is EquipSlot.Unknown)
return false; return false;
tuple = (slot, 0u, 0); tuple = (slot, 0u, StainIds.None);
return true; return true;
} }
private static bool InvokeTarget(InventoryManager* manager, InventoryType targetContainer, uint targetSlot, private static bool InvokeTarget(InventoryManager* manager, InventoryType targetContainer, uint targetSlot,
out (EquipSlot, uint, StainId) tuple) out (EquipSlot, uint, StainIds) tuple)
{ {
tuple = default; tuple = default;
if (targetContainer is not InventoryType.EquippedItems) if (targetContainer is not InventoryType.EquippedItems)
@ -189,7 +189,7 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService
if (item == null) if (item == null)
return false; 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; return true;
} }

View file

@ -169,13 +169,13 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable
if (!actor.AsObject->IsCharacter()) if (!actor.AsObject->IsCharacter())
return false; return false;
if (actor.AsCharacter->DrawData.WeaponDataSpan[0].DrawObject == characterBase) if (actor.AsCharacter->DrawData.WeaponData[0].DrawObject == characterBase)
{ {
type = MaterialValueIndex.DrawObjectType.Mainhand; type = MaterialValueIndex.DrawObjectType.Mainhand;
return true; return true;
} }
if (actor.AsCharacter->DrawData.WeaponDataSpan[1].DrawObject == characterBase) if (actor.AsCharacter->DrawData.WeaponData[1].DrawObject == characterBase)
{ {
type = MaterialValueIndex.DrawObjectType.Offhand; type = MaterialValueIndex.DrawObjectType.Offhand;
return true; return true;
@ -199,10 +199,11 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable
/// </summary> /// </summary>
private static CharacterWeapon GetTempSlot(Weapon* weapon) private static CharacterWeapon GetTempSlot(Weapon* weapon)
{ {
// TODO: Fix offset
var changedData = *(void**)((byte*)weapon + 0x918); var changedData = *(void**)((byte*)weapon + 0x918);
if (changedData == null) 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]));
} }
} }

View file

@ -62,8 +62,8 @@ public readonly record struct MaterialValueIndex(
model = DrawObject switch model = DrawObject switch
{ {
DrawObjectType.Human => actor.Model, DrawObjectType.Human => actor.Model,
DrawObjectType.Mainhand => actor.IsCharacter ? actor.AsCharacter->DrawData.WeaponDataSpan[0].DrawObject : Model.Null, DrawObjectType.Mainhand => actor.IsCharacter ? actor.AsCharacter->DrawData.WeaponData[0].DrawObject : Model.Null,
DrawObjectType.Offhand => actor.IsCharacter ? actor.AsCharacter->DrawData.WeaponDataSpan[1].DrawObject : Model.Null, DrawObjectType.Offhand => actor.IsCharacter ? actor.AsCharacter->DrawData.WeaponData[1].DrawObject : Model.Null,
_ => Model.Null, _ => Model.Null,
}; };
return model.IsCharacterBase; return model.IsCharacterBase;

View file

@ -203,7 +203,7 @@ public struct MaterialValueState(
=> DrawData.Skeleton == rhsData.Skeleton => DrawData.Skeleton == rhsData.Skeleton
&& DrawData.Weapon == rhsData.Weapon && DrawData.Weapon == rhsData.Weapon
&& DrawData.Variant == rhsData.Variant && DrawData.Variant == rhsData.Variant
&& DrawData.Stain == rhsData.Stain && DrawData.Stains == rhsData.Stains
&& Game.NearEqual(rhsRow); && Game.NearEqual(rhsRow);
public readonly MaterialValueDesign Convert() public readonly MaterialValueDesign Convert()

View file

@ -4,6 +4,7 @@ using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
using OtterGui.Classes; using OtterGui.Classes;
using OtterGui.Services; using OtterGui.Services;
using Penumbra.GameData;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Files.MaterialStructs; using Penumbra.GameData.Files.MaterialStructs;
using Penumbra.GameData.Interop; using Penumbra.GameData.Interop;
@ -22,7 +23,7 @@ public sealed unsafe class PrepareColorSet
public PrepareColorSet(HookManager hooks) public PrepareColorSet(HookManager hooks)
: base("Prepare Color Set ") : base("Prepare Color Set ")
=> _task = hooks.CreateHook<Delegate>(Name, "40 55 56 41 56 48 83 EC ?? 80 BA", Detour, true); => _task = hooks.CreateHook<Delegate>(Name, Sigs.PrepareColorSet, Detour, true);
private readonly Task<Hook<Delegate>> _task; private readonly Task<Hook<Delegate>> _task;
@ -54,7 +55,7 @@ public sealed unsafe class PrepareColorSet
return _task.Result.Original(characterBase, material, stainId); 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) out LegacyColorTable table)
{ {
if (material->ColorTable == null) if (material->ColorTable == null)
@ -64,8 +65,9 @@ public sealed unsafe class PrepareColorSet
} }
var newTable = *(LegacyColorTable*)material->ColorTable; var newTable = *(LegacyColorTable*)material->ColorTable;
if (stainId.Id != 0) // TODO
characterBase->ReadStainingTemplate(material, stainId.Id, (Half*)(&newTable)); //if (stainIds.Stain1.Id != 0 || stainIds.Stain2.Id != 0)
// characterBase->ReadStainingTemplate(material, stainId.Id, (Half*)(&newTable));
table = newTable; table = newTable;
return true; return true;
} }
@ -84,21 +86,21 @@ public sealed unsafe class PrepareColorSet
return false; 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) switch (index.DrawObject)
{ {
case MaterialValueIndex.DrawObjectType.Human: 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: case MaterialValueIndex.DrawObjectType.Mainhand:
var mainhand = (Model)actor.AsCharacter->DrawData.WeaponDataSpan[1].DrawObject; var mainhand = (Model)actor.AsCharacter->DrawData.WeaponData[1].DrawObject;
return mainhand.IsWeapon ? (StainId)mainhand.AsWeapon->ModelUnknown : 0; return mainhand.IsWeapon ? StainIds.FromWeapon(*mainhand.AsWeapon) : StainIds.None;
case MaterialValueIndex.DrawObjectType.Offhand: case MaterialValueIndex.DrawObjectType.Offhand:
var offhand = (Model)actor.AsCharacter->DrawData.WeaponDataSpan[1].DrawObject; var offhand = (Model)actor.AsCharacter->DrawData.WeaponData[1].DrawObject;
return offhand.IsWeapon ? (StainId)offhand.AsWeapon->ModelUnknown : 0; return offhand.IsWeapon ? StainIds.FromWeapon(*offhand.AsWeapon) : StainIds.None;
default: return 0; default: return StainIds.None;
} }
} }
} }

View file

@ -49,11 +49,11 @@ public unsafe class MetaService : IDisposable
return; return;
// The function seems to not do anything if the head is 0, but also breaks for carbuncles turned human, sometimes? // 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) 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)); _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) public void SetWeaponState(Actor actor, bool value)
@ -72,7 +72,7 @@ public unsafe class MetaService : IDisposable
return; return;
} }
Actor actor = drawData->Parent; Actor actor = drawData->OwnerObject;
var v = value == 0; var v = value == 0;
_headGearEvent.Invoke(actor, ref v); _headGearEvent.Invoke(actor, ref v);
value = (byte)(v ? 0 : 1); value = (byte)(v ? 0 : 1);
@ -82,7 +82,7 @@ public unsafe class MetaService : IDisposable
private void HideWeaponsDetour(DrawDataContainer* drawData, bool value) private void HideWeaponsDetour(DrawDataContainer* drawData, bool value)
{ {
Actor actor = drawData->Parent; Actor actor = drawData->OwnerObject;
value = !value; value = !value;
_weaponEvent.Invoke(actor, ref value); _weaponEvent.Invoke(actor, ref value);
value = !value; value = !value;
@ -92,7 +92,7 @@ public unsafe class MetaService : IDisposable
private void ToggleVisorDetour(DrawDataContainer* drawData, bool value) private void ToggleVisorDetour(DrawDataContainer* drawData, bool value)
{ {
Actor actor = drawData->Parent; Actor actor = drawData->OwnerObject;
_visorEvent.Invoke(actor.Model, true, ref value); _visorEvent.Invoke(actor.Model, true, ref value);
Glamourer.Log.Verbose($"[MetaService] Toggle Visor triggered with 0x{(nint)drawData:X} {value} for {actor.Utf8Name}."); Glamourer.Log.Verbose($"[MetaService] Toggle Visor triggered with 0x{(nint)drawData:X} {value} for {actor.Utf8Name}.");
_toggleVisorHook.Original(drawData, value); _toggleVisorHook.Original(drawData, value);

View file

@ -14,7 +14,7 @@ public class ObjectManager(
IFramework framework, IFramework framework,
IClientState clientState, IClientState clientState,
IObjectTable objects, IObjectTable objects,
DalamudPluginInterface pi, IDalamudPluginInterface pi,
Logger log, Logger log,
ActorManager actors, ActorManager actors,
ITargetManager targets) ITargetManager targets)

View file

@ -6,7 +6,7 @@ using OtterGui.Services;
namespace Glamourer.Interop.PalettePlus; 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 private string ConfigFile
=> Path.Combine(Path.GetDirectoryName(pluginInterface.GetPluginConfigDirectory())!, "PalettePlus.json"); => Path.Combine(Path.GetDirectoryName(pluginInterface.GetPluginConfigDirectory())!, "PalettePlus.json");

View file

@ -1,7 +1,7 @@
using Dalamud.Interface.Internal.Notifications; using Dalamud.Interface.ImGuiNotification;
using Dalamud.Plugin; using Dalamud.Plugin;
using OtterGui.Classes;
using OtterGui.Services; using OtterGui.Services;
using Notification = OtterGui.Classes.Notification;
namespace Glamourer.Interop.PalettePlus; namespace Glamourer.Interop.PalettePlus;
@ -9,9 +9,9 @@ public sealed class PalettePlusChecker : IRequiredService, IDisposable
{ {
private readonly Timer _paletteTimer; private readonly Timer _paletteTimer;
private readonly Configuration _config; 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; _config = config;
_pluginInterface = pluginInterface; _pluginInterface = pluginInterface;

View file

@ -1,4 +1,4 @@
using Dalamud.Interface.Internal.Notifications; using Dalamud.Interface.ImGuiNotification;
using Dalamud.Plugin; using Dalamud.Plugin;
using Glamourer.Events; using Glamourer.Events;
using OtterGui.Classes; using OtterGui.Classes;
@ -36,7 +36,7 @@ public unsafe class PenumbraService : IDisposable
public const int RequiredPenumbraBreakingVersion = 5; public const int RequiredPenumbraBreakingVersion = 5;
public const int RequiredPenumbraFeatureVersion = 0; public const int RequiredPenumbraFeatureVersion = 0;
private readonly DalamudPluginInterface _pluginInterface; private readonly IDalamudPluginInterface _pluginInterface;
private readonly EventSubscriber<ChangedItemType, uint> _tooltipSubscriber; private readonly EventSubscriber<ChangedItemType, uint> _tooltipSubscriber;
private readonly EventSubscriber<MouseButton, ChangedItemType, uint> _clickSubscriber; private readonly EventSubscriber<MouseButton, ChangedItemType, uint> _clickSubscriber;
private readonly EventSubscriber<nint, Guid, nint, nint, nint> _creatingCharacterBase; private readonly EventSubscriber<nint, Guid, nint, nint, nint> _creatingCharacterBase;
@ -68,7 +68,7 @@ public unsafe class PenumbraService : IDisposable
public int CurrentMinor { get; private set; } public int CurrentMinor { get; private set; }
public DateTime AttachTime { get; private set; } public DateTime AttachTime { get; private set; }
public PenumbraService(DalamudPluginInterface pi, PenumbraReloaded penumbraReloaded) public PenumbraService(IDalamudPluginInterface pi, PenumbraReloaded penumbraReloaded)
{ {
_pluginInterface = pi; _pluginInterface = pi;
_penumbraReloaded = penumbraReloaded; _penumbraReloaded = penumbraReloaded;

View file

@ -3,6 +3,7 @@ using Dalamud.Hooking;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using Dalamud.Utility.Signatures; using Dalamud.Utility.Signatures;
using FFXIVClientStructs.FFXIV.Client.Game.Character; using FFXIVClientStructs.FFXIV.Client.Game.Character;
using Penumbra.GameData;
using Penumbra.GameData.Interop; using Penumbra.GameData.Interop;
using Character = FFXIVClientStructs.FFXIV.Client.Game.Character.Character; using Character = FFXIVClientStructs.FFXIV.Client.Game.Character.Character;
@ -14,7 +15,7 @@ public unsafe class ScalingService : IDisposable
{ {
interop.InitializeFromAttributes(this); interop.InitializeFromAttributes(this);
_setupMountHook = _setupMountHook =
interop.HookFromAddress<SetupMount>((nint)Character.MountContainer.MemberFunctionPointers.SetupMount, SetupMountDetour); interop.HookFromAddress<SetupMount>((nint)MountContainer.MemberFunctionPointers.SetupMount, SetupMountDetour);
_setupOrnamentHook = interop.HookFromAddress<SetupOrnament>((nint)Ornament.MemberFunctionPointers.SetupOrnament, SetupOrnamentDetour); _setupOrnamentHook = interop.HookFromAddress<SetupOrnament>((nint)Ornament.MemberFunctionPointers.SetupOrnament, SetupOrnamentDetour);
_calculateHeightHook = _calculateHeightHook =
interop.HookFromAddress<CalculateHeight>((nint)Character.MemberFunctionPointers.CalculateHeight, CalculateHeightDetour); interop.HookFromAddress<CalculateHeight>((nint)Character.MemberFunctionPointers.CalculateHeight, CalculateHeightDetour);
@ -33,7 +34,7 @@ public unsafe class ScalingService : IDisposable
_calculateHeightHook.Dispose(); _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 SetupOrnament(Ornament* ornament, uint* unk1, float* unk2);
private delegate void PlaceMinion(Companion* character); private delegate void PlaceMinion(Companion* character);
private delegate float CalculateHeight(Character* character); private delegate float CalculateHeight(Character* character);
@ -45,15 +46,15 @@ public unsafe class ScalingService : IDisposable
private readonly Hook<CalculateHeight> _calculateHeightHook; private readonly Hook<CalculateHeight> _calculateHeightHook;
// TODO: Use client structs sig. // 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<PlaceMinion> _placeMinionHook = null!; private readonly Hook<PlaceMinion> _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); var (race, clan, gender) = GetScaleRelevantCustomize(container->OwnerObject);
SetScaleCustomize(&container->OwnerObject->Character, container->OwnerObject->Character.GameObject.DrawObject); SetScaleCustomize(container->OwnerObject, container->OwnerObject->DrawObject);
_setupMountHook.Original(container, mountId, unk1, unk2, unk3, unk4); _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) private void SetupOrnamentDetour(Ornament* ornament, uint* unk1, float* unk2)

View file

@ -18,33 +18,44 @@ public unsafe class UpdateSlotService : IDisposable
SlotUpdatingEvent = slotUpdating; SlotUpdatingEvent = slotUpdating;
interop.InitializeFromAttributes(this); interop.InitializeFromAttributes(this);
_flagSlotForUpdateHook.Enable(); _flagSlotForUpdateHook.Enable();
_flagBonusSlotForUpdateHook.Enable();
} }
public void Dispose() public void Dispose()
=> _flagSlotForUpdateHook.Dispose(); {
_flagSlotForUpdateHook.Dispose();
_flagBonusSlotForUpdateHook.Dispose();
}
public void UpdateSlot(Model drawObject, EquipSlot slot, CharacterArmor data) public void UpdateSlot(Model drawObject, EquipSlot slot, CharacterArmor data)
{ {
if (!drawObject.IsCharacterBase) if (!drawObject.IsCharacterBase)
return; 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) public void UpdateArmor(Model drawObject, EquipSlot slot, CharacterArmor armor, StainIds stains)
=> UpdateSlot(drawObject, slot, armor.With(stain)); => UpdateSlot(drawObject, slot, armor.With(stains));
public void UpdateArmor(Model drawObject, EquipSlot slot, CharacterArmor armor) 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) public void UpdateStain(Model drawObject, EquipSlot slot, StainIds stains)
=> UpdateArmor(drawObject, slot, drawObject.GetArmor(slot), stain); => UpdateArmor(drawObject, slot, drawObject.GetArmor(slot), stains);
private delegate ulong FlagSlotForUpdateDelegateIntern(nint drawObject, uint slot, CharacterArmor* data); private delegate ulong FlagSlotForUpdateDelegateIntern(nint drawObject, uint slot, CharacterArmor* data);
[Signature(Sigs.FlagSlotForUpdate, DetourName = nameof(FlagSlotForUpdateDetour))] [Signature(Sigs.FlagSlotForUpdate, DetourName = nameof(FlagSlotForUpdateDetour))]
private readonly Hook<FlagSlotForUpdateDelegateIntern> _flagSlotForUpdateHook = null!; private readonly Hook<FlagSlotForUpdateDelegateIntern> _flagSlotForUpdateHook = null!;
[Signature(Sigs.FlagBonusSlotForUpdate, DetourName = nameof(FlagBonusSlotForUpdateDetour))]
private readonly Hook<FlagSlotForUpdateDelegateIntern> _flagBonusSlotForUpdateHook = null!;
private ulong FlagSlotForUpdateDetour(nint drawObject, uint slotIdx, CharacterArmor* data) private ulong FlagSlotForUpdateDetour(nint drawObject, uint slotIdx, CharacterArmor* data)
{ {
var slot = slotIdx.ToEquipSlot(); var slot = slotIdx.ToEquipSlot();
@ -54,6 +65,15 @@ public unsafe class UpdateSlotService : IDisposable
return returnValue == ulong.MaxValue ? _flagSlotForUpdateHook.Original(drawObject, slotIdx, data) : returnValue; 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) private ulong FlagSlotForUpdateInterop(Model drawObject, EquipSlot slot, CharacterArmor armor)
=> _flagSlotForUpdateHook.Original(drawObject.Address, slot.ToIndex(), &armor); => _flagSlotForUpdateHook.Original(drawObject.Address, slot.ToIndex(), &armor);
} }

View file

@ -70,7 +70,7 @@ public unsafe class WeaponService : IDisposable
if (tmpWeapon.Value != weapon.Value) if (tmpWeapon.Value != weapon.Value)
{ {
if (tmpWeapon.Skeleton.Id == 0) if (tmpWeapon.Skeleton.Id == 0)
tmpWeapon.Stain = 0; tmpWeapon.Stains = StainIds.None;
_loadWeaponHook.Original(drawData, slot, tmpWeapon.Value, 1, unk2, 1, unk4); _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 mdl = character.Model;
var (_, _, mh, oh) = mdl.GetWeapons(character); var (_, _, mh, oh) = mdl.GetWeapons(character);
var value = slot == EquipSlot.OffHand ? oh : mh; 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); LoadWeapon(character, slot, weapon);
} }
} }

View file

@ -1,13 +1,13 @@
using Dalamud.Interface.Internal.Notifications; using Dalamud.Interface.ImGuiNotification;
using Glamourer.Interop.Penumbra; using Glamourer.Interop.Penumbra;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using OtterGui; using OtterGui;
using OtterGui.Classes;
using OtterGui.Filesystem; using OtterGui.Filesystem;
using OtterGui.Services; using OtterGui.Services;
using Penumbra.GameData.Actors; using Penumbra.GameData.Actors;
using Penumbra.GameData.Interop; using Penumbra.GameData.Interop;
using Notification = OtterGui.Classes.Notification;
namespace Glamourer.Services; namespace Glamourer.Services;

View file

@ -161,13 +161,6 @@ public sealed class CustomizeService(
return $"The gender {gender.ToName()} is unknown, reset to {Gender.Male.ToName()}."; 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; actualGender = gender;
return string.Empty; return string.Empty;
} }
@ -225,13 +218,6 @@ public sealed class CustomizeService(
customize.Race = newRace; customize.Race = newRace;
customize.Clan = newClan; 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); var set = Manager.GetSet(customize.Clan, customize.Gender);
return FixValues(set, ref customize) | flags; return FixValues(set, ref customize) | flags;
} }
@ -242,10 +228,6 @@ public sealed class CustomizeService(
if (customize.Gender == newGender) if (customize.Gender == newGender)
return 0; return 0;
// TODO Female Hrothgar
if (customize.Race is Race.Hrothgar)
return 0;
if (ValidateGender(customize.Race, newGender, out newGender).Length > 0) if (ValidateGender(customize.Race, newGender, out newGender).Length > 0)
return 0; return 0;

View file

@ -8,7 +8,7 @@ namespace Glamourer.Services;
public class DalamudServices 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);
services.AddExistingService(pi.UiBuilder); services.AddExistingService(pi.UiBuilder);

View file

@ -19,7 +19,7 @@ public class FilenameService
public readonly string NpcAppearanceFile; public readonly string NpcAppearanceFile;
public readonly string CollectionOverrideFile; public readonly string CollectionOverrideFile;
public FilenameService(DalamudPluginInterface pi) public FilenameService(IDalamudPluginInterface pi)
{ {
ConfigDirectory = pi.ConfigDirectory.FullName; ConfigDirectory = pi.ConfigDirectory.FullName;
ConfigFile = pi.ConfigFile.FullName; ConfigFile = pi.ConfigFile.FullName;

View file

@ -2,6 +2,7 @@ using Dalamud.Plugin.Services;
using Dalamud.Utility.Signatures; using Dalamud.Utility.Signatures;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using OtterGui.Services; using OtterGui.Services;
using Penumbra.GameData;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs; using Penumbra.GameData.Structs;
@ -9,7 +10,7 @@ namespace Glamourer.Services;
public unsafe class HeightService : IService public unsafe class HeightService : IService
{ {
[Signature("E8 ?? ?? ?? FF 48 8B 0D ?? ?? ?? ?? 0F 28 F0")] [Signature(Sigs.CalculateHeight)]
private readonly delegate* unmanaged[Stdcall]<CharacterUtility*, byte, byte, byte, byte, float> _calculateHeight = null!; private readonly delegate* unmanaged[Stdcall]<CharacterUtility*, byte, byte, byte, byte, float> _calculateHeight = null!;
public HeightService(IGameInteropProvider interop) public HeightService(IGameInteropProvider interop)

View file

@ -195,16 +195,16 @@ public class ItemManager
/// The returned stain id is either the input or 0. /// 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. /// The return value is an empty string if there was no problem and a warning otherwise.
/// </summary> /// </summary>
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; return string.Empty;
} }
ret = 0; ret = StainIds.None;
return $"The Stain {stain} does not exist, reset to unstained."; return $"The Stain {stains} does not exist, reset to unstained.";
} }
/// <summary> Returns whether an offhand is valid given the required offhand type. </summary> /// <summary> Returns whether an offhand is valid given the required offhand type. </summary>

View file

@ -33,7 +33,7 @@ namespace Glamourer.Services;
public static class StaticServiceManager 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); EventWrapperBase.ChangeLogger(log);
var services = new ServiceManager(log) var services = new ServiceManager(log)
@ -165,5 +165,6 @@ public static class StaticServiceManager
.AddSingleton<GlamourerChangelog>() .AddSingleton<GlamourerChangelog>()
.AddSingleton<DesignQuickBar>() .AddSingleton<DesignQuickBar>()
.AddSingleton<DesignColorUi>() .AddSingleton<DesignColorUi>()
.AddSingleton<NpcCombo>(); .AddSingleton<NpcCombo>()
.AddSingleton<TextureCache>();
} }

View file

@ -1,5 +1,5 @@
using Dalamud.Interface; using Dalamud.Interface;
using Dalamud.Interface.Internal; using Dalamud.Interface.Textures.TextureWraps;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using OtterGui.Classes; using OtterGui.Classes;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
@ -7,7 +7,7 @@ using Penumbra.GameData.Structs;
namespace Glamourer.Services; 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 : TextureCache(dataManager, textureProvider), IDisposable
{ {
private readonly IDalamudTextureWrap?[] _slotIcons = CreateSlotIcons(uiBuilder); 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]; var ret = new IDalamudTextureWrap?[12];

View file

@ -1,5 +1,4 @@
using Glamourer.Interop.Structs; using Penumbra.GameData.Structs;
using Penumbra.GameData.Structs;
namespace Glamourer.State; 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, 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) byte feetV, StainId[]? stains = null)
: this(new CharacterArmor(headS, headV, 0), new CharacterArmor(bodyS, bodyV, 0), new CharacterArmor(handsS, handsV, 0), : this(new CharacterArmor(headS, headV, StainIds.None), new CharacterArmor(bodyS, bodyV, StainIds.None), new CharacterArmor(handsS, handsV, StainIds.None),
new CharacterArmor(legsS, legsV, 0), new CharacterArmor(feetS, feetV, 0), stains) new CharacterArmor(legsS, legsV, StainIds.None), new CharacterArmor(feetS, feetV, StainIds.None), stains)
{ } { }
public static Group FullSetWithoutHat(ushort modelSet, byte variant, StainId[]? stains = null) public static Group FullSetWithoutHat(ushort modelSet, byte variant, StainId[]? stains = null)

View file

@ -1,5 +1,5 @@
using Dalamud.Game.ClientState.Objects.Enums; using Dalamud.Interface.ImGuiNotification;
using Dalamud.Interface.Internal.Notifications; using FFXIVClientStructs.FFXIV.Client.Game.Object;
using Glamourer.Designs; using Glamourer.Designs;
using Glamourer.Gui; using Glamourer.Gui;
using Glamourer.Services; using Glamourer.Services;
@ -106,7 +106,7 @@ public unsafe class FunModule : IDisposable
&& actor.OnlineStatus is OnlineStatus.PvEMentor or OnlineStatus.PvPMentor or OnlineStatus.TradeMentor && actor.OnlineStatus is OnlineStatus.PvEMentor or OnlineStatus.PvPMentor or OnlineStatus.TradeMentor
&& slot.IsEquipment()) && slot.IsEquipment())
{ {
armor = new CharacterArmor(6117, 1, 0); armor = new CharacterArmor(6117, 1, StainIds.None);
return; return;
} }
@ -171,7 +171,7 @@ public unsafe class FunModule : IDisposable
break; break;
case CodeService.CodeFlag.Dolphins: case CodeService.CodeFlag.Dolphins:
SetDolphin(EquipSlot.Body, ref armor[1]); SetDolphin(EquipSlot.Body, ref armor[1]);
SetDolphin(EquipSlot.Head, ref armor[0]); SetDolphin(EquipSlot.Head, ref armor[0]);
break; break;
case CodeService.CodeFlag.World when actor.Index != 0: case CodeService.CodeFlag.World when actor.Index != 0:
_worldSets.Apply(actor, _rng, armor); _worldSets.Apply(actor, _rng, armor);
@ -198,7 +198,7 @@ public unsafe class FunModule : IDisposable
private static bool ValidFunTarget(Actor actor) private static bool ValidFunTarget(Actor actor)
=> actor.IsCharacter => actor.IsCharacter
&& actor.AsObject->ObjectKind is (byte)ObjectKind.Player && actor.AsObject->ObjectKind is ObjectKind.Pc
&& !actor.IsTransformed && !actor.IsTransformed
&& actor.AsCharacter->CharacterData.ModelCharaId == 0; && actor.AsCharacter->CharacterData.ModelCharaId == 0;
@ -208,7 +208,7 @@ public unsafe class FunModule : IDisposable
private void SetRandomDye(ref CharacterArmor armor) private void SetRandomDye(ref CharacterArmor armor)
{ {
var stainIdx = _rng.Next(0, _stains.Length - 1); var stainIdx = _rng.Next(0, _stains.Length - 1);
armor.Stain = _stains[stainIdx]; armor.Stains = _stains[stainIdx];
} }
private void SetRandomItem(EquipSlot slot, ref CharacterArmor armor) private void SetRandomItem(EquipSlot slot, ref CharacterArmor armor)
@ -235,17 +235,17 @@ public unsafe class FunModule : IDisposable
private static IReadOnlyList<CharacterArmor> DolphinBodies private static IReadOnlyList<CharacterArmor> DolphinBodies
=> =>
[ [
new CharacterArmor(6089, 1, 4), // Toad new CharacterArmor(6089, 1, new StainIds(4)), // Toad
new CharacterArmor(6089, 1, 4), // Toad new CharacterArmor(6089, 1, new StainIds(4)), // Toad
new CharacterArmor(6089, 1, 4), // Toad new CharacterArmor(6089, 1, new StainIds(4)), // Toad
new CharacterArmor(6023, 1, 4), // Swine new CharacterArmor(6023, 1, new StainIds(4)), // Swine
new CharacterArmor(6023, 1, 4), // Swine new CharacterArmor(6023, 1, new StainIds(4)), // Swine
new CharacterArmor(6023, 1, 4), // Swine new CharacterArmor(6023, 1, new StainIds(4)), // Swine
new CharacterArmor(6133, 1, 4), // Gaja new CharacterArmor(6133, 1, new StainIds(4)), // Gaja
new CharacterArmor(6182, 1, 3), // Imp new CharacterArmor(6182, 1, new StainIds(3)), // Imp
new CharacterArmor(6182, 1, 3), // Imp new CharacterArmor(6182, 1, new StainIds(3)), // Imp
new CharacterArmor(6182, 1, 4), // Imp new CharacterArmor(6182, 1, new StainIds(4)), // Imp
new CharacterArmor(6182, 1, 4), // Imp new CharacterArmor(6182, 1, new StainIds(4)), // Imp
]; ];
private void SetDolphin(EquipSlot slot, ref CharacterArmor armor) private void SetDolphin(EquipSlot slot, ref CharacterArmor armor)
@ -253,7 +253,7 @@ public unsafe class FunModule : IDisposable
armor = slot switch armor = slot switch
{ {
EquipSlot.Body => DolphinBodies[_rng.Next(0, DolphinBodies.Count - 1)], 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, _ => armor,
}; };
} }
@ -270,7 +270,7 @@ public unsafe class FunModule : IDisposable
private static void SetCrown(Span<CharacterArmor> armor) private static void SetCrown(Span<CharacterArmor> armor)
{ {
var clown = new CharacterArmor(6117, 1, 0); var clown = new CharacterArmor(6117, 1, StainIds.None);
armor[0] = clown; armor[0] = clown;
armor[1] = clown; armor[1] = clown;
armor[2] = clown; armor[2] = clown;
@ -285,15 +285,12 @@ public unsafe class FunModule : IDisposable
return; return;
var targetClan = (SubRace)((int)race * 2 - (int)customize.Clan % 2); 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); _customizations.ChangeClan(ref customize, targetClan);
} }
private void SetGender(ref CustomizeArray customize) 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; return;
_customizations.ChangeGender(ref customize, customize.Gender is Gender.Male ? Gender.Female : Gender.Male); _customizations.ChangeGender(ref customize, customize.Gender is Gender.Male ? Gender.Female : Gender.Male);

View file

@ -152,11 +152,11 @@ public class InternalStateEditor(
} }
/// <summary> Change a single piece of equipment including stain. </summary> /// <summary> Change a single piece of equipment including stain. </summary>
public bool ChangeEquip(ActorState state, EquipSlot slot, EquipItem item, StainId stain, StateSource source, out EquipItem oldItem, public bool ChangeEquip(ActorState state, EquipSlot slot, EquipItem item, StainIds stains, StateSource source, out EquipItem oldItem,
out StainId oldStain, uint key = 0) out StainIds oldStains, uint key = 0)
{ {
oldItem = state.ModelData.Item(slot); oldItem = state.ModelData.Item(slot);
oldStain = state.ModelData.Stain(slot); oldStains = state.ModelData.Stain(slot);
if (!state.CanUnlock(key)) if (!state.CanUnlock(key))
return false; return false;
@ -168,7 +168,7 @@ public class InternalStateEditor(
return false; return false;
var old = oldItem; var old = oldItem;
var oldS = oldStain; var oldS = oldStains;
gPose.AddActionOnLeave(() => gPose.AddActionOnLeave(() =>
{ {
if (old.Type == state.BaseData.Item(slot).Type) if (old.Type == state.BaseData.Item(slot).Type)
@ -177,20 +177,20 @@ public class InternalStateEditor(
} }
state.ModelData.SetItem(slot, item); state.ModelData.SetItem(slot, item);
state.ModelData.SetStain(slot, stain); state.ModelData.SetStain(slot, stains);
state.Sources[slot, false] = source; state.Sources[slot, false] = source;
state.Sources[slot, true] = source; state.Sources[slot, true] = source;
return true; return true;
} }
/// <summary> Change only the stain of an equipment piece. </summary> /// <summary> Change only the stain of an equipment piece. </summary>
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)) if (!state.CanUnlock(key))
return false; return false;
state.ModelData.SetStain(slot, stain); state.ModelData.SetStain(slot, stains);
state.Sources[slot, true] = source; state.Sources[slot, true] = source;
return true; return true;
} }

View file

@ -130,22 +130,22 @@ public class StateApplier(
/// Change the stain of a single piece of armor or weapon. /// 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. /// If the offhand is empty, the stain will be fixed to 0 to prevent crashes.
/// </summary> /// </summary>
public void ChangeStain(ActorData data, EquipSlot slot, StainId stain) public void ChangeStain(ActorData data, EquipSlot slot, StainIds stains)
{ {
var idx = slot.ToIndex(); var idx = slot.ToIndex();
switch (idx) switch (idx)
{ {
case < 10: case < 10:
foreach (var actor in data.Objects.Where(a => a.Model.IsHuman)) foreach (var actor in data.Objects.Where(a => a.Model.IsHuman))
_updateSlot.UpdateStain(actor.Model, slot, stain); _updateSlot.UpdateStain(actor.Model, slot, stains);
break; break;
case 10: case 10:
foreach (var actor in data.Objects.Where(a => a.Model.IsHuman)) foreach (var actor in data.Objects.Where(a => a.Model.IsHuman))
_weapon.LoadStain(actor, EquipSlot.MainHand, stain); _weapon.LoadStain(actor, EquipSlot.MainHand, stains);
break; break;
case 11: case 11:
foreach (var actor in data.Objects.Where(a => a.Model.IsHuman)) foreach (var actor in data.Objects.Where(a => a.Model.IsHuman))
_weapon.LoadStain(actor, EquipSlot.OffHand, stain); _weapon.LoadStain(actor, EquipSlot.OffHand, stains);
break; break;
} }
} }
@ -162,12 +162,12 @@ public class StateApplier(
/// <summary> Apply a weapon to the appropriate slot. </summary> /// <summary> Apply a weapon to the appropriate slot. </summary>
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) if (slot is EquipSlot.MainHand)
ChangeMainhand(data, item, stain); ChangeMainhand(data, item, stains);
else else
ChangeOffhand(data, item, stain); ChangeOffhand(data, item, stains);
} }
/// <inheritdoc cref="ChangeWeapon(ActorData,EquipSlot,EquipItem,StainId)"/> /// <inheritdoc cref="ChangeWeapon(ActorData,EquipSlot,EquipItem,StainId)"/>
@ -186,19 +186,19 @@ public class StateApplier(
/// <summary> /// <summary>
/// Apply a weapon to the mainhand. If the weapon type has no associated offhand type, apply both. /// Apply a weapon to the mainhand. If the weapon type has no associated offhand type, apply both.
/// </summary> /// </summary>
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; var slot = weapon.Type.ValidOffhand() == FullEquipType.Unknown ? EquipSlot.BothHand : EquipSlot.MainHand;
foreach (var actor in data.Objects.Where(a => a.Model.IsHuman)) 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));
} }
/// <summary> Apply a weapon to the offhand. </summary> /// <summary> Apply a weapon to the offhand. </summary>
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)) 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));
} }
/// <summary> Change a meta state. </summary> /// <summary> Change a meta state. </summary>
@ -209,7 +209,7 @@ public class StateApplier(
case MetaIndex.Wetness: case MetaIndex.Wetness:
{ {
foreach (var actor in data.Objects.Where(a => a.IsCharacter)) foreach (var actor in data.Objects.Where(a => a.IsCharacter))
actor.AsCharacter->IsGPoseWet = value; actor.IsGPoseWet = value;
return; return;
} }
case MetaIndex.HatState: case MetaIndex.HatState:

View file

@ -90,22 +90,22 @@ public class StateEditor(
} }
/// <inheritdoc/> /// <inheritdoc/>
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 (false, false): return;
case (true, false): case (true, false):
ChangeItem(data, slot, item!.Value, settings); ChangeItem(data, slot, item!.Value, settings);
return; return;
case (false, true): case (false, true):
ChangeStain(data, slot, stain!.Value, settings); ChangeStains(data, slot, stains!.Value, settings);
return; return;
} }
var state = (ActorState)data; var state = (ActorState)data;
if (!Editor.ChangeEquip(state, slot, item ?? state.ModelData.Item(slot), stain ?? state.ModelData.Stain(slot), settings.Source, if (!Editor.ChangeEquip(state, slot, item ?? state.ModelData.Item(slot), stains ?? state.ModelData.Stain(slot), settings.Source,
out var old, out var oldStain, settings.Key)) out var old, out var oldStains, settings.Key))
return; return;
var type = slot.ToIndex() < 10 ? StateChangeType.Equip : StateChangeType.Weapon; 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)); item!.Value.Type != (slot is EquipSlot.MainHand ? state.BaseData.MainhandType : state.BaseData.OffhandType));
if (slot is EquipSlot.MainHand) if (slot is EquipSlot.MainHand)
ApplyMainhandPeriphery(state, item, stain, settings); ApplyMainhandPeriphery(state, item, stains, settings);
Glamourer.Log.Verbose( 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")}.]"); $"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(type, settings.Source, state, actors, (old, item!.Value, slot));
StateChanged.Invoke(StateChangeType.Stain, settings.Source, state, actors, (oldStain, stain!.Value, slot)); StateChanged.Invoke(StateChangeType.Stains, settings.Source, state, actors, (oldStains, stains!.Value, slot));
} }
/// <inheritdoc/> /// <inheritdoc/>
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; 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; return;
var actors = Applier.ChangeStain(state, slot, settings.Source.RequiresChange()); var actors = Applier.ChangeStain(state, slot, settings.Source.RequiresChange());
Glamourer.Log.Verbose( Glamourer.Log.Verbose(
$"Set {slot.ToName()} stain in state {state.Identifier.Incognito(null)} from {old.Id} to {stain.Id}. [Affecting {actors.ToLazyString("nothing")}.]"); $"Set {slot.ToName()} stain in state {state.Identifier.Incognito(null)} from {old} to {stains}. [Affecting {actors.ToLazyString("nothing")}.]");
StateChanged.Invoke(StateChangeType.Stain, settings.Source, state, actors, (old, stain, slot)); StateChanged.Invoke(StateChangeType.Stains, settings.Source, state, actors, (old, stains, slot));
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -269,7 +269,7 @@ public class StateEditor(
if (mergedDesign.Design.DoApplyStain(slot)) if (mergedDesign.Design.DoApplyStain(slot))
if (!settings.RespectManual || !state.Sources[slot, true].IsManual()) 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); Source(slot.ToState(true)), out _, settings.Key);
} }
@ -277,7 +277,7 @@ public class StateEditor(
{ {
if (mergedDesign.Design.DoApplyStain(weaponSlot)) if (mergedDesign.Design.DoApplyStain(weaponSlot))
if (!settings.RespectManual || !state.Sources[weaponSlot, true].IsManual()) 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); Source(weaponSlot.ToState(true)), out _, settings.Key);
if (!mergedDesign.Design.DoApplyEquip(weaponSlot)) if (!mergedDesign.Design.DoApplyEquip(weaponSlot))
@ -392,19 +392,19 @@ public class StateEditor(
/// <summary> Apply offhand item and potentially gauntlets if configured. </summary> /// <summary> Apply offhand item and potentially gauntlets if configured. </summary>
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()) if (!Config.ChangeEntireItem || !settings.Source.IsManual())
return; return;
var mh = newMainhand ?? state.ModelData.Item(EquipSlot.MainHand); var mh = newMainhand ?? state.ModelData.Item(EquipSlot.MainHand);
var offhand = newMainhand != null ? Items.GetDefaultOffhand(mh) : state.ModelData.Item(EquipSlot.OffHand); 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) 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)) 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), ChangeEquip(state, EquipSlot.Hands, newMainhand != null ? gauntlets : state.ModelData.Item(EquipSlot.Hands),
stain, settings); stains, settings);
} }
} }

View file

@ -227,7 +227,7 @@ public class StateListener : IDisposable
(_, armor) = _items.RestrictedGear.ResolveRestricted(armor, slot, customize.Race, customize.Gender); (_, 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(); _objects.Update();
var (identifier, objects) = _objects.PlayerData; var (identifier, objects) = _objects.PlayerData;
@ -250,14 +250,14 @@ public class StateListener : IDisposable
&& current.Weapon == changed.Weapon && current.Weapon == changed.Weapon
&& !state.Sources[slot, false].IsFixed(); && !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)) switch ((itemChanged, stainChanged))
{ {
case (true, true): 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) if (slot is EquipSlot.MainHand or EquipSlot.OffHand)
_applier.ChangeWeapon(objects, slot, currentItem, current.Stain); _applier.ChangeWeapon(objects, slot, currentItem, current.Stains);
else else
_applier.ChangeArmor(objects, slot, current.ToArmor(), !state.Sources[slot, false].IsFixed(), _applier.ChangeArmor(objects, slot, current.ToArmor(), !state.Sources[slot, false].IsFixed(),
state.ModelData.IsHatVisible()); state.ModelData.IsHatVisible());
@ -265,14 +265,14 @@ public class StateListener : IDisposable
case (true, false): case (true, false):
_manager.ChangeItem(state, slot, currentItem, ApplySettings.Game); _manager.ChangeItem(state, slot, currentItem, ApplySettings.Game);
if (slot is EquipSlot.MainHand or EquipSlot.OffHand) if (slot is EquipSlot.MainHand or EquipSlot.OffHand)
_applier.ChangeWeapon(objects, slot, currentItem, model.Stain); _applier.ChangeWeapon(objects, slot, currentItem, model.Stains);
else 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()); state.ModelData.IsHatVisible());
break; break;
case (false, true): case (false, true):
_manager.ChangeStain(state, slot, current.Stain, ApplySettings.Game); _manager.ChangeStains(state, slot, current.Stains, ApplySettings.Game);
_applier.ChangeStain(objects, slot, current.Stain); _applier.ChangeStain(objects, slot, current.Stains);
break; break;
} }
} }
@ -308,7 +308,7 @@ public class StateListener : IDisposable
apply = true; apply = true;
if (!state.Sources[slot, true].IsFixed()) 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 else
apply = true; apply = true;
break; break;
@ -332,7 +332,7 @@ public class StateListener : IDisposable
else else
{ {
if (weapon.Skeleton.Id != 0) if (weapon.Skeleton.Id != 0)
weapon = weapon.With(newWeapon.Stain); weapon = weapon.With(newWeapon.Stains);
// Force unlock if necessary. // Force unlock if necessary.
_manager.ChangeItem(state, slot, state.BaseData.Item(slot), ApplySettings.Game with { Key = state.Combination }); _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. // Fist Weapon Offhand hack.
if (slot is EquipSlot.MainHand && weapon.Skeleton.Id is > 1600 and < 1651) if (slot is EquipSlot.MainHand && weapon.Skeleton.Id is > 1600 and < 1651)
_lastFistOffhand = new CharacterWeapon((PrimaryId)(weapon.Skeleton.Id + 50), weapon.Weapon, weapon.Variant, _lastFistOffhand = new CharacterWeapon((PrimaryId)(weapon.Skeleton.Id + 50), weapon.Weapon, weapon.Variant,
weapon.Stain); weapon.Stains);
_funModule.ApplyFunToWeapon(actor, ref weapon, slot); _funModule.ApplyFunToWeapon(actor, ref weapon, slot);
} }
@ -365,7 +365,7 @@ public class StateListener : IDisposable
{ {
var item = _items.Identify(slot, actorArmor.Set, actorArmor.Variant); var item = _items.Identify(slot, actorArmor.Set, actorArmor.Variant);
state.BaseData.SetItem(EquipSlot.Head, item); state.BaseData.SetItem(EquipSlot.Head, item);
state.BaseData.SetStain(EquipSlot.Head, actorArmor.Stain); state.BaseData.SetStain(EquipSlot.Head, actorArmor.Stains);
return UpdateState.Change; return UpdateState.Change;
} }
@ -378,9 +378,9 @@ public class StateListener : IDisposable
var baseData = state.BaseData.Armor(slot); var baseData = state.BaseData.Armor(slot);
var change = UpdateState.NoChange; 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; change = UpdateState.Change;
} }
@ -418,7 +418,7 @@ public class StateListener : IDisposable
apply = true; apply = true;
if (!state.Sources[slot, true].IsFixed()) 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 else
apply = true; 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) if (slot is EquipSlot.OffHand && weapon.Value == 0 && actor.GetMainhand().Skeleton.Id is > 1600 and < 1651)
return UpdateState.NoChange; 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; change = UpdateState.Change;
} }

View file

@ -117,7 +117,7 @@ public sealed class StateManager(
if (!_humans.IsHuman((uint)actor.AsCharacter->CharacterData.ModelCharaId)) if (!_humans.IsHuman((uint)actor.AsCharacter->CharacterData.ModelCharaId))
{ {
ret.LoadNonHuman((uint)actor.AsCharacter->CharacterData.ModelCharaId, *(CustomizeArray*)&actor.AsCharacter->DrawData.CustomizeData, 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; return ret;
} }
@ -141,7 +141,7 @@ public sealed class StateManager(
var head = ret.IsHatVisible() || ignoreHatState ? model.GetArmor(EquipSlot.Head) : actor.GetArmor(EquipSlot.Head); var head = ret.IsHatVisible() || ignoreHatState ? model.GetArmor(EquipSlot.Head) : actor.GetArmor(EquipSlot.Head);
var headItem = Items.Identify(EquipSlot.Head, head.Set, head.Variant); var headItem = Items.Identify(EquipSlot.Head, head.Set, head.Variant);
ret.SetItem(EquipSlot.Head, headItem); 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. // The other slots can be used from the draw object.
foreach (var slot in EquipSlotExtensions.EqdpSlots.Skip(1)) foreach (var slot in EquipSlotExtensions.EqdpSlots.Skip(1))
@ -149,7 +149,7 @@ public sealed class StateManager(
var armor = model.GetArmor(slot); var armor = model.GetArmor(slot);
var item = Items.Identify(slot, armor.Set, armor.Variant); var item = Items.Identify(slot, armor.Set, armor.Variant);
ret.SetItem(slot, item); 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. // 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 armor = actor.GetArmor(slot);
var item = Items.Identify(slot, armor.Set, armor.Variant); var item = Items.Identify(slot, armor.Set, armor.Variant);
ret.SetItem(slot, item); ret.SetItem(slot, item);
ret.SetStain(slot, armor.Stain); ret.SetStain(slot, armor.Stains);
} }
main = actor.GetMainhand(); main = actor.GetMainhand();
@ -187,13 +187,13 @@ public sealed class StateManager(
var mainItem = Items.Identify(EquipSlot.MainHand, main.Skeleton, main.Weapon, main.Variant); 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); var offItem = Items.Identify(EquipSlot.OffHand, off.Skeleton, off.Weapon, off.Variant, mainItem.Type);
ret.SetItem(EquipSlot.MainHand, mainItem); ret.SetItem(EquipSlot.MainHand, mainItem);
ret.SetStain(EquipSlot.MainHand, main.Stain); ret.SetStain(EquipSlot.MainHand, main.Stains);
ret.SetItem(EquipSlot.OffHand, offItem); 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. // Wetness can technically only be set in GPose or via external tools.
// It is only available in the game object. // 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, // 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. // 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.Variant = mainhand.Variant;
offhand.Weapon = mainhand.Weapon; offhand.Weapon = mainhand.Weapon;
ret.SetItem(EquipSlot.Hands, gauntlets); ret.SetItem(EquipSlot.Hands, gauntlets);
ret.SetStain(EquipSlot.Hands, mainhand.Stain); ret.SetStain(EquipSlot.Hands, mainhand.Stains);
} }
/// <summary> Turn an actor human. </summary> /// <summary> Turn an actor human. </summary>
@ -414,7 +414,9 @@ public sealed class StateManager(
public void ReapplyState(Actor actor, ActorState state, bool forceRedraw, StateSource source) public void ReapplyState(Actor actor, ActorState state, bool forceRedraw, StateSource source)
{ {
var data = Applier.ApplyAll(state, 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); StateChanged.Invoke(StateChangeType.Reapply, source, state, data, null);
} }

View file

@ -22,7 +22,7 @@ public class WorldSets
[(Gender.Male, Race.AuRa)] = FunEquipSet.Group.FullSetWithoutHat(0257, 2), [(Gender.Male, Race.AuRa)] = FunEquipSet.Group.FullSetWithoutHat(0257, 2),
[(Gender.Female, Race.AuRa)] = FunEquipSet.Group.FullSetWithoutHat(0258, 2), [(Gender.Female, Race.AuRa)] = FunEquipSet.Group.FullSetWithoutHat(0258, 2),
[(Gender.Male, Race.Hrothgar)] = FunEquipSet.Group.FullSetWithoutHat(0597, 1), [(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.Male, Race.Viera)] = FunEquipSet.Group.FullSetWithoutHat(0744, 1),
[(Gender.Female, Race.Viera)] = FunEquipSet.Group.FullSetWithoutHat(0581, 1), [(Gender.Female, Race.Viera)] = FunEquipSet.Group.FullSetWithoutHat(0581, 1),
}; };

View file

@ -1,4 +1,4 @@
using Dalamud; using Dalamud.Game;
using Dalamud.Hooking; using Dalamud.Hooking;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using Dalamud.Utility; using Dalamud.Utility;
@ -8,6 +8,7 @@ using Glamourer.GameData;
using Glamourer.Events; using Glamourer.Events;
using Glamourer.Services; using Glamourer.Services;
using Lumina.Excel.GeneratedSheets; using Lumina.Excel.GeneratedSheets;
using Penumbra.GameData;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
namespace Glamourer.Unlocks; namespace Glamourer.Unlocks;
@ -127,8 +128,8 @@ public class CustomizeUnlockManager : IDisposable, ISavable
} }
private delegate void SetUnlockLinkValueDelegate(nint uiState, uint data, byte value); 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<SetUnlockLinkValueDelegate> _setUnlockLinkValueHook = null!; private readonly Hook<SetUnlockLinkValueDelegate> _setUnlockLinkValueHook = null!;
private void SetUnlockLinkValueDetour(nint uiState, uint data, byte value) private void SetUnlockLinkValueDetour(nint uiState, uint data, byte value)

View file

@ -1,4 +1,4 @@
using Dalamud.Interface.Internal.Notifications; using Dalamud.Interface.ImGuiNotification;
using Glamourer.Services; using Glamourer.Services;
using Newtonsoft.Json; using Newtonsoft.Json;
using OtterGui.Classes; using OtterGui.Classes;

View file

@ -144,8 +144,7 @@ public class ItemUnlockManager : ISavable, IDisposable, IReadOnlyDictionary<Item
if (newGlamourState != _lastGlamourState) if (newGlamourState != _lastGlamourState)
{ {
_lastGlamourState = newGlamourState; _lastGlamourState = newGlamourState;
// TODO: Make independent from hardcoded value var span = mirageManager->PrismBoxItemIds;
var span = new ReadOnlySpan<uint>(mirageManager->PrismBoxItemIds, 800);
foreach (var item in span) foreach (var item in span)
changes |= AddItem(item, time); changes |= AddItem(item, time);
} }
@ -154,10 +153,9 @@ public class ItemUnlockManager : ISavable, IDisposable, IReadOnlyDictionary<Item
if (newPlateState != _lastPlateState) if (newPlateState != _lastPlateState)
{ {
_lastPlateState = newPlateState; _lastPlateState = newPlateState;
foreach (var plate in mirageManager->GlamourPlatesSpan) foreach (var plate in mirageManager->GlamourPlates)
{ {
// TODO: Make independent from hardcoded value var span = plate.ItemIds;
var span = new ReadOnlySpan<uint>(plate.ItemIds, 12);
foreach (var item in span) foreach (var item in span)
changes |= AddItem(item, time); changes |= AddItem(item, time);
} }
@ -176,8 +174,8 @@ public class ItemUnlockManager : ISavable, IDisposable, IReadOnlyDictionary<Item
var item = container->GetInventorySlot(_currentInventoryIndex++); var item = container->GetInventorySlot(_currentInventoryIndex++);
if (item != null) if (item != null)
{ {
changes |= AddItem(item->ItemID, time); changes |= AddItem(item->ItemId, time);
changes |= AddItem(item->GlamourID, time); changes |= AddItem(item->GlamourId, time);
} }
} }
else else

View file

@ -1,4 +1,4 @@
using Dalamud.Interface.Internal.Notifications; using Dalamud.Interface.ImGuiNotification;
using OtterGui.Classes; using OtterGui.Classes;
namespace Glamourer.Unlocks; namespace Glamourer.Unlocks;

@ -1 +1 @@
Subproject commit fd791285606d49a7644762ea0b4dc2bbb1368eac Subproject commit c2738e1d42974cddbe5a31238c6ed236a831d17d

@ -1 +1 @@
Subproject commit 6aeae346332a255b7575ccfca554afb0f3cf1494 Subproject commit 8ec296d1f8113ae2ba509527749cd3e8f54debbf

@ -1 +1 @@
Subproject commit caa58c5c92710e69ce07b9d736ebe2d228cb4488 Subproject commit f04abbabedf5e757c5cbb970f3e513fef23e53cf

View file

@ -21,7 +21,7 @@
"TestingAssemblyVersion": "1.2.3.3", "TestingAssemblyVersion": "1.2.3.3",
"RepoUrl": "https://github.com/Ottermandias/Glamourer", "RepoUrl": "https://github.com/Ottermandias/Glamourer",
"ApplicableVersion": "any", "ApplicableVersion": "any",
"DalamudApiLevel": 9, "DalamudApiLevel": 10,
"IsHide": "False", "IsHide": "False",
"IsTestingExclusive": "False", "IsTestingExclusive": "False",
"DownloadCount": 1, "DownloadCount": 1,