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.Interface.Internal.Notifications;
using Dalamud.Interface.ImGuiNotification;
using Glamourer.Designs;
using Glamourer.Designs.Special;
using Glamourer.Events;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -176,7 +176,7 @@ public class DesignConverter(
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)
{
if (armors.Count != 10)
@ -194,7 +194,7 @@ public class DesignConverter(
item = ItemManager.NothingItem(slot);
}
yield return (slot, item, armor.Stain);
yield return (slot, item, armor.Stains);
}
var mh = _items.Identify(EquipSlot.MainHand, mainhand.Skeleton, mainhand.Weapon, mainhand.Variant);
@ -204,7 +204,7 @@ public class DesignConverter(
mh = _items.DefaultSword;
}
yield return (EquipSlot.MainHand, mh, mainhand.Stain);
yield return (EquipSlot.MainHand, mh, mainhand.Stains);
var oh = _items.Identify(EquipSlot.OffHand, offhand.Skeleton, offhand.Weapon, offhand.Variant, mh.Type);
if (!skipWarnings && !oh.Valid)
@ -215,7 +215,7 @@ public class DesignConverter(
oh = ItemManager.NothingItem(FullEquipType.Shield);
}
yield return (EquipSlot.OffHand, oh, offhand.Stain);
yield return (EquipSlot.OffHand, oh, offhand.Stains);
}
private static void ComputeMaterials(DesignMaterialManager manager, in StateMaterialManager materials,

View file

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

View file

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

View file

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

View file

@ -65,11 +65,11 @@ public interface IDesignEditor
=> ChangeEquip(data, slot, item, null, settings);
/// <summary> Change the stain for any equipment piece. </summary>
public void ChangeStain(object data, EquipSlot slot, StainId stain, ApplySettings settings = default)
=> ChangeEquip(data, slot, null, stain, settings);
public void ChangeStains(object data, EquipSlot slot, StainIds stains, ApplySettings settings = default)
=> ChangeEquip(data, slot, null, stains, settings);
/// <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>
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.Classes;
using OtterGui.Services;
using Notification = OtterGui.Classes.Notification;
namespace Glamourer.Designs.Links;

View file

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

View file

@ -11,7 +11,7 @@ namespace Glamourer.Events;
/// </list>
/// </summary>
public sealed class MovedEquipment()
: EventWrapper<(EquipSlot, uint, StainId)[], MovedEquipment.Priority>(nameof(MovedEquipment))
: EventWrapper<(EquipSlot, uint, StainIds)[], MovedEquipment.Priority>(nameof(MovedEquipment))
{
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 OtterGui.Classes;
using OtterGui.Services;
@ -32,8 +33,8 @@ public class CustomizeManager : IAsyncDataContainer
}
/// <summary> Get specific icons. </summary>
public IDalamudTextureWrap GetIcon(uint id)
=> _icons.LoadIcon(id)!;
public ISharedImmediateTexture GetIcon(uint id)
=> _icons.TextureProvider.GetFromGameIcon(id);
/// <summary> Iterate over all supported genders and clans. </summary>
public static IEnumerable<(SubRace Clan, Gender Gender)> AllSets()
@ -47,7 +48,7 @@ public class CustomizeManager : IAsyncDataContainer
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 tmpTask = Task.Run(() =>
{
@ -72,7 +73,7 @@ public class CustomizeManager : IAsyncDataContainer
public bool Finished
=> Awaiter.IsCompletedSuccessfully;
private readonly IconStorage _icons;
private readonly TextureCache _icons;
private static readonly int ListSize = Clans.Count * Genders.Count;
private readonly CustomizeSet[] _customizationSets = new CustomizeSet[ListSize];

View file

@ -1,4 +1,5 @@
using Dalamud;
using Dalamud.Game;
using Dalamud.Plugin.Services;
using Dalamud.Utility;
using Lumina.Excel;
@ -13,11 +14,11 @@ namespace Glamourer.GameData;
internal class CustomizeSetFactory(
IDataManager _gameData,
IPluginLog _log,
IconStorage _icons,
TextureCache _icons,
NpcCustomizeSet _npcCustomizeSet,
ColorParameters _colors)
{
public CustomizeSetFactory(IDataManager gameData, IPluginLog log, IconStorage icons, NpcCustomizeSet npcCustomizeSet)
public CustomizeSetFactory(IDataManager gameData, IPluginLog log, TextureCache icons, NpcCustomizeSet npcCustomizeSet)
: this(gameData, log, icons, npcCustomizeSet, new ColorParameters(gameData, log))
{ }
@ -87,7 +88,8 @@ internal class CustomizeSetFactory(
var npcCustomizations = new HashSet<(CustomizeIndex, CustomizeValue)>();
_npcCustomizeSet.Awaiter.Wait();
foreach (var customize in _npcCustomizeSet.Select(s => s.Customize).Where(c => c.Clan == race && c.Gender == gender && c.BodyType.Value == 1))
foreach (var customize in _npcCustomizeSet.Select(s => s.Customize)
.Where(c => c.Clan == race && c.Gender == gender && c.BodyType.Value == 1))
{
foreach (var customizeIndex in customizeIndices)
{
@ -346,10 +348,6 @@ internal class CustomizeSetFactory(
/// <summary> Set the availability of options according to actual availability. </summary>
private static void SetAvailability(CustomizeSet set, CharaMakeParams row)
{
// TODO: Hrothgar female
if (set is { Race: Race.Hrothgar, Gender: Gender.Female })
return;
Set(true, CustomizeIndex.Height);
Set(set.Faces.Count > 0, CustomizeIndex.Face);
Set(true, CustomizeIndex.Hairstyle);

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

View file

@ -13,7 +13,7 @@ public unsafe struct NpcData
public CustomizeArray Customize;
/// <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>
public CharacterWeapon Mainhand;
@ -54,36 +54,35 @@ public unsafe struct NpcData
{
sb.Append(span[i].Set.Id.ToString("D4"))
.Append('-')
.Append(span[i].Variant.Id.ToString("D3"))
.Append('-')
.Append(span[i].Stain.Id.ToString("D3"))
.Append(", ");
.Append(span[i].Variant.Id.ToString("D3"));
foreach (var stain in span[i].Stains)
sb.Append('-').Append(stain.Id.ToString("D3"));
}
sb.Append(Mainhand.Skeleton.Id.ToString("D4"))
.Append('-')
.Append(Mainhand.Weapon.Id.ToString("D4"))
.Append('-')
.Append(Mainhand.Variant.Id.ToString("D3"))
.Append('-')
.Append(Mainhand.Stain.Id.ToString("D4"))
.Append(", ")
.Append(Mainhand.Variant.Id.ToString("D3"));
foreach (var stain in Mainhand.Stains)
sb.Append('-').Append(stain.Id.ToString("D3"));
sb.Append(", ")
.Append(Offhand.Skeleton.Id.ToString("D4"))
.Append('-')
.Append(Offhand.Weapon.Id.ToString("D4"))
.Append('-')
.Append(Offhand.Variant.Id.ToString("D3"))
.Append('-')
.Append(Offhand.Stain.Id.ToString("D3"));
.Append(Offhand.Variant.Id.ToString("D3"));
foreach (var stain in Mainhand.Stains)
sb.Append('-').Append(stain.Id.ToString("D3"));
return sb.ToString();
}
/// <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)
{
((uint*)ptr)[idx] = value;
((ulong*)ptr)[idx] = value;
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,4 +1,4 @@
using Dalamud.Interface.Internal.Notifications;
using Dalamud.Interface.ImGuiNotification;
using Dalamud.Interface.Windowing;
using Dalamud.Plugin;
using Glamourer.Designs;
@ -64,7 +64,7 @@ public class MainWindow : Window, IDisposable
public TabType SelectTab;
public MainWindow(DalamudPluginInterface pi, Configuration config, SettingsTab settings, ActorTab actors, DesignTab designs,
public MainWindow(IDalamudPluginInterface pi, Configuration config, SettingsTab settings, ActorTab actors, DesignTab designs,
DebugTab debugTab, AutomationTab automation, UnlocksTab unlocks, TabSelected @event, MessagesTab messages, DesignQuickBar quickBar,
NpcTab npcs, MainWindowPosition position, PenumbraService penumbra)
: base("GlamourerMainWindow")

View file

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

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))
{
PrintRow(slot.ToName(), ItemString(state.BaseData, slot), ItemString(state.ModelData, slot), state.Sources[slot, false]);
ImGuiUtil.DrawTableColumn(state.BaseData.Stain(slot).Id.ToString());
ImGuiUtil.DrawTableColumn(state.ModelData.Stain(slot).Id.ToString());
ImGuiUtil.DrawTableColumn(state.BaseData.Stain(slot).ToString());
ImGuiUtil.DrawTableColumn(state.ModelData.Stain(slot).ToString());
ImGuiUtil.DrawTableColumn(state.Sources[slot, true].ToString());
}

View file

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

View file

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

View file

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

View file

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

View file

@ -11,7 +11,7 @@ using Penumbra.GameData.Structs;
namespace Glamourer.Gui.Tabs.DebugTab.IpcTester;
public class ItemsIpcTester(DalamudPluginInterface pluginInterface) : IUiService
public class ItemsIpcTester(IDalamudPluginInterface pluginInterface) : IUiService
{
private int _gameObjectIndex;
private string _gameObjectName = string.Empty;
@ -40,12 +40,12 @@ public class ItemsIpcTester(DalamudPluginInterface pluginInterface) : IUiService
IpcTesterHelpers.DrawIntro(SetItem.Label);
if (ImGui.Button("Set##Idx"))
_lastError = new SetItem(pluginInterface).Invoke(_gameObjectIndex, (ApiEquipSlot)_slot, _customItemId.Id, _stainId.Id, _key,
_lastError = new SetItem(pluginInterface).Invoke(_gameObjectIndex, (ApiEquipSlot)_slot, _customItemId.Id, [_stainId.Id], _key,
_flags);
IpcTesterHelpers.DrawIntro(SetItemName.Label);
if (ImGui.Button("Set##Name"))
_lastError = new SetItemName(pluginInterface).Invoke(_gameObjectName, (ApiEquipSlot)_slot, _customItemId.Id, _stainId.Id, _key,
_lastError = new SetItemName(pluginInterface).Invoke(_gameObjectName, (ApiEquipSlot)_slot, _customItemId.Id, [_stainId.Id], _key,
_flags);
}

View file

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

View file

@ -5,6 +5,8 @@ using Glamourer.Interop.Structs;
using ImGuiNET;
using OtterGui;
using OtterGui.Raii;
using OtterGui.Text;
using Penumbra.GameData.DataContainers;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Gui.Debug;
using Penumbra.GameData.Interop;
@ -18,7 +20,8 @@ public unsafe class ModelEvaluationPanel(
VisorService _visorService,
UpdateSlotService _updateSlotService,
ChangeCustomizeService _changeCustomizeService,
CrestService _crestService) : IGameDataDrawer
CrestService _crestService,
DictGlasses _glasses) : IGameDataDrawer
{
public string Label
=> "Model Evaluation";
@ -177,7 +180,7 @@ public unsafe class ModelEvaluationPanel(
{
using var id = ImRaii.PushId("Wetness");
ImGuiUtil.DrawTableColumn("Wetness");
ImGuiUtil.DrawTableColumn(actor.IsCharacter ? actor.AsCharacter->IsGPoseWet ? "GPose" : "None" : "No Character");
ImGuiUtil.DrawTableColumn(actor.IsCharacter ? actor.IsGPoseWet ? "GPose" : "None" : "No Character");
var modelString = model.IsCharacterBase
? $"{model.AsCharacterBase->SwimmingWetness:F4} Swimming\n"
+ $"{model.AsCharacterBase->WeatherWetness:F4} Weather\n"
@ -190,13 +193,13 @@ public unsafe class ModelEvaluationPanel(
return;
if (ImGui.SmallButton("GPose On"))
actor.AsCharacter->IsGPoseWet = true;
actor.IsGPoseWet = true;
ImGui.SameLine();
if (ImGui.SmallButton("GPose Off"))
actor.AsCharacter->IsGPoseWet = false;
actor.IsGPoseWet = false;
ImGui.SameLine();
if (ImGui.SmallButton("GPose Toggle"))
actor.AsCharacter->IsGPoseWet = !actor.AsCharacter->IsGPoseWet;
actor.IsGPoseWet = !actor.IsGPoseWet;
}
private void DrawEquip(Actor actor, Model model)
@ -214,14 +217,39 @@ public unsafe class ModelEvaluationPanel(
if (ImGui.SmallButton("Change Piece"))
_updateSlotService.UpdateArmor(model, slot,
new CharacterArmor((PrimaryId)(slot == EquipSlot.Hands ? 6064 : slot == EquipSlot.Head ? 6072 : 1), 1, 0));
new CharacterArmor((PrimaryId)(slot == EquipSlot.Hands ? 6064 : slot == EquipSlot.Head ? 6072 : 1), 1, StainIds.None));
ImGui.SameLine();
if (ImGui.SmallButton("Change Stain"))
_updateSlotService.UpdateStain(model, slot, 5);
_updateSlotService.UpdateStain(model, slot, new StainIds(5, 7));
ImGui.SameLine();
if (ImGui.SmallButton("Reset"))
_updateSlotService.UpdateSlot(model, slot, actor.GetArmor(slot));
}
using (ImRaii.PushId((int)EquipSlot.FaceWear))
{
ImGuiUtil.DrawTableColumn(EquipSlot.FaceWear.ToName());
if (!actor.IsCharacter)
{
ImGuiUtil.DrawTableColumn("No Character");
}
else
{
var glassesId = actor.AsCharacter->DrawData.GlassesIds[(int)EquipSlot.FaceWear.ToBonusIndex()];
if (_glasses.TryGetValue(glassesId, out var glasses))
ImGuiUtil.DrawTableColumn($"{glasses.Id.Id},{glasses.Variant.Id} ({glassesId})");
else
ImGuiUtil.DrawTableColumn($"{glassesId}");
}
ImGuiUtil.DrawTableColumn(model.IsHuman ? model.GetArmor(EquipSlot.FaceWear).ToString() : "No Human");
ImGui.TableNextColumn();
if (ImUtf8.SmallButton("Change Piece"u8))
{
var data = model.GetArmor(EquipSlot.FaceWear);
_updateSlotService.UpdateSlot(model, EquipSlot.FaceWear, data with { Variant = (Variant)((data.Variant.Id + 1) % 12) });
}
}
}
private void DrawCustomize(Actor actor, Model model)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -17,7 +17,7 @@ public unsafe class ChangeCustomizeService : EventWrapperRef2<Model, CustomizeAr
{
private readonly PenumbraReloaded _penumbraReloaded;
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();

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -4,6 +4,7 @@ using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
using OtterGui.Classes;
using OtterGui.Services;
using Penumbra.GameData;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Files.MaterialStructs;
using Penumbra.GameData.Interop;
@ -22,7 +23,7 @@ public sealed unsafe class PrepareColorSet
public PrepareColorSet(HookManager hooks)
: base("Prepare Color Set ")
=> _task = hooks.CreateHook<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;
@ -54,7 +55,7 @@ public sealed unsafe class PrepareColorSet
return _task.Result.Original(characterBase, material, stainId);
}
public static bool TryGetColorTable(CharacterBase* characterBase, MaterialResourceHandle* material, StainId stainId,
public static bool TryGetColorTable(CharacterBase* characterBase, MaterialResourceHandle* material, StainIds stainIds,
out LegacyColorTable table)
{
if (material->ColorTable == null)
@ -64,8 +65,9 @@ public sealed unsafe class PrepareColorSet
}
var newTable = *(LegacyColorTable*)material->ColorTable;
if (stainId.Id != 0)
characterBase->ReadStainingTemplate(material, stainId.Id, (Half*)(&newTable));
// TODO
//if (stainIds.Stain1.Id != 0 || stainIds.Stain2.Id != 0)
// characterBase->ReadStainingTemplate(material, stainId.Id, (Half*)(&newTable));
table = newTable;
return true;
}
@ -84,21 +86,21 @@ public sealed unsafe class PrepareColorSet
return false;
}
return TryGetColorTable(model.AsCharacterBase, handle, GetStain(), out table);
return TryGetColorTable(model.AsCharacterBase, handle, GetStains(), out table);
StainId GetStain()
StainIds GetStains()
{
switch (index.DrawObject)
{
case MaterialValueIndex.DrawObjectType.Human:
return index.SlotIndex < 10 ? actor.Model.GetArmor(((uint)index.SlotIndex).ToEquipSlot()).Stain : 0;
return index.SlotIndex < 10 ? actor.Model.GetArmor(((uint)index.SlotIndex).ToEquipSlot()).Stains : StainIds.None;
case MaterialValueIndex.DrawObjectType.Mainhand:
var mainhand = (Model)actor.AsCharacter->DrawData.WeaponDataSpan[1].DrawObject;
return mainhand.IsWeapon ? (StainId)mainhand.AsWeapon->ModelUnknown : 0;
var mainhand = (Model)actor.AsCharacter->DrawData.WeaponData[1].DrawObject;
return mainhand.IsWeapon ? StainIds.FromWeapon(*mainhand.AsWeapon) : StainIds.None;
case MaterialValueIndex.DrawObjectType.Offhand:
var offhand = (Model)actor.AsCharacter->DrawData.WeaponDataSpan[1].DrawObject;
return offhand.IsWeapon ? (StainId)offhand.AsWeapon->ModelUnknown : 0;
default: return 0;
var offhand = (Model)actor.AsCharacter->DrawData.WeaponData[1].DrawObject;
return offhand.IsWeapon ? StainIds.FromWeapon(*offhand.AsWeapon) : StainIds.None;
default: return StainIds.None;
}
}
}

View file

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

View file

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

View file

@ -6,7 +6,7 @@ using OtterGui.Services;
namespace Glamourer.Interop.PalettePlus;
public class PaletteImport(DalamudPluginInterface pluginInterface, DesignManager designManager, DesignFileSystem designFileSystem) : IService
public class PaletteImport(IDalamudPluginInterface pluginInterface, DesignManager designManager, DesignFileSystem designFileSystem) : IService
{
private string ConfigFile
=> Path.Combine(Path.GetDirectoryName(pluginInterface.GetPluginConfigDirectory())!, "PalettePlus.json");

View file

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

View file

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

View file

@ -3,6 +3,7 @@ using Dalamud.Hooking;
using Dalamud.Plugin.Services;
using Dalamud.Utility.Signatures;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using Penumbra.GameData;
using Penumbra.GameData.Interop;
using Character = FFXIVClientStructs.FFXIV.Client.Game.Character.Character;
@ -14,7 +15,7 @@ public unsafe class ScalingService : IDisposable
{
interop.InitializeFromAttributes(this);
_setupMountHook =
interop.HookFromAddress<SetupMount>((nint)Character.MountContainer.MemberFunctionPointers.SetupMount, SetupMountDetour);
interop.HookFromAddress<SetupMount>((nint)MountContainer.MemberFunctionPointers.SetupMount, SetupMountDetour);
_setupOrnamentHook = interop.HookFromAddress<SetupOrnament>((nint)Ornament.MemberFunctionPointers.SetupOrnament, SetupOrnamentDetour);
_calculateHeightHook =
interop.HookFromAddress<CalculateHeight>((nint)Character.MemberFunctionPointers.CalculateHeight, CalculateHeightDetour);
@ -33,7 +34,7 @@ public unsafe class ScalingService : IDisposable
_calculateHeightHook.Dispose();
}
private delegate void SetupMount(Character.MountContainer* container, short mountId, uint unk1, uint unk2, uint unk3, byte unk4);
private delegate void SetupMount(MountContainer* container, short mountId, uint unk1, uint unk2, uint unk3, byte unk4);
private delegate void SetupOrnament(Ornament* ornament, uint* unk1, float* unk2);
private delegate void PlaceMinion(Companion* character);
private delegate float CalculateHeight(Character* character);
@ -45,15 +46,15 @@ public unsafe class ScalingService : IDisposable
private readonly Hook<CalculateHeight> _calculateHeightHook;
// TODO: Use client structs sig.
[Signature("48 89 5C 24 ?? 55 57 41 57 48 8D 6C 24", DetourName = nameof(PlaceMinionDetour))]
[Signature(Sigs.PlaceMinion, DetourName = nameof(PlaceMinionDetour))]
private readonly Hook<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);
SetScaleCustomize(&container->OwnerObject->Character, container->OwnerObject->Character.GameObject.DrawObject);
var (race, clan, gender) = GetScaleRelevantCustomize(container->OwnerObject);
SetScaleCustomize(container->OwnerObject, container->OwnerObject->DrawObject);
_setupMountHook.Original(container, mountId, unk1, unk2, unk3, unk4);
SetScaleCustomize(&container->OwnerObject->Character, race, clan, gender);
SetScaleCustomize(container->OwnerObject, race, clan, gender);
}
private void SetupOrnamentDetour(Ornament* ornament, uint* unk1, float* unk2)

View file

@ -18,33 +18,44 @@ public unsafe class UpdateSlotService : IDisposable
SlotUpdatingEvent = slotUpdating;
interop.InitializeFromAttributes(this);
_flagSlotForUpdateHook.Enable();
_flagBonusSlotForUpdateHook.Enable();
}
public void Dispose()
=> _flagSlotForUpdateHook.Dispose();
{
_flagSlotForUpdateHook.Dispose();
_flagBonusSlotForUpdateHook.Dispose();
}
public void UpdateSlot(Model drawObject, EquipSlot slot, CharacterArmor data)
{
if (!drawObject.IsCharacterBase)
return;
var bonusSlot = slot.ToBonusIndex();
if (bonusSlot == uint.MaxValue)
FlagSlotForUpdateInterop(drawObject, slot, data);
else
_flagBonusSlotForUpdateHook.Original(drawObject.Address, bonusSlot, &data);
}
public void UpdateArmor(Model drawObject, EquipSlot slot, CharacterArmor armor, StainId stain)
=> UpdateSlot(drawObject, slot, armor.With(stain));
public void UpdateArmor(Model drawObject, EquipSlot slot, CharacterArmor armor, StainIds stains)
=> UpdateSlot(drawObject, slot, armor.With(stains));
public void UpdateArmor(Model drawObject, EquipSlot slot, CharacterArmor armor)
=> UpdateArmor(drawObject, slot, armor, drawObject.GetArmor(slot).Stain);
=> UpdateArmor(drawObject, slot, armor, drawObject.GetArmor(slot).Stains);
public void UpdateStain(Model drawObject, EquipSlot slot, StainId stain)
=> UpdateArmor(drawObject, slot, drawObject.GetArmor(slot), stain);
public void UpdateStain(Model drawObject, EquipSlot slot, StainIds stains)
=> UpdateArmor(drawObject, slot, drawObject.GetArmor(slot), stains);
private delegate ulong FlagSlotForUpdateDelegateIntern(nint drawObject, uint slot, CharacterArmor* data);
[Signature(Sigs.FlagSlotForUpdate, DetourName = nameof(FlagSlotForUpdateDetour))]
private readonly Hook<FlagSlotForUpdateDelegateIntern> _flagSlotForUpdateHook = null!;
[Signature(Sigs.FlagBonusSlotForUpdate, DetourName = nameof(FlagBonusSlotForUpdateDetour))]
private readonly Hook<FlagSlotForUpdateDelegateIntern> _flagBonusSlotForUpdateHook = null!;
private ulong FlagSlotForUpdateDetour(nint drawObject, uint slotIdx, CharacterArmor* data)
{
var slot = slotIdx.ToEquipSlot();
@ -54,6 +65,15 @@ public unsafe class UpdateSlotService : IDisposable
return returnValue == ulong.MaxValue ? _flagSlotForUpdateHook.Original(drawObject, slotIdx, data) : returnValue;
}
private ulong FlagBonusSlotForUpdateDetour(nint drawObject, uint slotIdx, CharacterArmor* data)
{
var slot = slotIdx.ToBonusSlot();
var returnValue = ulong.MaxValue;
SlotUpdatingEvent.Invoke(drawObject, slot, ref *data, ref returnValue);
Glamourer.Log.Excessive($"[FlagBonusSlotForUpdate] Called with 0x{drawObject:X} for slot {slot} with {*data} ({returnValue:X}).");
return returnValue == ulong.MaxValue ? _flagBonusSlotForUpdateHook.Original(drawObject, slotIdx, data) : returnValue;
}
private ulong FlagSlotForUpdateInterop(Model drawObject, EquipSlot slot, CharacterArmor armor)
=> _flagSlotForUpdateHook.Original(drawObject.Address, slot.ToIndex(), &armor);
}

View file

@ -70,7 +70,7 @@ public unsafe class WeaponService : IDisposable
if (tmpWeapon.Value != weapon.Value)
{
if (tmpWeapon.Skeleton.Id == 0)
tmpWeapon.Stain = 0;
tmpWeapon.Stains = StainIds.None;
_loadWeaponHook.Original(drawData, slot, tmpWeapon.Value, 1, unk2, 1, unk4);
}
@ -107,12 +107,12 @@ public unsafe class WeaponService : IDisposable
}
}
public void LoadStain(Actor character, EquipSlot slot, StainId stain)
public void LoadStain(Actor character, EquipSlot slot, StainIds stains)
{
var mdl = character.Model;
var (_, _, mh, oh) = mdl.GetWeapons(character);
var value = slot == EquipSlot.OffHand ? oh : mh;
var weapon = value.With(value.Skeleton.Id == 0 ? 0 : stain);
var weapon = value.With(value.Skeleton.Id == 0 ? StainIds.None : stains);
LoadWeapon(character, slot, weapon);
}
}

View file

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

View file

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

View file

@ -8,7 +8,7 @@ namespace Glamourer.Services;
public class DalamudServices
{
public static void AddServices(ServiceManager services, DalamudPluginInterface pi)
public static void AddServices(ServiceManager services, IDalamudPluginInterface pi)
{
services.AddExistingService(pi);
services.AddExistingService(pi.UiBuilder);

View file

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

View file

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

View file

@ -195,16 +195,16 @@ public class ItemManager
/// The returned stain id is either the input or 0.
/// The return value is an empty string if there was no problem and a warning otherwise.
/// </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;
}
ret = 0;
return $"The Stain {stain} does not exist, reset to unstained.";
ret = StainIds.None;
return $"The Stain {stains} does not exist, reset to unstained.";
}
/// <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 ServiceManager CreateProvider(DalamudPluginInterface pi, Logger log, Glamourer glamourer)
public static ServiceManager CreateProvider(IDalamudPluginInterface pi, Logger log, Glamourer glamourer)
{
EventWrapperBase.ChangeLogger(log);
var services = new ServiceManager(log)
@ -165,5 +165,6 @@ public static class StaticServiceManager
.AddSingleton<GlamourerChangelog>()
.AddSingleton<DesignQuickBar>()
.AddSingleton<DesignColorUi>()
.AddSingleton<NpcCombo>();
.AddSingleton<NpcCombo>()
.AddSingleton<TextureCache>();
}

View file

@ -1,5 +1,5 @@
using Dalamud.Interface;
using Dalamud.Interface.Internal;
using Dalamud.Interface.Textures.TextureWraps;
using Dalamud.Plugin.Services;
using OtterGui.Classes;
using Penumbra.GameData.Enums;
@ -7,7 +7,7 @@ using Penumbra.GameData.Structs;
namespace Glamourer.Services;
public sealed class TextureService(UiBuilder uiBuilder, IDataManager dataManager, ITextureProvider textureProvider)
public sealed class TextureService(IUiBuilder uiBuilder, IDataManager dataManager, ITextureProvider textureProvider)
: TextureCache(dataManager, textureProvider), IDisposable
{
private readonly IDalamudTextureWrap?[] _slotIcons = CreateSlotIcons(uiBuilder);
@ -32,7 +32,7 @@ public sealed class TextureService(UiBuilder uiBuilder, IDataManager dataManager
}
}
private static IDalamudTextureWrap?[] CreateSlotIcons(UiBuilder uiBuilder)
private static IDalamudTextureWrap?[] CreateSlotIcons(IUiBuilder uiBuilder)
{
var ret = new IDalamudTextureWrap?[12];

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -117,7 +117,7 @@ public sealed class StateManager(
if (!_humans.IsHuman((uint)actor.AsCharacter->CharacterData.ModelCharaId))
{
ret.LoadNonHuman((uint)actor.AsCharacter->CharacterData.ModelCharaId, *(CustomizeArray*)&actor.AsCharacter->DrawData.CustomizeData,
(nint)(&actor.AsCharacter->DrawData.Head));
(nint)Unsafe.AsPointer(ref actor.AsCharacter->DrawData.EquipmentModelIds[0]));
return ret;
}
@ -141,7 +141,7 @@ public sealed class StateManager(
var head = ret.IsHatVisible() || ignoreHatState ? model.GetArmor(EquipSlot.Head) : actor.GetArmor(EquipSlot.Head);
var headItem = Items.Identify(EquipSlot.Head, head.Set, head.Variant);
ret.SetItem(EquipSlot.Head, headItem);
ret.SetStain(EquipSlot.Head, head.Stain);
ret.SetStain(EquipSlot.Head, head.Stains);
// The other slots can be used from the draw object.
foreach (var slot in EquipSlotExtensions.EqdpSlots.Skip(1))
@ -149,7 +149,7 @@ public sealed class StateManager(
var armor = model.GetArmor(slot);
var item = Items.Identify(slot, armor.Set, armor.Variant);
ret.SetItem(slot, item);
ret.SetStain(slot, armor.Stain);
ret.SetStain(slot, armor.Stains);
}
// Weapons use the draw objects of the weapons, but require the game object either way.
@ -171,7 +171,7 @@ public sealed class StateManager(
var armor = actor.GetArmor(slot);
var item = Items.Identify(slot, armor.Set, armor.Variant);
ret.SetItem(slot, item);
ret.SetStain(slot, armor.Stain);
ret.SetStain(slot, armor.Stains);
}
main = actor.GetMainhand();
@ -187,13 +187,13 @@ public sealed class StateManager(
var mainItem = Items.Identify(EquipSlot.MainHand, main.Skeleton, main.Weapon, main.Variant);
var offItem = Items.Identify(EquipSlot.OffHand, off.Skeleton, off.Weapon, off.Variant, mainItem.Type);
ret.SetItem(EquipSlot.MainHand, mainItem);
ret.SetStain(EquipSlot.MainHand, main.Stain);
ret.SetStain(EquipSlot.MainHand, main.Stains);
ret.SetItem(EquipSlot.OffHand, offItem);
ret.SetStain(EquipSlot.OffHand, off.Stain);
ret.SetStain(EquipSlot.OffHand, off.Stains);
// Wetness can technically only be set in GPose or via external tools.
// It is only available in the game object.
ret.SetIsWet(actor.AsCharacter->IsGPoseWet);
ret.SetIsWet(actor.IsGPoseWet);
// Weapon visibility could technically be inferred from the weapon draw objects,
// but since we use hat visibility from the game object we can also use weapon visibility from it.
@ -214,7 +214,7 @@ public sealed class StateManager(
offhand.Variant = mainhand.Variant;
offhand.Weapon = mainhand.Weapon;
ret.SetItem(EquipSlot.Hands, gauntlets);
ret.SetStain(EquipSlot.Hands, mainhand.Stain);
ret.SetStain(EquipSlot.Hands, mainhand.Stains);
}
/// <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)
{
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);
}

View file

@ -22,7 +22,7 @@ public class WorldSets
[(Gender.Male, Race.AuRa)] = FunEquipSet.Group.FullSetWithoutHat(0257, 2),
[(Gender.Female, Race.AuRa)] = FunEquipSet.Group.FullSetWithoutHat(0258, 2),
[(Gender.Male, Race.Hrothgar)] = FunEquipSet.Group.FullSetWithoutHat(0597, 1),
[(Gender.Female, Race.Hrothgar)] = FunEquipSet.Group.FullSetWithoutHat(0000, 0), // TODO Hrothgar Female
[(Gender.Female, Race.Hrothgar)] = FunEquipSet.Group.FullSetWithoutHat(0829, 1),
[(Gender.Male, Race.Viera)] = FunEquipSet.Group.FullSetWithoutHat(0744, 1),
[(Gender.Female, Race.Viera)] = FunEquipSet.Group.FullSetWithoutHat(0581, 1),
};

View file

@ -1,4 +1,4 @@
using Dalamud;
using Dalamud.Game;
using Dalamud.Hooking;
using Dalamud.Plugin.Services;
using Dalamud.Utility;
@ -8,6 +8,7 @@ using Glamourer.GameData;
using Glamourer.Events;
using Glamourer.Services;
using Lumina.Excel.GeneratedSheets;
using Penumbra.GameData;
using Penumbra.GameData.Enums;
namespace Glamourer.Unlocks;
@ -128,7 +129,7 @@ public class CustomizeUnlockManager : IDisposable, ISavable
private delegate void SetUnlockLinkValueDelegate(nint uiState, uint data, byte value);
[Signature("48 83 EC ?? 8B C2 44 8B D2", DetourName = nameof(SetUnlockLinkValueDetour))]
[Signature(Sigs.SetUnlockLinkValue, DetourName = nameof(SetUnlockLinkValueDetour))]
private readonly Hook<SetUnlockLinkValueDelegate> _setUnlockLinkValueHook = null!;
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 Newtonsoft.Json;
using OtterGui.Classes;

View file

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

View file

@ -1,4 +1,4 @@
using Dalamud.Interface.Internal.Notifications;
using Dalamud.Interface.ImGuiNotification;
using OtterGui.Classes;
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",
"RepoUrl": "https://github.com/Ottermandias/Glamourer",
"ApplicableVersion": "any",
"DalamudApiLevel": 9,
"DalamudApiLevel": 10,
"IsHide": "False",
"IsTestingExclusive": "False",
"DownloadCount": 1,