From 20cc67275a477c15722c960fc158ad90ba57499e Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 10 Jul 2023 16:33:42 +0200 Subject: [PATCH] . --- Glamourer/Automation/AutoDesignApplier.cs | 16 ++-- Glamourer/Designs/Design.cs | 4 +- Glamourer/Designs/DesignBase.cs | 82 +++++++++++------- Glamourer/Designs/DesignData.cs | 56 ++++++------ Glamourer/Designs/DesignManager.cs | 42 ++------- Glamourer/Events/DesignChanged.cs | 3 - .../CustomizationDrawer.Color.cs | 29 +++++-- .../Gui/Customization/CustomizationDrawer.cs | 35 +++++++- Glamourer/Gui/Equipment/EquipmentDrawer.cs | 85 +++++++++++++++++-- Glamourer/Gui/Equipment/ItemCombo.cs | 4 +- Glamourer/Gui/Equipment/WeaponCombo.cs | 4 +- Glamourer/Gui/PenumbraChangedItemTooltip.cs | 2 +- Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 12 +-- Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs | 2 +- Glamourer/Gui/Tabs/DebugTab.cs | 17 +++- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 6 +- .../Gui/Tabs/UnlocksTab/UnlockOverview.cs | 4 +- Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs | 16 ++-- Glamourer/Interop/JobService.cs | 12 +-- Glamourer/Services/CodeService.cs | 8 +- Glamourer/Services/CustomizationService.cs | 12 +-- Glamourer/Services/ItemManager.cs | 21 +++-- Glamourer/State/StateEditor.cs | 13 ++- Glamourer/State/StateManager.cs | 4 +- Glamourer/Unlocks/ItemUnlockManager.cs | 8 +- 25 files changed, 324 insertions(+), 173 deletions(-) diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index 49685c5..2c95c8b 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -94,7 +94,7 @@ public class AutoDesignApplier : IDisposable } } - private void OnJobChange(Actor actor, Job _) + private void OnJobChange(Actor actor, Job oldJob, Job newJob) { if (!_config.EnableAutoDesigns || !actor.Identifier(_actors.AwaitedService, out var id)) return; @@ -102,16 +102,18 @@ public class AutoDesignApplier : IDisposable if (!GetPlayerSet(id, out var set)) { if (_state.TryGetValue(id, out var s)) - s.LastJob = actor.Job; + s.LastJob = (byte) newJob.Id; return; } if (!_state.GetOrCreate(id, actor, out var state)) return; - var sameJob = state.LastJob == actor.Job; + if (oldJob.Id == newJob.Id && state.LastJob == newJob.Id) + return; + state.LastJob = actor.Job; - Reduce(actor, state, set, sameJob); + Reduce(actor, state, set, state.LastJob == newJob.Id); _state.ReapplyState(actor); } @@ -185,7 +187,7 @@ public class AutoDesignApplier : IDisposable if (equipFlags.HasFlag(flag)) { var item = design.Item(slot); - if (_code.EnabledInventory || _itemUnlocks.IsUnlocked(item.Id, out _)) + if (_code.EnabledInventory || _itemUnlocks.IsUnlocked(item.ItemId, out _)) { if (!respectManual || state[slot, false] is not StateChanged.Source.Manual) _state.ChangeItem(state, slot, item, StateChanged.Source.Fixed); @@ -206,7 +208,7 @@ public class AutoDesignApplier : IDisposable { var item = design.Item(EquipSlot.MainHand); if (state.ModelData.Item(EquipSlot.MainHand).Type == item.Type - && (_code.EnabledInventory || _itemUnlocks.IsUnlocked(item.Id, out _))) + && (_code.EnabledInventory || _itemUnlocks.IsUnlocked(item.ItemId, out _))) { if (!respectManual || state[EquipSlot.MainHand, false] is not StateChanged.Source.Manual) _state.ChangeItem(state, EquipSlot.MainHand, item, StateChanged.Source.Fixed); @@ -218,7 +220,7 @@ public class AutoDesignApplier : IDisposable { var item = design.Item(EquipSlot.OffHand); if (state.ModelData.Item(EquipSlot.OffHand).Type == item.Type - && (_code.EnabledInventory || _itemUnlocks.IsUnlocked(item.Id, out _))) + && (_code.EnabledInventory || _itemUnlocks.IsUnlocked(item.ItemId, out _))) { if (!respectManual || state[EquipSlot.OffHand, false] is not StateChanged.Source.Manual) _state.ChangeItem(state, EquipSlot.OffHand, item, StateChanged.Source.Fixed); diff --git a/Glamourer/Designs/Design.cs b/Glamourer/Designs/Design.cs index dcc5760..f947671 100644 --- a/Glamourer/Designs/Design.cs +++ b/Glamourer/Designs/Design.cs @@ -130,8 +130,8 @@ public sealed class Design : DesignBase, ISavable if (design.LastEdit < creationDate) design.LastEdit = creationDate; - LoadCustomize(customizations, json["Customize"], design, design.Name); - LoadEquip(items, json["Equipment"], design, design.Name); + LoadCustomize(customizations, json["Customize"], design, design.Name, true, false); + LoadEquip(items, json["Equipment"], design, design.Name, false); LoadMods(json["Mods"], design); return design; } diff --git a/Glamourer/Designs/DesignBase.cs b/Glamourer/Designs/DesignBase.cs index 05f3545..4508caf 100644 --- a/Glamourer/Designs/DesignBase.cs +++ b/Glamourer/Designs/DesignBase.cs @@ -167,27 +167,33 @@ public class DesignBase protected JObject SerializeEquipment() { - static JObject Serialize(uint itemId, StainId stain, bool apply, bool applyStain) + static JObject Serialize(ulong id, StainId stain, bool apply, bool applyStain) => new() { - ["ItemId"] = itemId, + ["ItemId"] = id, ["Stain"] = stain.Value, ["Apply"] = apply, ["ApplyStain"] = applyStain, }; var ret = new JObject(); - foreach (var slot in EquipSlotExtensions.EqdpSlots.Prepend(EquipSlot.OffHand).Prepend(EquipSlot.MainHand)) + if (DesignData.IsHuman) { - var item = DesignData.Item(slot); - var stain = DesignData.Stain(slot); - ret[slot.ToString()] = Serialize(item.Id, stain, DoApplyEquip(slot), DoApplyStain(slot)); - } + foreach (var slot in EquipSlotExtensions.EqdpSlots.Prepend(EquipSlot.OffHand).Prepend(EquipSlot.MainHand)) + { + var item = DesignData.Item(slot); + var stain = DesignData.Stain(slot); + ret[slot.ToString()] = Serialize(item.Id, stain, DoApplyEquip(slot), DoApplyStain(slot)); + } - ret["Hat"] = new QuadBool(DesignData.IsHatVisible(), DoApplyHatVisible()).ToJObject("Show", "Apply"); - ret["Visor"] = new QuadBool(DesignData.IsVisorToggled(), DoApplyVisorToggle()).ToJObject("IsToggled", "Apply"); - ret["Weapon"] = new QuadBool(DesignData.IsWeaponVisible(), DoApplyWeaponVisible()).ToJObject("Show", "Apply"); - ret["Array"] = DesignData.WriteEquipmentBytesBase64(); + ret["Hat"] = new QuadBool(DesignData.IsHatVisible(), DoApplyHatVisible()).ToJObject("Show", "Apply"); + ret["Visor"] = new QuadBool(DesignData.IsVisorToggled(), DoApplyVisorToggle()).ToJObject("IsToggled", "Apply"); + ret["Weapon"] = new QuadBool(DesignData.IsWeaponVisible(), DoApplyWeaponVisible()).ToJObject("Show", "Apply"); + } + else + { + ret["Array"] = DesignData.WriteEquipmentBytesBase64(); + } return ret; } @@ -198,22 +204,25 @@ public class DesignBase { ["ModelId"] = DesignData.ModelId, }; + var customize = DesignData.Customize; - foreach (var idx in Enum.GetValues()) - { - ret[idx.ToString()] = new JObject() + if (DesignData.IsHuman) + foreach (var idx in Enum.GetValues()) { - ["Value"] = customize[idx].Value, - ["Apply"] = DoApplyCustomize(idx), - }; - } + ret[idx.ToString()] = new JObject() + { + ["Value"] = customize[idx].Value, + ["Apply"] = DoApplyCustomize(idx), + }; + } + else + ret["Array"] = customize.WriteBase64(); ret["Wetness"] = new JObject() { ["Value"] = DesignData.IsWet(), ["Apply"] = DoApplyWetness(), }; - ret["Array"] = DesignData.Customize.WriteBase64(); return ret; } @@ -235,12 +244,12 @@ public class DesignBase private static DesignBase LoadDesignV1Base(CustomizationService customizations, ItemManager items, JObject json) { var ret = new DesignBase(items); - LoadCustomize(customizations, json["Customize"], ret, "Temporary Design"); - LoadEquip(items, json["Equipment"], ret, "Temporary Design"); + LoadCustomize(customizations, json["Customize"], ret, "Temporary Design", false, true); + LoadEquip(items, json["Equipment"], ret, "Temporary Design", true); return ret; } - protected static void LoadEquip(ItemManager items, JToken? equip, DesignBase design, string name) + protected static void LoadEquip(ItemManager items, JToken? equip, DesignBase design, string name, bool allowUnknown) { if (equip == null) { @@ -257,9 +266,9 @@ public class DesignBase return; } - static (uint, StainId, bool, bool) ParseItem(EquipSlot slot, JToken? item) + static (ulong, StainId, bool, bool) ParseItem(EquipSlot slot, JToken? item) { - var id = item?["ItemId"]?.ToObject() ?? ItemManager.NothingId(slot); + var id = item?["ItemId"]?.ToObject() ?? ItemManager.NothingId(slot); var stain = (StainId)(item?["Stain"]?.ToObject() ?? 0); var apply = item?["Apply"]?.ToObject() ?? false; var applyStain = item?["ApplyStain"]?.ToObject() ?? false; @@ -276,8 +285,8 @@ public class DesignBase { var (id, stain, apply, applyStain) = ParseItem(slot, equip[slot.ToString()]); - PrintWarning(items.ValidateItem(slot, id, out var item)); - PrintWarning(items.ValidateStain(stain, out stain)); + PrintWarning(items.ValidateItem(slot, id, out var item, allowUnknown)); + PrintWarning(items.ValidateStain(stain, out stain, allowUnknown)); design.DesignData.SetItem(slot, item); design.DesignData.SetStain(slot, stain); design.SetApplyEquip(slot, apply); @@ -287,14 +296,14 @@ public class DesignBase { var (id, stain, apply, applyStain) = ParseItem(EquipSlot.MainHand, equip[EquipSlot.MainHand.ToString()]); if (id == ItemManager.NothingId(EquipSlot.MainHand)) - id = items.DefaultSword.Id; + id = items.DefaultSword.ItemId; var (idOff, stainOff, applyOff, applyStainOff) = ParseItem(EquipSlot.OffHand, equip[EquipSlot.OffHand.ToString()]); if (id == ItemManager.NothingId(EquipSlot.OffHand)) id = ItemManager.NothingId(FullEquipType.Shield); - PrintWarning(items.ValidateWeapons(id, idOff, out var main, out var off)); - PrintWarning(items.ValidateStain(stain, out stain)); - PrintWarning(items.ValidateStain(stainOff, out stainOff)); + PrintWarning(items.ValidateWeapons((uint)id, (uint)idOff, out var main, out var off)); + PrintWarning(items.ValidateStain(stain, out stain, allowUnknown)); + PrintWarning(items.ValidateStain(stainOff, out stainOff, allowUnknown)); design.DesignData.SetItem(EquipSlot.MainHand, main); design.DesignData.SetItem(EquipSlot.OffHand, off); design.DesignData.SetStain(EquipSlot.MainHand, stain); @@ -317,7 +326,8 @@ public class DesignBase design.DesignData.SetVisor(metaValue.ForcedValue); } - protected static void LoadCustomize(CustomizationService customizations, JToken? json, DesignBase design, string name) + protected static void LoadCustomize(CustomizationService customizations, JToken? json, DesignBase design, string name, bool forbidNonHuman, + bool allowUnknown) { if (json == null) { @@ -341,7 +351,13 @@ public class DesignBase design.DesignData.ModelId = json["ModelId"]?.ToObject() ?? 0; PrintWarning(customizations.ValidateModelId(design.DesignData.ModelId, out design.DesignData.ModelId, out design.DesignData.IsHuman)); - if (!design.DesignData.IsHuman) + if (design.DesignData.ModelId != 0 && forbidNonHuman) + { + PrintWarning("Model IDs different from 0 are not currently allowed, reset model id to 0."); + design.DesignData.ModelId = 0; + design.DesignData.IsHuman = true; + } + else if (!design.DesignData.IsHuman) { var arrayText = json["Array"]?.ToObject() ?? string.Empty; design.DesignData.Customize.LoadBase64(arrayText); @@ -366,7 +382,7 @@ public class DesignBase { var tok = json[idx.ToString()]; var data = (CustomizeValue)(tok?["Value"]?.ToObject() ?? 0); - PrintWarning(CustomizationService.ValidateCustomizeValue(set, design.DesignData.Customize.Face, idx, data, out data)); + PrintWarning(CustomizationService.ValidateCustomizeValue(set, design.DesignData.Customize.Face, idx, data, out data, allowUnknown)); var apply = tok?["Apply"]?.ToObject() ?? false; design.DesignData.Customize[idx] = data; design.SetApplyCustomize(idx, apply); diff --git a/Glamourer/Designs/DesignData.cs b/Glamourer/Designs/DesignData.cs index 7570b11..79f1e0e 100644 --- a/Glamourer/Designs/DesignData.cs +++ b/Glamourer/Designs/DesignData.cs @@ -45,22 +45,28 @@ public unsafe struct DesignData return index > 11 ? (StainId)0 : _equipmentBytes[4 * index + 3]; } + public FullEquipType MainhandType + => _typeMainhand; + + public FullEquipType OffhandType + => _typeOffhand; + public readonly EquipItem Item(EquipSlot slot) => slot.ToIndex() switch { // @formatter:off - 0 => new EquipItem(_nameHead, _itemIds[ 0], _iconIds[ 0], (SetId)(_equipmentBytes[ 0] | (_equipmentBytes[ 1] << 8)), (WeaponType)0, _equipmentBytes[ 2], FullEquipType.Head ), - 1 => new EquipItem(_nameBody, _itemIds[ 1], _iconIds[ 1], (SetId)(_equipmentBytes[ 4] | (_equipmentBytes[ 5] << 8)), (WeaponType)0, _equipmentBytes[ 6], FullEquipType.Body ), - 2 => new EquipItem(_nameHands, _itemIds[ 2], _iconIds[ 2], (SetId)(_equipmentBytes[ 8] | (_equipmentBytes[ 9] << 8)), (WeaponType)0, _equipmentBytes[10], FullEquipType.Hands ), - 3 => new EquipItem(_nameLegs, _itemIds[ 3], _iconIds[ 3], (SetId)(_equipmentBytes[12] | (_equipmentBytes[13] << 8)), (WeaponType)0, _equipmentBytes[14], FullEquipType.Legs ), - 4 => new EquipItem(_nameFeet, _itemIds[ 4], _iconIds[ 4], (SetId)(_equipmentBytes[16] | (_equipmentBytes[17] << 8)), (WeaponType)0, _equipmentBytes[18], FullEquipType.Feet ), - 5 => new EquipItem(_nameEars, _itemIds[ 5], _iconIds[ 5], (SetId)(_equipmentBytes[20] | (_equipmentBytes[21] << 8)), (WeaponType)0, _equipmentBytes[22], FullEquipType.Ears ), - 6 => new EquipItem(_nameNeck, _itemIds[ 6], _iconIds[ 6], (SetId)(_equipmentBytes[24] | (_equipmentBytes[25] << 8)), (WeaponType)0, _equipmentBytes[26], FullEquipType.Neck ), - 7 => new EquipItem(_nameWrists, _itemIds[ 7], _iconIds[ 7], (SetId)(_equipmentBytes[28] | (_equipmentBytes[29] << 8)), (WeaponType)0, _equipmentBytes[30], FullEquipType.Wrists ), - 8 => new EquipItem(_nameRFinger, _itemIds[ 8], _iconIds[ 8], (SetId)(_equipmentBytes[32] | (_equipmentBytes[33] << 8)), (WeaponType)0, _equipmentBytes[34], FullEquipType.Finger ), - 9 => new EquipItem(_nameLFinger, _itemIds[ 9], _iconIds[ 9], (SetId)(_equipmentBytes[36] | (_equipmentBytes[37] << 8)), (WeaponType)0, _equipmentBytes[38], FullEquipType.Finger ), - 10 => new EquipItem(_nameMainhand, _itemIds[10], _iconIds[10], (SetId)(_equipmentBytes[40] | (_equipmentBytes[41] << 8)), _secondaryMainhand, _equipmentBytes[42], _typeMainhand ), - 11 => new EquipItem(_nameOffhand, _itemIds[11], _iconIds[11], (SetId)(_equipmentBytes[44] | (_equipmentBytes[45] << 8)), _secondaryOffhand, _equipmentBytes[46], _typeOffhand ), + 0 => EquipItem.FromIds(_itemIds[ 0], _iconIds[ 0], (SetId)(_equipmentBytes[ 0] | (_equipmentBytes[ 1] << 8)), (WeaponType)0, _equipmentBytes[ 2], FullEquipType.Head, _nameHead ), + 1 => EquipItem.FromIds(_itemIds[ 1], _iconIds[ 1], (SetId)(_equipmentBytes[ 4] | (_equipmentBytes[ 5] << 8)), (WeaponType)0, _equipmentBytes[ 6], FullEquipType.Body, _nameBody ), + 2 => EquipItem.FromIds(_itemIds[ 2], _iconIds[ 2], (SetId)(_equipmentBytes[ 8] | (_equipmentBytes[ 9] << 8)), (WeaponType)0, _equipmentBytes[10], FullEquipType.Hands, _nameHands ), + 3 => EquipItem.FromIds(_itemIds[ 3], _iconIds[ 3], (SetId)(_equipmentBytes[12] | (_equipmentBytes[13] << 8)), (WeaponType)0, _equipmentBytes[14], FullEquipType.Legs, _nameLegs ), + 4 => EquipItem.FromIds(_itemIds[ 4], _iconIds[ 4], (SetId)(_equipmentBytes[16] | (_equipmentBytes[17] << 8)), (WeaponType)0, _equipmentBytes[18], FullEquipType.Feet, _nameFeet ), + 5 => EquipItem.FromIds(_itemIds[ 5], _iconIds[ 5], (SetId)(_equipmentBytes[20] | (_equipmentBytes[21] << 8)), (WeaponType)0, _equipmentBytes[22], FullEquipType.Ears, _nameEars ), + 6 => EquipItem.FromIds(_itemIds[ 6], _iconIds[ 6], (SetId)(_equipmentBytes[24] | (_equipmentBytes[25] << 8)), (WeaponType)0, _equipmentBytes[26], FullEquipType.Neck, _nameNeck ), + 7 => EquipItem.FromIds(_itemIds[ 7], _iconIds[ 7], (SetId)(_equipmentBytes[28] | (_equipmentBytes[29] << 8)), (WeaponType)0, _equipmentBytes[30], FullEquipType.Wrists, _nameWrists ), + 8 => EquipItem.FromIds(_itemIds[ 8], _iconIds[ 8], (SetId)(_equipmentBytes[32] | (_equipmentBytes[33] << 8)), (WeaponType)0, _equipmentBytes[34], FullEquipType.Finger, _nameRFinger ), + 9 => EquipItem.FromIds(_itemIds[ 9], _iconIds[ 9], (SetId)(_equipmentBytes[36] | (_equipmentBytes[37] << 8)), (WeaponType)0, _equipmentBytes[38], FullEquipType.Finger, _nameLFinger ), + 10 => EquipItem.FromIds(_itemIds[10], _iconIds[10], (SetId)(_equipmentBytes[40] | (_equipmentBytes[41] << 8)), _secondaryMainhand, _equipmentBytes[42], _typeMainhand, _nameMainhand), + 11 => EquipItem.FromIds(_itemIds[11], _iconIds[11], (SetId)(_equipmentBytes[44] | (_equipmentBytes[45] << 8)), _secondaryOffhand, _equipmentBytes[46], _typeOffhand, _nameOffhand ), _ => new EquipItem(), // @formatter:on }; @@ -86,10 +92,10 @@ public unsafe struct DesignData public bool SetItem(EquipSlot slot, EquipItem item) { var index = slot.ToIndex(); - if (index > 11 || _itemIds[index] == item.Id) + if (index > 11) return false; - _itemIds[index] = item.Id; + _itemIds[index] = item.ItemId; _iconIds[index] = item.IconId; _equipmentBytes[4 * index + 0] = (byte)item.ModelId; _equipmentBytes[4 * index + 1] = (byte)(item.ModelId.Value >> 8); @@ -212,7 +218,7 @@ public unsafe struct DesignData Customize.Load(customize); fixed (byte* ptr = _equipmentBytes) { - MemoryUtility.MemCpyUnchecked(ptr, (byte*) equipData, 40); + MemoryUtility.MemCpyUnchecked(ptr, (byte*)equipData, 40); } SetHatVisible(true); @@ -228,16 +234,16 @@ public unsafe struct DesignData MemoryUtility.MemSet(ptr, 0, 10 * 2); } - _nameHead = string.Empty; - _nameBody = string.Empty; - _nameHands = string.Empty; - _nameLegs = string.Empty; - _nameFeet = string.Empty; - _nameEars = string.Empty; - _nameNeck = string.Empty; - _nameWrists = string.Empty; - _nameRFinger = string.Empty; - _nameLFinger = string.Empty; + _nameHead = string.Empty; + _nameBody = string.Empty; + _nameHands = string.Empty; + _nameLegs = string.Empty; + _nameFeet = string.Empty; + _nameEars = string.Empty; + _nameNeck = string.Empty; + _nameWrists = string.Empty; + _nameRFinger = string.Empty; + _nameLFinger = string.Empty; return true; } diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index 5c58671..f705faa 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -13,7 +13,6 @@ using Newtonsoft.Json.Linq; using OtterGui; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; -using static OtterGui.Raii.ImRaii; namespace Glamourer.Designs; @@ -260,28 +259,6 @@ public class DesignManager _event.Invoke(DesignChanged.Type.WriteProtection, design, value); } - public void ChangeModelId(Design design, uint modelId, Customize customize, nint equipData, bool isHuman) - { - var oldValue = design.DesignData.ModelId; - - if (!isHuman) - { - design.DesignData.LoadNonHuman(modelId, customize, equipData); - } - else if (!design.DesignData.IsHuman) - { - design.DesignData.IsHuman = true; - design.DesignData.ModelId = modelId; - design.DesignData.SetDefaultEquipment(_items); - design.DesignData.Customize = Customize.Default; - } - - design.LastEdit = DateTimeOffset.UtcNow; - Glamourer.Log.Debug($"Changed model id in design {design.Identifier} from {oldValue} to {modelId}."); - _saveService.QueueSave(design); - _event.Invoke(DesignChanged.Type.ModelId, design, (oldValue, modelId)); - } - /// Change a customization value. public void ChangeCustomize(Design design, CustomizeIndex idx, CustomizeValue value) { @@ -332,7 +309,7 @@ public class DesignManager /// Change a non-weapon equipment piece. public void ChangeEquip(Design design, EquipSlot slot, EquipItem item) { - if (!_items.IsItemValid(slot, item.Id, out item)) + if (!_items.IsItemValid(slot, item.ItemId, out item)) return; var old = design.DesignData.Item(slot); @@ -341,7 +318,7 @@ public class DesignManager design.LastEdit = DateTimeOffset.UtcNow; Glamourer.Log.Debug( - $"Set {slot.ToName()} equipment piece in design {design.Identifier} from {old.Name} ({old.Id}) to {item.Name} ({item.Id})."); + $"Set {slot.ToName()} equipment piece in design {design.Identifier} from {old.Name} ({old.ItemId}) to {item.Name} ({item.ItemId})."); _saveService.QueueSave(design); _event.Invoke(DesignChanged.Type.Equip, design, (old, item, slot)); } @@ -355,13 +332,13 @@ public class DesignManager { case EquipSlot.MainHand: var newOff = currentOff; - if (!_items.IsItemValid(EquipSlot.MainHand, item.Id, out item)) + if (!_items.IsItemValid(EquipSlot.MainHand, item.ItemId, out item)) return; if (item.Type != currentMain.Type) { var newOffId = item.Type.Offhand().IsOffhandType() - ? item.Id + ? item.ItemId : ItemManager.NothingId(item.Type.Offhand()); if (!_items.IsOffhandValid(item, newOffId, out newOff)) return; @@ -373,12 +350,12 @@ public class DesignManager design.LastEdit = DateTimeOffset.UtcNow; _saveService.QueueSave(design); Glamourer.Log.Debug( - $"Set {EquipSlot.MainHand.ToName()} weapon in design {design.Identifier} from {currentMain.Name} ({currentMain.Id}) to {item.Name} ({item.Id})."); + $"Set {EquipSlot.MainHand.ToName()} weapon in design {design.Identifier} from {currentMain.Name} ({currentMain.ItemId}) to {item.Name} ({item.ItemId})."); _event.Invoke(DesignChanged.Type.Weapon, design, (currentMain, currentOff, item, newOff)); return; case EquipSlot.OffHand: - if (!_items.IsOffhandValid(currentOff.Type, item.Id, out item)) + if (!_items.IsOffhandValid(currentOff.Type, item.ItemId, out item)) return; if (!design.DesignData.SetItem(EquipSlot.OffHand, item)) @@ -387,7 +364,7 @@ public class DesignManager design.LastEdit = DateTimeOffset.UtcNow; _saveService.QueueSave(design); Glamourer.Log.Debug( - $"Set {EquipSlot.OffHand.ToName()} weapon in design {design.Identifier} from {currentOff.Name} ({currentOff.Id}) to {item.Name} ({item.Id})."); + $"Set {EquipSlot.OffHand.ToName()} weapon in design {design.Identifier} from {currentOff.Name} ({currentOff.ItemId}) to {item.Name} ({item.ItemId})."); _event.Invoke(DesignChanged.Type.Weapon, design, (currentMain, currentOff, currentMain, item)); return; default: return; @@ -409,7 +386,7 @@ public class DesignManager /// Change the stain for any equipment piece. public void ChangeStain(Design design, EquipSlot slot, StainId stain) { - if (_items.ValidateStain(stain, out _).Length > 0) + if (_items.ValidateStain(stain, out _, false).Length > 0) return; var oldStain = design.DesignData.Stain(slot); @@ -477,9 +454,6 @@ public class DesignManager /// Apply an entire design based on its appliance rules piece by piece. public void ApplyDesign(Design design, DesignBase other) { - ChangeModelId(design, other.DesignData.ModelId, other.DesignData.Customize, other.DesignData.GetEquipmentPtr(), - other.DesignData.IsHuman); - if (other.DoApplyWetness()) design.DesignData.SetIsWet(other.DesignData.IsWet()); if (other.DoApplyHatVisible()) diff --git a/Glamourer/Events/DesignChanged.cs b/Glamourer/Events/DesignChanged.cs index b36af52..154880a 100644 --- a/Glamourer/Events/DesignChanged.cs +++ b/Glamourer/Events/DesignChanged.cs @@ -46,9 +46,6 @@ public sealed class DesignChanged : EventWrapper An existing design had an existing associated mod removed. Data is the Mod and its Settings [(Mod, ModSettings)]. RemovedMod, - /// An existing design had its model id changed. This means everything else might also have changed. Data is the old value and the new value. [(uint, uint)]. - ModelId, - /// An existing design had a customization changed. Data is the old value, the new value and the type [(CustomizeValue, CustomizeValue, CustomizeIndex)]. Customize, diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs b/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs index b68624e..7d46a09 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs @@ -1,5 +1,6 @@ using System; using System.Numerics; +using Dalamud.Interface; using Glamourer.Customization; using ImGuiNET; using OtterGui.Raii; @@ -14,19 +15,33 @@ public partial class CustomizationDrawer { using var _ = SetId(index); var (current, custom) = GetCurrentCustomization(index); - var color = ImGui.ColorConvertU32ToFloat4(custom.Color); - // Print 1-based index instead of 0. - if (ImGui.ColorButton($"{current + 1}##color", color, ImGuiColorEditFlags.None, _framedIconSize)) - ImGui.OpenPopup(ColorPickerPopupName); + var color = ImGui.ColorConvertU32ToFloat4(current < 0 ? ImGui.GetColorU32(ImGuiCol.FrameBg) : custom.Color); + + using (var style = ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, 2 * ImGuiHelpers.GlobalScale, current < 0)) + { + // Print 1-based index instead of 0. + if (ImGui.ColorButton($"{current + 1}##color", color, ImGuiColorEditFlags.None, _framedIconSize)) + ImGui.OpenPopup(ColorPickerPopupName); + } + + if (current < 0) + { + using var font = ImRaii.PushFont(UiBuilder.IconFont); + var size = ImGui.CalcTextSize(FontAwesomeIcon.Question.ToIconString()); + var pos = ImGui.GetItemRectMin() + (ImGui.GetItemRectSize() - size) / 2; + ImGui.GetWindowDrawList().AddText(pos, ImGui.GetColorU32(ImGuiCol.Text), FontAwesomeIcon.Question.ToIconString()); + current = 0; + } ImGui.SameLine(); using (var group = ImRaii.Group()) { DataInputInt(current); - ImGui.TextUnformatted(_currentOption); + ImGui.TextUnformatted(custom.Color == 0 ? $"{_currentOption} (Custom #{custom.Value})" : _currentOption); } + DrawColorPickerPopup(); } @@ -57,8 +72,8 @@ public partial class CustomizationDrawer { var current = _set.DataByValue(index, _customize[index], out var custom, _customize.Face); if (_set.IsAvailable(index) && current < 0) - throw new Exception($"Read invalid customization value {_customize[index]} for {index}."); + return (current, new CustomizeData(index, _customize[index], 0, 0)); return (current, custom!.Value); } -} \ No newline at end of file +} diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.cs b/Glamourer/Gui/Customization/CustomizationDrawer.cs index 7f78b3d..36ecd03 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.cs @@ -1,17 +1,21 @@ using System; using System.Numerics; using System.Reflection; +using Dalamud.Interface; using Dalamud.Plugin; using Glamourer.Customization; using Glamourer.Services; using ImGuiNET; using OtterGui; using OtterGui.Raii; +using CustomizeData = Penumbra.GameData.Structs.CustomizeData; namespace Glamourer.Gui.Customization; public partial class CustomizationDrawer : IDisposable { + private readonly CodeService _codes; + private readonly Vector4 _redTint = new(0.6f, 0.3f, 0.3f, 1f); private readonly ImGuiScene.TextureWrap? _legacyTattoo; @@ -38,9 +42,10 @@ public partial class CustomizationDrawer : IDisposable private readonly CustomizationService _service; - public CustomizationDrawer(DalamudPluginInterface pi, CustomizationService service) + public CustomizationDrawer(DalamudPluginInterface pi, CustomizationService service, CodeService codes) { _service = service; + _codes = codes; _legacyTattoo = GetLegacyTattooIcon(pi); _customize = Customize.Default; } @@ -100,6 +105,9 @@ public partial class CustomizationDrawer : IDisposable try { + if (_codes.EnabledArtisan) + return DrawArtisan(); + DrawRaceGenderSelector(); _set = _service.AwaitedService.GetList(_customize.Clan, _customize.Gender); @@ -129,6 +137,31 @@ public partial class CustomizationDrawer : IDisposable } } + private unsafe bool DrawArtisan() + { + for (var i = 0; i < CustomizeData.Size; ++i) + { + using var id = ImRaii.PushId(i); + int value = _customize.Data.Data[i]; + ImGui.SetNextItemWidth(40 * ImGuiHelpers.GlobalScale); + if (!ImGui.InputInt(string.Empty, ref value, 0, 0)) + continue; + + var newValue = (byte)Math.Clamp(value, 0, byte.MaxValue); + if (newValue != _customize.Data.Data[i]) + foreach (var flag in Enum.GetValues()) + { + var (j, mask) = flag.ToByteAndMask(); + if (j == i) + Changed |= flag.ToFlag(); + } + + _customize.Data.Data[i] = newValue; + } + + return Changed != 0; + } + private void UpdateSizes() { _iconSize = new Vector2(ImGui.GetTextLineHeightWithSpacing() * 2); diff --git a/Glamourer/Gui/Equipment/EquipmentDrawer.cs b/Glamourer/Gui/Equipment/EquipmentDrawer.cs index 4464b3c..007a72f 100644 --- a/Glamourer/Gui/Equipment/EquipmentDrawer.cs +++ b/Glamourer/Gui/Equipment/EquipmentDrawer.cs @@ -8,6 +8,7 @@ using Glamourer.Designs; using Glamourer.Services; using ImGuiNET; using OtterGui; +using OtterGui.Raii; using OtterGui.Widgets; using Penumbra.GameData.Data; using Penumbra.GameData.Enums; @@ -22,10 +23,13 @@ public class EquipmentDrawer private readonly StainData _stainData; private readonly ItemCombo[] _itemCombo; private readonly Dictionary _weaponCombo; + private readonly CodeService _codes; - public EquipmentDrawer(DataManager gameData, ItemManager items) + + public EquipmentDrawer(DataManager gameData, ItemManager items, CodeService codes) { _items = items; + _codes = codes; _stainData = items.Stains; _stainCombo = new FilterComboColors(140, _stainData.Data.Prepend(new KeyValuePair(0, ("None", 0, false)))); @@ -57,9 +61,12 @@ public class EquipmentDrawer public bool DrawArmor(EquipItem current, EquipSlot slot, out EquipItem armor, Gender gender = Gender.Unknown, Race race = Race.Unknown) { Debug.Assert(slot.IsEquipment() || slot.IsAccessory(), $"Called {nameof(DrawArmor)} on {slot}."); + if (_codes.EnabledArtisan) + return DrawArmorArtisan(current, slot, out armor, gender, race); + var combo = _itemCombo[slot.ToIndex()]; armor = current; - var change = combo.Draw(VerifyRestrictedGear(armor, slot, gender, race), armor.Id, 320 * ImGuiHelpers.GlobalScale); + var change = combo.Draw(VerifyRestrictedGear(armor, slot, gender, race), armor.ItemId, 320 * ImGuiHelpers.GlobalScale); if (armor.ModelId.Value != 0) { ImGuiUtil.HoverTooltip("Right-click to clear."); @@ -81,18 +88,82 @@ public class EquipmentDrawer return change; } - public bool DrawStain(StainId current, EquipSlot slot, out Stain stain) + public bool DrawArmorArtisan(EquipItem current, EquipSlot slot, out EquipItem armor, Gender gender = Gender.Unknown, + Race race = Race.Unknown) { - var found = _stainData.TryGetValue(current, out stain); + using var id = ImRaii.PushId((int)slot); + int setId = current.ModelId.Value; + int variant = current.Variant; + var ret = false; + armor = current; + ImGui.SetNextItemWidth(80 * ImGuiHelpers.GlobalScale); + if (ImGui.InputInt("##setId", ref setId, 0, 0)) + { + var newSetId = (SetId)Math.Clamp(setId, 0, ushort.MaxValue); + if (newSetId.Value != current.ModelId.Value) + { + armor = _items.Identify(slot, newSetId, current.Variant); + ret = true; + } + } + + ImGui.SameLine(); + ImGui.SetNextItemWidth(40 * ImGuiHelpers.GlobalScale); + if (ImGui.InputInt("##variant", ref variant, 0, 0)) + { + var newVariant = (byte)Math.Clamp(variant, 0, byte.MaxValue); + if (newVariant != current.Variant) + { + armor = _items.Identify(slot, current.ModelId, newVariant); + ret = true; + } + } + + return ret; + } + + public bool DrawStain(StainId current, EquipSlot slot, out StainId ret) + { + if (_codes.EnabledArtisan) + return DrawStainArtisan(current, slot, out ret); + + var found = _stainData.TryGetValue(current, out var stain); var change = _stainCombo.Draw($"##stain{slot}", stain.RgbaColor, stain.Name, found); ImGuiUtil.HoverTooltip("Right-click to clear."); if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) { stain = Stain.None; + ret = stain.RowIndex; return true; } - return change && _stainData.TryGetValue(_stainCombo.CurrentSelection.Key, out stain); + if (change && _stainData.TryGetValue(_stainCombo.CurrentSelection.Key, out stain)) + { + ret = stain.RowIndex; + return true; + } + + ret = current; + return false; + } + + public bool DrawStainArtisan(StainId current, EquipSlot slot, out StainId stain) + { + using var id = ImRaii.PushId((int)slot); + int stainId = current.Value; + ImGui.SetNextItemWidth(40 * ImGuiHelpers.GlobalScale); + if (ImGui.InputInt("##stain", ref stainId, 0, 0)) + { + var newStainId = (StainId)Math.Clamp(stainId, 0, byte.MaxValue); + if (newStainId != current) + { + stain = newStainId; + return true; + } + } + + stain = current; + return false; } public bool DrawMainhand(EquipItem current, bool drawAll, out EquipItem weapon) @@ -101,7 +172,7 @@ public class EquipmentDrawer if (!_weaponCombo.TryGetValue(drawAll ? FullEquipType.Unknown : current.Type, out var combo)) return false; - if (!combo.Draw(weapon.Name, weapon.Id, 320 * ImGuiHelpers.GlobalScale)) + if (!combo.Draw(weapon.Name, weapon.ItemId, 320 * ImGuiHelpers.GlobalScale)) return false; weapon = combo.CurrentSelection; @@ -118,7 +189,7 @@ public class EquipmentDrawer if (!_weaponCombo.TryGetValue(offType, out var combo)) return false; - var change = combo.Draw(weapon.Name, weapon.Id, 320 * ImGuiHelpers.GlobalScale); + var change = combo.Draw(weapon.Name, weapon.ItemId, 320 * ImGuiHelpers.GlobalScale); if (!offType.IsOffhandType() && weapon.ModelId.Value != 0) { ImGuiUtil.HoverTooltip("Right-click to clear."); diff --git a/Glamourer/Gui/Equipment/ItemCombo.cs b/Glamourer/Gui/Equipment/ItemCombo.cs index e324656..d8163be 100644 --- a/Glamourer/Gui/Equipment/ItemCombo.cs +++ b/Glamourer/Gui/Equipment/ItemCombo.cs @@ -34,10 +34,10 @@ public sealed class ItemCombo : FilterComboCache protected override int UpdateCurrentSelected(int currentSelected) { - if (CurrentSelection.Id == _currentItem) + if (CurrentSelection.ItemId == _currentItem) return currentSelected; - CurrentSelectionIdx = Items.IndexOf(i => i.Id == _currentItem); + CurrentSelectionIdx = Items.IndexOf(i => i.ItemId == _currentItem); CurrentSelection = CurrentSelectionIdx >= 0 ? Items[CurrentSelectionIdx] : default; return base.UpdateCurrentSelected(CurrentSelectionIdx); diff --git a/Glamourer/Gui/Equipment/WeaponCombo.cs b/Glamourer/Gui/Equipment/WeaponCombo.cs index 4fb112e..4946906 100644 --- a/Glamourer/Gui/Equipment/WeaponCombo.cs +++ b/Glamourer/Gui/Equipment/WeaponCombo.cs @@ -30,10 +30,10 @@ public sealed class WeaponCombo : FilterComboCache protected override int UpdateCurrentSelected(int currentSelected) { - if (CurrentSelection.Id == _currentItemId) + if (CurrentSelection.ItemId == _currentItemId) return currentSelected; - CurrentSelectionIdx = Items.IndexOf(i => i.Id == _currentItemId); + CurrentSelectionIdx = Items.IndexOf(i => i.ItemId == _currentItemId); CurrentSelection = CurrentSelectionIdx >= 0 ? Items[CurrentSelectionIdx] : default; return base.UpdateCurrentSelected(CurrentSelectionIdx); } diff --git a/Glamourer/Gui/PenumbraChangedItemTooltip.cs b/Glamourer/Gui/PenumbraChangedItemTooltip.cs index 44bdc1b..c51ad04 100644 --- a/Glamourer/Gui/PenumbraChangedItemTooltip.cs +++ b/Glamourer/Gui/PenumbraChangedItemTooltip.cs @@ -210,7 +210,7 @@ public class PenumbraChangedItemTooltip : IDisposable else { var oldItem = state.ModelData.Item(slot); - if (oldItem.Id != item.Id) + if (oldItem.ItemId != item.ItemId) _lastItems[slot.ToIndex()] = oldItem; } } diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index 2639528..100183b 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -131,33 +131,33 @@ public class ActorPanel { var stain = _state.ModelData.Stain(slot); if (_equipmentDrawer.DrawStain(stain, slot, out var newStain)) - _stateManager.ChangeStain(_state, slot, newStain.RowIndex, StateChanged.Source.Manual); + _stateManager.ChangeStain(_state, slot, newStain, StateChanged.Source.Manual); ImGui.SameLine(); var armor = _state.ModelData.Item(slot); if (_equipmentDrawer.DrawArmor(armor, slot, out var newArmor, _state.ModelData.Customize.Gender, _state.ModelData.Customize.Race)) - _stateManager.ChangeEquip(_state, slot, newArmor, newStain.RowIndex, StateChanged.Source.Manual); + _stateManager.ChangeEquip(_state, slot, newArmor, newStain, StateChanged.Source.Manual); } var mhStain = _state.ModelData.Stain(EquipSlot.MainHand); if (_equipmentDrawer.DrawStain(mhStain, EquipSlot.MainHand, out var newMhStain)) - _stateManager.ChangeStain(_state, EquipSlot.MainHand, newMhStain.RowIndex, StateChanged.Source.Manual); + _stateManager.ChangeStain(_state, EquipSlot.MainHand, newMhStain, StateChanged.Source.Manual); ImGui.SameLine(); var mh = _state.ModelData.Item(EquipSlot.MainHand); if (_equipmentDrawer.DrawMainhand(mh, false, out var newMh)) - _stateManager.ChangeEquip(_state, EquipSlot.MainHand, newMh, newMhStain.RowIndex, StateChanged.Source.Manual); + _stateManager.ChangeEquip(_state, EquipSlot.MainHand, newMh, newMhStain, StateChanged.Source.Manual); if (newMh.Type.Offhand() is not FullEquipType.Unknown) { var ohStain = _state.ModelData.Stain(EquipSlot.OffHand); if (_equipmentDrawer.DrawStain(ohStain, EquipSlot.OffHand, out var newOhStain)) - _stateManager.ChangeStain(_state, EquipSlot.OffHand, newOhStain.RowIndex, StateChanged.Source.Manual); + _stateManager.ChangeStain(_state, EquipSlot.OffHand, newOhStain, StateChanged.Source.Manual); ImGui.SameLine(); var oh = _state.ModelData.Item(EquipSlot.OffHand); if (_equipmentDrawer.DrawMainhand(oh, false, out var newOh)) - _stateManager.ChangeEquip(_state, EquipSlot.OffHand, newOh, newOhStain.RowIndex, StateChanged.Source.Manual); + _stateManager.ChangeEquip(_state, EquipSlot.OffHand, newOh, newOhStain, StateChanged.Source.Manual); } } diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs index eed59af..0fe5c19 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs @@ -168,7 +168,7 @@ public class SetPanel continue; var item = design.Design.DesignData.Item(slot); - if (!_itemUnlocks.IsUnlocked(item.Id, out _)) + if (!_itemUnlocks.IsUnlocked(item.ItemId, out _)) sb.AppendLine($"{item.Name} in {slot.ToName()} slot is not unlocked but should be applied."); } diff --git a/Glamourer/Gui/Tabs/DebugTab.cs b/Glamourer/Gui/Tabs/DebugTab.cs index 5f99b05..1da882b 100644 --- a/Glamourer/Gui/Tabs/DebugTab.cs +++ b/Glamourer/Gui/Tabs/DebugTab.cs @@ -155,6 +155,15 @@ public unsafe class DebugTab : ITab ImGui.TableNextColumn(); ImGuiUtil.CopyOnClickSelectable(model.ToString()); ImGui.TableNextColumn(); + if (actor.IsCharacter) + { + if (actor.AsCharacter->CharacterData.TransformationId != 0) + ImGui.TextUnformatted($"Transformation Id: {actor.AsCharacter->CharacterData.TransformationId}"); + if (actor.AsCharacter->CharacterData.ModelCharaId_2 != -1) + ImGui.TextUnformatted($"ModelChara2 {actor.AsCharacter->CharacterData.ModelCharaId_2}"); + if (actor.AsCharacter->CharacterData.StatusEffectVFXId != 0) + ImGui.TextUnformatted($"Status Id: {actor.AsCharacter->CharacterData.StatusEffectVFXId}"); + } ImGuiUtil.DrawTableColumn("Mainhand"); ImGuiUtil.DrawTableColumn(actor.IsCharacter ? actor.GetMainhand().ToString() : "No Character"); @@ -712,7 +721,7 @@ public unsafe class DebugTab : ITab return; disabled.Dispose(); - ImRaii.TreeNode($"Default Sword: {_items.DefaultSword.Name} ({_items.DefaultSword.Id}) ({_items.DefaultSword.Weapon()})", + ImRaii.TreeNode($"Default Sword: {_items.DefaultSword.Name} ({_items.DefaultSword.ItemId}) ({_items.DefaultSword.Weapon()})", ImGuiTreeNodeFlags.Leaf).Dispose(); DrawNameTable("All Items (Main)", ref _itemFilter, _items.ItemService.AwaitedService.AllItems(true).Select(p => (p.Item1, @@ -726,7 +735,7 @@ public unsafe class DebugTab : ITab { DrawNameTable(type.ToName(), ref _itemFilter, _items.ItemService.AwaitedService[type] - .Select(p => (p.Id, $"{p.Name} ({(p.WeaponType == 0 ? p.Armor().ToString() : p.Weapon().ToString())})"))); + .Select(p => (Id: p.ItemId, $"{p.Name} ({(p.WeaponType == 0 ? p.Armor().ToString() : p.Weapon().ToString())})"))); } } @@ -1091,7 +1100,7 @@ public unsafe class DebugTab : ITab var stain = data.Stain(slot); ImGuiUtil.DrawTableColumn(slot.ToName()); ImGuiUtil.DrawTableColumn(item.Name); - ImGuiUtil.DrawTableColumn(item.Id.ToString()); + ImGuiUtil.DrawTableColumn(item.ItemId.ToString()); ImGuiUtil.DrawTableColumn(stain.ToString()); } @@ -1175,7 +1184,7 @@ public unsafe class DebugTab : ITab var applyStain = design.DoApplyStain(slot); ImGuiUtil.DrawTableColumn(slot.ToName()); ImGuiUtil.DrawTableColumn(item.Name); - ImGuiUtil.DrawTableColumn(item.Id.ToString()); + ImGuiUtil.DrawTableColumn(item.ItemId.ToString()); ImGuiUtil.DrawTableColumn(apply ? "Apply" : "Keep"); ImGuiUtil.DrawTableColumn(stain.ToString()); ImGuiUtil.DrawTableColumn(applyStain ? "Apply" : "Keep"); diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index 8f6d75d..53866ed 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -131,7 +131,7 @@ public class DesignPanel { var stain = _selector.Selected!.DesignData.Stain(slot); if (_equipmentDrawer.DrawStain(stain, slot, out var newStain)) - _manager.ChangeStain(_selector.Selected!, slot, newStain.RowIndex); + _manager.ChangeStain(_selector.Selected!, slot, newStain); ImGui.SameLine(); var armor = _selector.Selected!.DesignData.Item(slot); @@ -142,7 +142,7 @@ public class DesignPanel var mhStain = _selector.Selected!.DesignData.Stain(EquipSlot.MainHand); if (_equipmentDrawer.DrawStain(mhStain, EquipSlot.MainHand, out var newMhStain)) - _manager.ChangeStain(_selector.Selected!, EquipSlot.MainHand, newMhStain.RowIndex); + _manager.ChangeStain(_selector.Selected!, EquipSlot.MainHand, newMhStain); ImGui.SameLine(); var mh = _selector.Selected!.DesignData.Item(EquipSlot.MainHand); @@ -153,7 +153,7 @@ public class DesignPanel { var ohStain = _selector.Selected!.DesignData.Stain(EquipSlot.OffHand); if (_equipmentDrawer.DrawStain(ohStain, EquipSlot.OffHand, out var newOhStain)) - _manager.ChangeStain(_selector.Selected!, EquipSlot.OffHand, newOhStain.RowIndex); + _manager.ChangeStain(_selector.Selected!, EquipSlot.OffHand, newOhStain); ImGui.SameLine(); var oh = _selector.Selected!.DesignData.Item(EquipSlot.OffHand); diff --git a/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs b/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs index d0386ad..d4aae47 100644 --- a/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs +++ b/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs @@ -153,7 +153,7 @@ public class UnlockOverview void DrawItem(EquipItem item) { - var unlocked = _itemUnlocks.IsUnlocked(item.Id, out var time); + var unlocked = _itemUnlocks.IsUnlocked(item.ItemId, out var time); var iconHandle = _textureCache.LoadIcon(item.IconId); if (!iconHandle.HasValue) return; @@ -179,7 +179,7 @@ public class UnlockOverview ImGui.TextUnformatted($"{item.Type.ToName()} ({slot.ToName()})"); if (item.Type.Offhand().IsOffhandType()) ImGui.TextUnformatted( - $"{item.Weapon()}{(_items.ItemService.AwaitedService.TryGetValue(item.Id, false, out var offhand) ? $" | {offhand.Weapon()}" : string.Empty)}"); + $"{item.Weapon()}{(_items.ItemService.AwaitedService.TryGetValue(item.ItemId, false, out var offhand) ? $" | {offhand.Weapon()}" : string.Empty)}"); else ImGui.TextUnformatted(slot is EquipSlot.MainHand ? $"{item.Weapon()}" : $"{item.Armor()}"); ImGui.TextUnformatted( diff --git a/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs b/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs index e5e18ce..2f3faff 100644 --- a/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs +++ b/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs @@ -193,7 +193,7 @@ public class UnlockTable : Table, IDisposable public override void DrawColumn(EquipItem item, int idx) { - if (!_unlocks.IsUnlocked(item.Id, out var time)) + if (!_unlocks.IsUnlocked(item.ItemId, out var time)) return; ImGui.AlignTextToFramePadding(); @@ -202,8 +202,8 @@ public class UnlockTable : Table, IDisposable public override int Compare(EquipItem lhs, EquipItem rhs) { - var unlockedLhs = _unlocks.IsUnlocked(lhs.Id, out var timeLhs); - var unlockedRhs = _unlocks.IsUnlocked(rhs.Id, out var timeRhs); + var unlockedLhs = _unlocks.IsUnlocked(lhs.ItemId, out var timeLhs); + var unlockedRhs = _unlocks.IsUnlocked(rhs.ItemId, out var timeRhs); var c1 = unlockedLhs.CompareTo(unlockedRhs); return c1 != 0 ? c1 : timeLhs.CompareTo(timeRhs); } @@ -215,15 +215,15 @@ public class UnlockTable : Table, IDisposable => 70 * ImGuiHelpers.GlobalScale; public override int Compare(EquipItem lhs, EquipItem rhs) - => lhs.Id.CompareTo(rhs.Id); + => lhs.ItemId.CompareTo(rhs.ItemId); public override string ToName(EquipItem item) - => item.Id.ToString(); + => item.ItemId.ToString(); public override void DrawColumn(EquipItem item, int _) { ImGui.AlignTextToFramePadding(); - ImGuiUtil.RightAlign(item.Id.ToString()); + ImGuiUtil.RightAlign(item.ItemId.ToString()); } } @@ -243,7 +243,7 @@ public class UnlockTable : Table, IDisposable ImGuiUtil.RightAlign(item.ModelString); if (ImGui.IsItemHovered() && item.Type.Offhand().IsOffhandType() - && _items.ItemService.AwaitedService.TryGetValue(item.Id, false, out var offhand)) + && _items.ItemService.AwaitedService.TryGetValue(item.ItemId, false, out var offhand)) { using var tt = ImRaii.Tooltip(); ImGui.TextUnformatted("Offhand: " + offhand.ModelString); @@ -261,7 +261,7 @@ public class UnlockTable : Table, IDisposable if (FilterRegex?.IsMatch(item.ModelString) ?? item.ModelString.Contains(FilterValue, StringComparison.OrdinalIgnoreCase)) return true; - if (item.Type.Offhand().IsOffhandType() && _items.ItemService.AwaitedService.TryGetValue(item.Id, false, out var offhand)) + if (item.Type.Offhand().IsOffhandType() && _items.ItemService.AwaitedService.TryGetValue(item.ItemId, false, out var offhand)) return FilterRegex?.IsMatch(offhand.ModelString) ?? offhand.ModelString.Contains(FilterValue, StringComparison.OrdinalIgnoreCase); diff --git a/Glamourer/Interop/JobService.cs b/Glamourer/Interop/JobService.cs index 877f6a1..a4249a6 100644 --- a/Glamourer/Interop/JobService.cs +++ b/Glamourer/Interop/JobService.cs @@ -17,7 +17,7 @@ public class JobService : IDisposable public readonly IReadOnlyDictionary Jobs; public readonly IReadOnlyDictionary JobGroups; - public event Action? JobChanged; + public event Action? JobChanged; public JobService(DataManager gameData) { @@ -40,10 +40,12 @@ public class JobService : IDisposable private void ChangeJobDetour(nint data, uint jobIndex) { + var old = ((Actor)(data - _characterDataOffset)).Job; _changeJobHook.Original(data, jobIndex); - var actor = (Actor)(data - _characterDataOffset); - var job = Jobs.TryGetValue((byte) jobIndex, out var j) ? j : Jobs[0]; - Glamourer.Log.Excessive($"{actor} changed job to {job}"); - JobChanged?.Invoke(actor, job); + var actor = (Actor)(data - _characterDataOffset); + var job = Jobs.TryGetValue((byte)jobIndex, out var j) ? j : Jobs[0]; + var oldJob = Jobs.TryGetValue(old, out var o) ? o : Jobs[0]; + Glamourer.Log.Excessive($"{actor} changed job from {oldJob} to {job}"); + JobChanged?.Invoke(actor, oldJob, job); } } diff --git a/Glamourer/Services/CodeService.cs b/Glamourer/Services/CodeService.cs index 366663e..f9646dc 100644 --- a/Glamourer/Services/CodeService.cs +++ b/Glamourer/Services/CodeService.cs @@ -26,9 +26,13 @@ public class CodeService public Race EnabledOops { get; private set; } public bool EnabledMesmer { get; private set; } public bool EnabledInventory { get; private set; } + public bool EnabledArtisan { get; private set; } public CodeService(Configuration config) - => _config = config; + { + _config = config; + Load(); + } private void Load() { @@ -84,6 +88,7 @@ public class CodeService _ when CodeOops7.SequenceEqual(sha) => v => EnabledOops = v ? Race.Hrothgar : Race.Unknown, _ when CodeOops8.SequenceEqual(sha) => v => EnabledOops = v ? Race.Viera : Race.Unknown, _ when CodeInventory.SequenceEqual(sha) => v => EnabledInventory = v, + _ when CodeArtisan.SequenceEqual(sha) => v => EnabledArtisan = v, _ => null, }; } @@ -104,5 +109,6 @@ public class CodeService private static ReadOnlySpan CodeOops7 => new byte[] { 0x41, 0xEC, 0x65, 0x05, 0x8D, 0x20, 0x68, 0x5A, 0xB7, 0xEB, 0x92, 0x15, 0x43, 0xCF, 0x15, 0x05, 0x27, 0x51, 0xFE, 0x20, 0xC9, 0xB6, 0x2B, 0x84, 0xD9, 0x6A, 0x49, 0x5A, 0x5B, 0x7F, 0x2E, 0xE7 }; private static ReadOnlySpan CodeOops8 => new byte[] { 0x16, 0xFF, 0x63, 0x85, 0x1C, 0xF5, 0x34, 0x33, 0x67, 0x8C, 0x46, 0x8E, 0x3E, 0xE3, 0xA6, 0x94, 0xF9, 0x74, 0x47, 0xAA, 0xC7, 0x29, 0x59, 0x1F, 0x6C, 0x6E, 0xF2, 0xF5, 0x87, 0x24, 0x9E, 0x2B }; private static ReadOnlySpan CodeInventory => new byte[] { 0xD1, 0x35, 0xD7, 0x18, 0xBE, 0x45, 0x42, 0xBD, 0x88, 0x77, 0x7E, 0xC4, 0x41, 0x06, 0x34, 0x4D, 0x71, 0x3A, 0xC5, 0xCC, 0xA4, 0x1B, 0x7D, 0x3F, 0x3B, 0x86, 0x07, 0xCB, 0x63, 0xD7, 0xF9, 0xDB }; + private static ReadOnlySpan CodeArtisan => new byte[] { 0xDE, 0x01, 0x32, 0x1E, 0x7F, 0x22, 0x80, 0x3D, 0x76, 0xDF, 0x74, 0x0E, 0xEC, 0x33, 0xD3, 0xF4, 0x1A, 0x98, 0x9E, 0x9D, 0x22, 0x5C, 0xAC, 0x3B, 0xFE, 0x0B, 0xC2, 0x13, 0xB9, 0x91, 0x24, 0x61 }; // @formatter:on } diff --git a/Glamourer/Services/CustomizationService.cs b/Glamourer/Services/CustomizationService.cs index 0e4f65d..a0f9c17 100644 --- a/Glamourer/Services/CustomizationService.cs +++ b/Glamourer/Services/CustomizationService.cs @@ -19,7 +19,7 @@ public sealed class CustomizationService : AsyncServiceWrapper HumanModels = humanModels; public (Customize NewValue, CustomizeFlag Applied, CustomizeFlag Changed) Combine(Customize oldValues, Customize newValues, - CustomizeFlag applyWhich) + CustomizeFlag applyWhich, bool allowUnknown) { CustomizeFlag applied = 0; CustomizeFlag changed = 0; @@ -46,7 +46,7 @@ public sealed class CustomizationService : AsyncServiceWrapper /// Check that the given model id is valid. - /// The returned model id is 0. + /// The returned model id is 0 if it is not. /// The return value is an empty string if everything was correct and a warning otherwise. /// public string ValidateModelId(uint modelId, out uint actualModelId, out bool isHuman) @@ -217,9 +217,9 @@ public sealed class CustomizationService : AsyncServiceWrapper public static string ValidateCustomizeValue(CustomizationSet set, CustomizeValue face, CustomizeIndex index, CustomizeValue value, - out CustomizeValue actualValue) + out CustomizeValue actualValue, bool allowUnknown) { - if (IsCustomizationValid(set, face, index, value)) + if (allowUnknown || IsCustomizationValid(set, face, index, value)) { actualValue = value; return string.Empty; @@ -279,7 +279,7 @@ public sealed class CustomizationService : AsyncServiceWrapper().Where(set.IsAvailable)) { - if (ValidateCustomizeValue(set, customize.Face, idx, customize[idx], out var fixedValue).Length > 0) + if (ValidateCustomizeValue(set, customize.Face, idx, customize[idx], out var fixedValue, false).Length > 0) { customize[idx] = fixedValue; flags |= idx.ToFlag(); diff --git a/Glamourer/Services/ItemManager.cs b/Glamourer/Services/ItemManager.cs index 7016dab..91b8fd3 100644 --- a/Glamourer/Services/ItemManager.cs +++ b/Glamourer/Services/ItemManager.cs @@ -7,6 +7,7 @@ using Lumina.Excel; using Penumbra.GameData.Data; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; +using static OtterGui.Raii.ImRaii; using Race = Penumbra.GameData.Enums.Race; namespace Glamourer.Services; @@ -111,7 +112,7 @@ public class ItemManager : IDisposable var item = IdentifierService.AwaitedService.Identify(id, variant, slot).FirstOrDefault(); return item.Valid ? item - : new EquipItem($"Unknown ({id.Value}-{variant})", 0, 0, id, 0, variant, 0); + : new EquipItem($"Unknown ({id.Value}-{variant})", 0, 0, id, 0, variant, slot.ToEquipType()); } } @@ -131,7 +132,7 @@ public class ItemManager : IDisposable var item = IdentifierService.AwaitedService.Identify(id, type, variant, slot).FirstOrDefault(); return item.Valid ? item - : new EquipItem($"Unknown ({id.Value}-{type.Value}-{variant})", 0, 0, id, type, variant, 0); + : EquipItem.FromIds(0, 0, id, type, variant, slot.ToEquipType(), null); } /// Returns whether an item id represents a valid item for a slot and gives the item. @@ -147,12 +148,20 @@ public class ItemManager : IDisposable /// The returned item is either the resolved correct item, or the Nothing item for that slot. /// The return value is an empty string if there was no problem and a warning otherwise. /// - public string ValidateItem(EquipSlot slot, uint itemId, out EquipItem item) + public string ValidateItem(EquipSlot slot, ulong itemId, out EquipItem item, bool allowUnknown) { if (slot is EquipSlot.MainHand or EquipSlot.OffHand) throw new Exception("Internal Error: Used armor functionality for weapons."); - if (IsItemValid(slot, itemId, out item)) + if (itemId > uint.MaxValue) + { + var id = (SetId)(itemId & ushort.MaxValue); + var variant = (byte)(itemId >> 32); + item = new EquipItem($"Unknown ({id}-{variant})", 0, 0, id, 0, variant, slot.ToEquipType()); + return allowUnknown ? string.Empty : $"The item {itemId} yields an unknown item."; + } + + if (IsItemValid(slot, (uint) itemId, out item)) return string.Empty; item = NothingItem(slot); @@ -169,9 +178,9 @@ public class ItemManager : IDisposable /// The returned stain id is either the input or 0. /// The return value is an empty string if there was no problem and a warning otherwise. /// - public string ValidateStain(StainId stain, out StainId ret) + public string ValidateStain(StainId stain, out StainId ret, bool allowUnknown) { - if (IsStainValid(stain)) + if (allowUnknown || IsStainValid(stain)) { ret = stain; return string.Empty; diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index 789e027..9b7c0e7 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -23,10 +23,16 @@ public class StateEditor } /// Change the model id. If the actor is changed from a human to another human, customize and equipData are unused. + /// We currently only allow changing things to humans, not humans to monsters. public bool ChangeModelId(ActorState state, uint modelId, in Customize customize, nint equipData, StateChanged.Source source, out uint oldModelId, uint key = 0) { oldModelId = state.ModelData.ModelId; + + // TODO think about this. + if (modelId != 0) + return false; + if (!state.CanUnlock(key)) return false; @@ -94,7 +100,7 @@ public class StateEditor if (!state.CanUnlock(key)) return false; - (var customize, var applied, changed) = _customizations.Combine(state.ModelData.Customize, customizeInput, applyWhich); + (var customize, var applied, changed) = _customizations.Combine(state.ModelData.Customize, customizeInput, applyWhich, true); if (changed == 0) return false; @@ -130,6 +136,11 @@ public class StateEditor if (!state.CanUnlock(key)) return false; + // Can not change weapon type from expected type in state. + if (slot is EquipSlot.MainHand && item.Type != state.BaseData.MainhandType + || slot is EquipSlot.OffHand && item.Type != state.BaseData.MainhandType.Offhand()) + return false; + state.ModelData.SetItem(slot, item); state.ModelData.SetStain(slot, stain); state[slot, false] = source; diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index 37becae..d354062 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -245,7 +245,7 @@ public class StateManager : IReadOnlyDictionary ? _applier.ChangeArmor(state, slot, source is StateChanged.Source.Manual or StateChanged.Source.Ipc) : _applier.ChangeWeapon(state, slot, source is StateChanged.Source.Manual or StateChanged.Source.Ipc); Glamourer.Log.Verbose( - $"Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.Id}) to {item.Name} ({item.Id}). [Affecting {actors.ToLazyString("nothing")}.]"); + $"Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.ItemId}) to {item.Name} ({item.ItemId}). [Affecting {actors.ToLazyString("nothing")}.]"); _event.Invoke(type, source, state, actors, (old, item, slot)); } @@ -260,7 +260,7 @@ public class StateManager : IReadOnlyDictionary ? _applier.ChangeArmor(state, slot, source is StateChanged.Source.Manual or StateChanged.Source.Ipc) : _applier.ChangeWeapon(state, slot, source is StateChanged.Source.Manual or StateChanged.Source.Ipc); Glamourer.Log.Verbose( - $"Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.Id}) to {item.Name} ({item.Id}) and its stain from {oldStain.Value} to {stain.Value}. [Affecting {actors.ToLazyString("nothing")}.]"); + $"Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.ItemId}) to {item.Name} ({item.ItemId}) and its stain from {oldStain.Value} to {stain.Value}. [Affecting {actors.ToLazyString("nothing")}.]"); _event.Invoke(type, source, state, actors, (old, item, slot)); _event.Invoke(StateChanged.Type.Stain, source, state, actors, (oldStain, stain, slot)); } diff --git a/Glamourer/Unlocks/ItemUnlockManager.cs b/Glamourer/Unlocks/ItemUnlockManager.cs index fed2cdf..2321990 100644 --- a/Glamourer/Unlocks/ItemUnlockManager.cs +++ b/Glamourer/Unlocks/ItemUnlockManager.cs @@ -125,10 +125,10 @@ public class ItemUnlockManager : ISavable, IDisposable bool AddItem(uint itemId) { - if (!_items.ItemService.AwaitedService.TryGetValue(itemId, out var equip) || !_unlocked.TryAdd(equip.Id, time)) + if (!_items.ItemService.AwaitedService.TryGetValue(itemId, out var equip) || !_unlocked.TryAdd(equip.ItemId, time)) return false; - _event.Invoke(ObjectUnlocked.Type.Item, equip.Id, DateTimeOffset.FromUnixTimeMilliseconds(time)); + _event.Invoke(ObjectUnlocked.Type.Item, equip.ItemId, DateTimeOffset.FromUnixTimeMilliseconds(time)); return true; } @@ -274,7 +274,7 @@ public class ItemUnlockManager : ISavable, IDisposable foreach (var row in cabinet) { if (items.ItemService.AwaitedService.TryGetValue(row.Item.Row, out var item)) - ret.TryAdd(item.Id, new UnlockRequirements(row.RowId, 0, 0, 0, UnlockType.Cabinet)); + ret.TryAdd(item.ItemId, new UnlockRequirements(row.RowId, 0, 0, 0, UnlockType.Cabinet)); } var gilShop = gameData.GetExcelSheet()!; @@ -290,7 +290,7 @@ public class ItemUnlockManager : ISavable, IDisposable var type = (quest1 != 0 ? UnlockType.Quest1 : 0) | (quest2 != 0 ? UnlockType.Quest2 : 0) | (achievement != 0 ? UnlockType.Achievement : 0); - ret.TryAdd(item.Id, new UnlockRequirements(quest1, quest2, achievement, state, type)); + ret.TryAdd(item.ItemId, new UnlockRequirements(quest1, quest2, achievement, state, type)); } return ret;