This commit is contained in:
Ottermandias 2023-07-10 16:33:42 +02:00
parent ee98d6b600
commit 20cc67275a
25 changed files with 324 additions and 173 deletions

View file

@ -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)) if (!_config.EnableAutoDesigns || !actor.Identifier(_actors.AwaitedService, out var id))
return; return;
@ -102,16 +102,18 @@ public class AutoDesignApplier : IDisposable
if (!GetPlayerSet(id, out var set)) if (!GetPlayerSet(id, out var set))
{ {
if (_state.TryGetValue(id, out var s)) if (_state.TryGetValue(id, out var s))
s.LastJob = actor.Job; s.LastJob = (byte) newJob.Id;
return; return;
} }
if (!_state.GetOrCreate(id, actor, out var state)) if (!_state.GetOrCreate(id, actor, out var state))
return; return;
var sameJob = state.LastJob == actor.Job; if (oldJob.Id == newJob.Id && state.LastJob == newJob.Id)
return;
state.LastJob = actor.Job; state.LastJob = actor.Job;
Reduce(actor, state, set, sameJob); Reduce(actor, state, set, state.LastJob == newJob.Id);
_state.ReapplyState(actor); _state.ReapplyState(actor);
} }
@ -185,7 +187,7 @@ public class AutoDesignApplier : IDisposable
if (equipFlags.HasFlag(flag)) if (equipFlags.HasFlag(flag))
{ {
var item = design.Item(slot); 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) if (!respectManual || state[slot, false] is not StateChanged.Source.Manual)
_state.ChangeItem(state, slot, item, StateChanged.Source.Fixed); _state.ChangeItem(state, slot, item, StateChanged.Source.Fixed);
@ -206,7 +208,7 @@ public class AutoDesignApplier : IDisposable
{ {
var item = design.Item(EquipSlot.MainHand); var item = design.Item(EquipSlot.MainHand);
if (state.ModelData.Item(EquipSlot.MainHand).Type == item.Type 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) if (!respectManual || state[EquipSlot.MainHand, false] is not StateChanged.Source.Manual)
_state.ChangeItem(state, EquipSlot.MainHand, item, StateChanged.Source.Fixed); _state.ChangeItem(state, EquipSlot.MainHand, item, StateChanged.Source.Fixed);
@ -218,7 +220,7 @@ public class AutoDesignApplier : IDisposable
{ {
var item = design.Item(EquipSlot.OffHand); var item = design.Item(EquipSlot.OffHand);
if (state.ModelData.Item(EquipSlot.OffHand).Type == item.Type 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) if (!respectManual || state[EquipSlot.OffHand, false] is not StateChanged.Source.Manual)
_state.ChangeItem(state, EquipSlot.OffHand, item, StateChanged.Source.Fixed); _state.ChangeItem(state, EquipSlot.OffHand, item, StateChanged.Source.Fixed);

View file

@ -130,8 +130,8 @@ public sealed class Design : DesignBase, ISavable
if (design.LastEdit < creationDate) if (design.LastEdit < creationDate)
design.LastEdit = creationDate; design.LastEdit = creationDate;
LoadCustomize(customizations, json["Customize"], design, design.Name); LoadCustomize(customizations, json["Customize"], design, design.Name, true, false);
LoadEquip(items, json["Equipment"], design, design.Name); LoadEquip(items, json["Equipment"], design, design.Name, false);
LoadMods(json["Mods"], design); LoadMods(json["Mods"], design);
return design; return design;
} }

View file

@ -167,27 +167,33 @@ public class DesignBase
protected JObject SerializeEquipment() 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() => new()
{ {
["ItemId"] = itemId, ["ItemId"] = id,
["Stain"] = stain.Value, ["Stain"] = stain.Value,
["Apply"] = apply, ["Apply"] = apply,
["ApplyStain"] = applyStain, ["ApplyStain"] = applyStain,
}; };
var ret = new JObject(); var ret = new JObject();
foreach (var slot in EquipSlotExtensions.EqdpSlots.Prepend(EquipSlot.OffHand).Prepend(EquipSlot.MainHand)) if (DesignData.IsHuman)
{ {
var item = DesignData.Item(slot); foreach (var slot in EquipSlotExtensions.EqdpSlots.Prepend(EquipSlot.OffHand).Prepend(EquipSlot.MainHand))
var stain = DesignData.Stain(slot); {
ret[slot.ToString()] = Serialize(item.Id, stain, DoApplyEquip(slot), DoApplyStain(slot)); 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["Hat"] = new QuadBool(DesignData.IsHatVisible(), DoApplyHatVisible()).ToJObject("Show", "Apply");
ret["Visor"] = new QuadBool(DesignData.IsVisorToggled(), DoApplyVisorToggle()).ToJObject("IsToggled", "Apply"); ret["Visor"] = new QuadBool(DesignData.IsVisorToggled(), DoApplyVisorToggle()).ToJObject("IsToggled", "Apply");
ret["Weapon"] = new QuadBool(DesignData.IsWeaponVisible(), DoApplyWeaponVisible()).ToJObject("Show", "Apply"); ret["Weapon"] = new QuadBool(DesignData.IsWeaponVisible(), DoApplyWeaponVisible()).ToJObject("Show", "Apply");
ret["Array"] = DesignData.WriteEquipmentBytesBase64(); }
else
{
ret["Array"] = DesignData.WriteEquipmentBytesBase64();
}
return ret; return ret;
} }
@ -198,22 +204,25 @@ public class DesignBase
{ {
["ModelId"] = DesignData.ModelId, ["ModelId"] = DesignData.ModelId,
}; };
var customize = DesignData.Customize; var customize = DesignData.Customize;
foreach (var idx in Enum.GetValues<CustomizeIndex>()) if (DesignData.IsHuman)
{ foreach (var idx in Enum.GetValues<CustomizeIndex>())
ret[idx.ToString()] = new JObject()
{ {
["Value"] = customize[idx].Value, ret[idx.ToString()] = new JObject()
["Apply"] = DoApplyCustomize(idx), {
}; ["Value"] = customize[idx].Value,
} ["Apply"] = DoApplyCustomize(idx),
};
}
else
ret["Array"] = customize.WriteBase64();
ret["Wetness"] = new JObject() ret["Wetness"] = new JObject()
{ {
["Value"] = DesignData.IsWet(), ["Value"] = DesignData.IsWet(),
["Apply"] = DoApplyWetness(), ["Apply"] = DoApplyWetness(),
}; };
ret["Array"] = DesignData.Customize.WriteBase64();
return ret; return ret;
} }
@ -235,12 +244,12 @@ public class DesignBase
private static DesignBase LoadDesignV1Base(CustomizationService customizations, ItemManager items, JObject json) private static DesignBase LoadDesignV1Base(CustomizationService customizations, ItemManager items, JObject json)
{ {
var ret = new DesignBase(items); var ret = new DesignBase(items);
LoadCustomize(customizations, json["Customize"], ret, "Temporary Design"); LoadCustomize(customizations, json["Customize"], ret, "Temporary Design", false, true);
LoadEquip(items, json["Equipment"], ret, "Temporary Design"); LoadEquip(items, json["Equipment"], ret, "Temporary Design", true);
return ret; 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) if (equip == null)
{ {
@ -257,9 +266,9 @@ public class DesignBase
return; 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<uint>() ?? ItemManager.NothingId(slot); var id = item?["ItemId"]?.ToObject<ulong>() ?? ItemManager.NothingId(slot);
var stain = (StainId)(item?["Stain"]?.ToObject<byte>() ?? 0); var stain = (StainId)(item?["Stain"]?.ToObject<byte>() ?? 0);
var apply = item?["Apply"]?.ToObject<bool>() ?? false; var apply = item?["Apply"]?.ToObject<bool>() ?? false;
var applyStain = item?["ApplyStain"]?.ToObject<bool>() ?? false; var applyStain = item?["ApplyStain"]?.ToObject<bool>() ?? false;
@ -276,8 +285,8 @@ public class DesignBase
{ {
var (id, stain, apply, applyStain) = ParseItem(slot, equip[slot.ToString()]); var (id, stain, apply, applyStain) = ParseItem(slot, equip[slot.ToString()]);
PrintWarning(items.ValidateItem(slot, id, out var item)); PrintWarning(items.ValidateItem(slot, id, out var item, allowUnknown));
PrintWarning(items.ValidateStain(stain, out stain)); PrintWarning(items.ValidateStain(stain, out stain, allowUnknown));
design.DesignData.SetItem(slot, item); design.DesignData.SetItem(slot, item);
design.DesignData.SetStain(slot, stain); design.DesignData.SetStain(slot, stain);
design.SetApplyEquip(slot, apply); design.SetApplyEquip(slot, apply);
@ -287,14 +296,14 @@ public class DesignBase
{ {
var (id, stain, apply, applyStain) = ParseItem(EquipSlot.MainHand, equip[EquipSlot.MainHand.ToString()]); var (id, stain, apply, applyStain) = ParseItem(EquipSlot.MainHand, equip[EquipSlot.MainHand.ToString()]);
if (id == ItemManager.NothingId(EquipSlot.MainHand)) 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()]); var (idOff, stainOff, applyOff, applyStainOff) = ParseItem(EquipSlot.OffHand, equip[EquipSlot.OffHand.ToString()]);
if (id == ItemManager.NothingId(EquipSlot.OffHand)) if (id == ItemManager.NothingId(EquipSlot.OffHand))
id = ItemManager.NothingId(FullEquipType.Shield); id = ItemManager.NothingId(FullEquipType.Shield);
PrintWarning(items.ValidateWeapons(id, idOff, out var main, out var off)); PrintWarning(items.ValidateWeapons((uint)id, (uint)idOff, out var main, out var off));
PrintWarning(items.ValidateStain(stain, out stain)); PrintWarning(items.ValidateStain(stain, out stain, allowUnknown));
PrintWarning(items.ValidateStain(stainOff, out stainOff)); PrintWarning(items.ValidateStain(stainOff, out stainOff, allowUnknown));
design.DesignData.SetItem(EquipSlot.MainHand, main); design.DesignData.SetItem(EquipSlot.MainHand, main);
design.DesignData.SetItem(EquipSlot.OffHand, off); design.DesignData.SetItem(EquipSlot.OffHand, off);
design.DesignData.SetStain(EquipSlot.MainHand, stain); design.DesignData.SetStain(EquipSlot.MainHand, stain);
@ -317,7 +326,8 @@ public class DesignBase
design.DesignData.SetVisor(metaValue.ForcedValue); 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) if (json == null)
{ {
@ -341,7 +351,13 @@ public class DesignBase
design.DesignData.ModelId = json["ModelId"]?.ToObject<uint>() ?? 0; design.DesignData.ModelId = json["ModelId"]?.ToObject<uint>() ?? 0;
PrintWarning(customizations.ValidateModelId(design.DesignData.ModelId, out design.DesignData.ModelId, out design.DesignData.IsHuman)); 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>() ?? string.Empty; var arrayText = json["Array"]?.ToObject<string>() ?? string.Empty;
design.DesignData.Customize.LoadBase64(arrayText); design.DesignData.Customize.LoadBase64(arrayText);
@ -366,7 +382,7 @@ public class DesignBase
{ {
var tok = json[idx.ToString()]; var tok = json[idx.ToString()];
var data = (CustomizeValue)(tok?["Value"]?.ToObject<byte>() ?? 0); var data = (CustomizeValue)(tok?["Value"]?.ToObject<byte>() ?? 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<bool>() ?? false; var apply = tok?["Apply"]?.ToObject<bool>() ?? false;
design.DesignData.Customize[idx] = data; design.DesignData.Customize[idx] = data;
design.SetApplyCustomize(idx, apply); design.SetApplyCustomize(idx, apply);

View file

@ -45,22 +45,28 @@ public unsafe struct DesignData
return index > 11 ? (StainId)0 : _equipmentBytes[4 * index + 3]; return index > 11 ? (StainId)0 : _equipmentBytes[4 * index + 3];
} }
public FullEquipType MainhandType
=> _typeMainhand;
public FullEquipType OffhandType
=> _typeOffhand;
public readonly EquipItem Item(EquipSlot slot) public readonly EquipItem Item(EquipSlot slot)
=> slot.ToIndex() switch => slot.ToIndex() switch
{ {
// @formatter:off // @formatter:off
0 => new EquipItem(_nameHead, _itemIds[ 0], _iconIds[ 0], (SetId)(_equipmentBytes[ 0] | (_equipmentBytes[ 1] << 8)), (WeaponType)0, _equipmentBytes[ 2], FullEquipType.Head ), 0 => EquipItem.FromIds(_itemIds[ 0], _iconIds[ 0], (SetId)(_equipmentBytes[ 0] | (_equipmentBytes[ 1] << 8)), (WeaponType)0, _equipmentBytes[ 2], FullEquipType.Head, _nameHead ),
1 => new EquipItem(_nameBody, _itemIds[ 1], _iconIds[ 1], (SetId)(_equipmentBytes[ 4] | (_equipmentBytes[ 5] << 8)), (WeaponType)0, _equipmentBytes[ 6], FullEquipType.Body ), 1 => EquipItem.FromIds(_itemIds[ 1], _iconIds[ 1], (SetId)(_equipmentBytes[ 4] | (_equipmentBytes[ 5] << 8)), (WeaponType)0, _equipmentBytes[ 6], FullEquipType.Body, _nameBody ),
2 => new EquipItem(_nameHands, _itemIds[ 2], _iconIds[ 2], (SetId)(_equipmentBytes[ 8] | (_equipmentBytes[ 9] << 8)), (WeaponType)0, _equipmentBytes[10], FullEquipType.Hands ), 2 => EquipItem.FromIds(_itemIds[ 2], _iconIds[ 2], (SetId)(_equipmentBytes[ 8] | (_equipmentBytes[ 9] << 8)), (WeaponType)0, _equipmentBytes[10], FullEquipType.Hands, _nameHands ),
3 => new EquipItem(_nameLegs, _itemIds[ 3], _iconIds[ 3], (SetId)(_equipmentBytes[12] | (_equipmentBytes[13] << 8)), (WeaponType)0, _equipmentBytes[14], FullEquipType.Legs ), 3 => EquipItem.FromIds(_itemIds[ 3], _iconIds[ 3], (SetId)(_equipmentBytes[12] | (_equipmentBytes[13] << 8)), (WeaponType)0, _equipmentBytes[14], FullEquipType.Legs, _nameLegs ),
4 => new EquipItem(_nameFeet, _itemIds[ 4], _iconIds[ 4], (SetId)(_equipmentBytes[16] | (_equipmentBytes[17] << 8)), (WeaponType)0, _equipmentBytes[18], FullEquipType.Feet ), 4 => EquipItem.FromIds(_itemIds[ 4], _iconIds[ 4], (SetId)(_equipmentBytes[16] | (_equipmentBytes[17] << 8)), (WeaponType)0, _equipmentBytes[18], FullEquipType.Feet, _nameFeet ),
5 => new EquipItem(_nameEars, _itemIds[ 5], _iconIds[ 5], (SetId)(_equipmentBytes[20] | (_equipmentBytes[21] << 8)), (WeaponType)0, _equipmentBytes[22], FullEquipType.Ears ), 5 => EquipItem.FromIds(_itemIds[ 5], _iconIds[ 5], (SetId)(_equipmentBytes[20] | (_equipmentBytes[21] << 8)), (WeaponType)0, _equipmentBytes[22], FullEquipType.Ears, _nameEars ),
6 => new EquipItem(_nameNeck, _itemIds[ 6], _iconIds[ 6], (SetId)(_equipmentBytes[24] | (_equipmentBytes[25] << 8)), (WeaponType)0, _equipmentBytes[26], FullEquipType.Neck ), 6 => EquipItem.FromIds(_itemIds[ 6], _iconIds[ 6], (SetId)(_equipmentBytes[24] | (_equipmentBytes[25] << 8)), (WeaponType)0, _equipmentBytes[26], FullEquipType.Neck, _nameNeck ),
7 => new EquipItem(_nameWrists, _itemIds[ 7], _iconIds[ 7], (SetId)(_equipmentBytes[28] | (_equipmentBytes[29] << 8)), (WeaponType)0, _equipmentBytes[30], FullEquipType.Wrists ), 7 => EquipItem.FromIds(_itemIds[ 7], _iconIds[ 7], (SetId)(_equipmentBytes[28] | (_equipmentBytes[29] << 8)), (WeaponType)0, _equipmentBytes[30], FullEquipType.Wrists, _nameWrists ),
8 => new EquipItem(_nameRFinger, _itemIds[ 8], _iconIds[ 8], (SetId)(_equipmentBytes[32] | (_equipmentBytes[33] << 8)), (WeaponType)0, _equipmentBytes[34], FullEquipType.Finger ), 8 => EquipItem.FromIds(_itemIds[ 8], _iconIds[ 8], (SetId)(_equipmentBytes[32] | (_equipmentBytes[33] << 8)), (WeaponType)0, _equipmentBytes[34], FullEquipType.Finger, _nameRFinger ),
9 => new EquipItem(_nameLFinger, _itemIds[ 9], _iconIds[ 9], (SetId)(_equipmentBytes[36] | (_equipmentBytes[37] << 8)), (WeaponType)0, _equipmentBytes[38], FullEquipType.Finger ), 9 => EquipItem.FromIds(_itemIds[ 9], _iconIds[ 9], (SetId)(_equipmentBytes[36] | (_equipmentBytes[37] << 8)), (WeaponType)0, _equipmentBytes[38], FullEquipType.Finger, _nameLFinger ),
10 => new EquipItem(_nameMainhand, _itemIds[10], _iconIds[10], (SetId)(_equipmentBytes[40] | (_equipmentBytes[41] << 8)), _secondaryMainhand, _equipmentBytes[42], _typeMainhand ), 10 => EquipItem.FromIds(_itemIds[10], _iconIds[10], (SetId)(_equipmentBytes[40] | (_equipmentBytes[41] << 8)), _secondaryMainhand, _equipmentBytes[42], _typeMainhand, _nameMainhand),
11 => new EquipItem(_nameOffhand, _itemIds[11], _iconIds[11], (SetId)(_equipmentBytes[44] | (_equipmentBytes[45] << 8)), _secondaryOffhand, _equipmentBytes[46], _typeOffhand ), 11 => EquipItem.FromIds(_itemIds[11], _iconIds[11], (SetId)(_equipmentBytes[44] | (_equipmentBytes[45] << 8)), _secondaryOffhand, _equipmentBytes[46], _typeOffhand, _nameOffhand ),
_ => new EquipItem(), _ => new EquipItem(),
// @formatter:on // @formatter:on
}; };
@ -86,10 +92,10 @@ public unsafe struct DesignData
public bool SetItem(EquipSlot slot, EquipItem item) public bool SetItem(EquipSlot slot, EquipItem item)
{ {
var index = slot.ToIndex(); var index = slot.ToIndex();
if (index > 11 || _itemIds[index] == item.Id) if (index > 11)
return false; return false;
_itemIds[index] = item.Id; _itemIds[index] = item.ItemId;
_iconIds[index] = item.IconId; _iconIds[index] = item.IconId;
_equipmentBytes[4 * index + 0] = (byte)item.ModelId; _equipmentBytes[4 * index + 0] = (byte)item.ModelId;
_equipmentBytes[4 * index + 1] = (byte)(item.ModelId.Value >> 8); _equipmentBytes[4 * index + 1] = (byte)(item.ModelId.Value >> 8);
@ -212,7 +218,7 @@ public unsafe struct DesignData
Customize.Load(customize); Customize.Load(customize);
fixed (byte* ptr = _equipmentBytes) fixed (byte* ptr = _equipmentBytes)
{ {
MemoryUtility.MemCpyUnchecked(ptr, (byte*) equipData, 40); MemoryUtility.MemCpyUnchecked(ptr, (byte*)equipData, 40);
} }
SetHatVisible(true); SetHatVisible(true);
@ -228,16 +234,16 @@ public unsafe struct DesignData
MemoryUtility.MemSet(ptr, 0, 10 * 2); MemoryUtility.MemSet(ptr, 0, 10 * 2);
} }
_nameHead = string.Empty; _nameHead = string.Empty;
_nameBody = string.Empty; _nameBody = string.Empty;
_nameHands = string.Empty; _nameHands = string.Empty;
_nameLegs = string.Empty; _nameLegs = string.Empty;
_nameFeet = string.Empty; _nameFeet = string.Empty;
_nameEars = string.Empty; _nameEars = string.Empty;
_nameNeck = string.Empty; _nameNeck = string.Empty;
_nameWrists = string.Empty; _nameWrists = string.Empty;
_nameRFinger = string.Empty; _nameRFinger = string.Empty;
_nameLFinger = string.Empty; _nameLFinger = string.Empty;
return true; return true;
} }

View file

@ -13,7 +13,6 @@ using Newtonsoft.Json.Linq;
using OtterGui; using OtterGui;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs; using Penumbra.GameData.Structs;
using static OtterGui.Raii.ImRaii;
namespace Glamourer.Designs; namespace Glamourer.Designs;
@ -260,28 +259,6 @@ public class DesignManager
_event.Invoke(DesignChanged.Type.WriteProtection, design, value); _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));
}
/// <summary> Change a customization value. </summary> /// <summary> Change a customization value. </summary>
public void ChangeCustomize(Design design, CustomizeIndex idx, CustomizeValue value) public void ChangeCustomize(Design design, CustomizeIndex idx, CustomizeValue value)
{ {
@ -332,7 +309,7 @@ public class DesignManager
/// <summary> Change a non-weapon equipment piece. </summary> /// <summary> Change a non-weapon equipment piece. </summary>
public void ChangeEquip(Design design, EquipSlot slot, EquipItem item) 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; return;
var old = design.DesignData.Item(slot); var old = design.DesignData.Item(slot);
@ -341,7 +318,7 @@ public class DesignManager
design.LastEdit = DateTimeOffset.UtcNow; design.LastEdit = DateTimeOffset.UtcNow;
Glamourer.Log.Debug( 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); _saveService.QueueSave(design);
_event.Invoke(DesignChanged.Type.Equip, design, (old, item, slot)); _event.Invoke(DesignChanged.Type.Equip, design, (old, item, slot));
} }
@ -355,13 +332,13 @@ public class DesignManager
{ {
case EquipSlot.MainHand: case EquipSlot.MainHand:
var newOff = currentOff; var newOff = currentOff;
if (!_items.IsItemValid(EquipSlot.MainHand, item.Id, out item)) if (!_items.IsItemValid(EquipSlot.MainHand, item.ItemId, out item))
return; return;
if (item.Type != currentMain.Type) if (item.Type != currentMain.Type)
{ {
var newOffId = item.Type.Offhand().IsOffhandType() var newOffId = item.Type.Offhand().IsOffhandType()
? item.Id ? item.ItemId
: ItemManager.NothingId(item.Type.Offhand()); : ItemManager.NothingId(item.Type.Offhand());
if (!_items.IsOffhandValid(item, newOffId, out newOff)) if (!_items.IsOffhandValid(item, newOffId, out newOff))
return; return;
@ -373,12 +350,12 @@ public class DesignManager
design.LastEdit = DateTimeOffset.UtcNow; design.LastEdit = DateTimeOffset.UtcNow;
_saveService.QueueSave(design); _saveService.QueueSave(design);
Glamourer.Log.Debug( 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)); _event.Invoke(DesignChanged.Type.Weapon, design, (currentMain, currentOff, item, newOff));
return; return;
case EquipSlot.OffHand: case EquipSlot.OffHand:
if (!_items.IsOffhandValid(currentOff.Type, item.Id, out item)) if (!_items.IsOffhandValid(currentOff.Type, item.ItemId, out item))
return; return;
if (!design.DesignData.SetItem(EquipSlot.OffHand, item)) if (!design.DesignData.SetItem(EquipSlot.OffHand, item))
@ -387,7 +364,7 @@ public class DesignManager
design.LastEdit = DateTimeOffset.UtcNow; design.LastEdit = DateTimeOffset.UtcNow;
_saveService.QueueSave(design); _saveService.QueueSave(design);
Glamourer.Log.Debug( 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)); _event.Invoke(DesignChanged.Type.Weapon, design, (currentMain, currentOff, currentMain, item));
return; return;
default: return; default: return;
@ -409,7 +386,7 @@ public class DesignManager
/// <summary> Change the stain for any equipment piece. </summary> /// <summary> Change the stain for any equipment piece. </summary>
public void ChangeStain(Design design, EquipSlot slot, StainId stain) 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; return;
var oldStain = design.DesignData.Stain(slot); var oldStain = design.DesignData.Stain(slot);
@ -477,9 +454,6 @@ public class DesignManager
/// <summary> Apply an entire design based on its appliance rules piece by piece. </summary> /// <summary> Apply an entire design based on its appliance rules piece by piece. </summary>
public void ApplyDesign(Design design, DesignBase other) public void ApplyDesign(Design design, DesignBase other)
{ {
ChangeModelId(design, other.DesignData.ModelId, other.DesignData.Customize, other.DesignData.GetEquipmentPtr(),
other.DesignData.IsHuman);
if (other.DoApplyWetness()) if (other.DoApplyWetness())
design.DesignData.SetIsWet(other.DesignData.IsWet()); design.DesignData.SetIsWet(other.DesignData.IsWet());
if (other.DoApplyHatVisible()) if (other.DoApplyHatVisible())

View file

@ -46,9 +46,6 @@ public sealed class DesignChanged : EventWrapper<Action<DesignChanged.Type, Desi
/// <summary> An existing design had an existing associated mod removed. Data is the Mod and its Settings [(Mod, ModSettings)]. </summary> /// <summary> An existing design had an existing associated mod removed. Data is the Mod and its Settings [(Mod, ModSettings)]. </summary>
RemovedMod, RemovedMod,
/// <summary> 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)]. </summary>
ModelId,
/// <summary> An existing design had a customization changed. Data is the old value, the new value and the type [(CustomizeValue, CustomizeValue, CustomizeIndex)]. </summary> /// <summary> An existing design had a customization changed. Data is the old value, the new value and the type [(CustomizeValue, CustomizeValue, CustomizeIndex)]. </summary>
Customize, Customize,

View file

@ -1,5 +1,6 @@
using System; using System;
using System.Numerics; using System.Numerics;
using Dalamud.Interface;
using Glamourer.Customization; using Glamourer.Customization;
using ImGuiNET; using ImGuiNET;
using OtterGui.Raii; using OtterGui.Raii;
@ -14,19 +15,33 @@ public partial class CustomizationDrawer
{ {
using var _ = SetId(index); using var _ = SetId(index);
var (current, custom) = GetCurrentCustomization(index); var (current, custom) = GetCurrentCustomization(index);
var color = ImGui.ColorConvertU32ToFloat4(custom.Color);
// Print 1-based index instead of 0. var color = ImGui.ColorConvertU32ToFloat4(current < 0 ? ImGui.GetColorU32(ImGuiCol.FrameBg) : custom.Color);
if (ImGui.ColorButton($"{current + 1}##color", color, ImGuiColorEditFlags.None, _framedIconSize))
ImGui.OpenPopup(ColorPickerPopupName); 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(); ImGui.SameLine();
using (var group = ImRaii.Group()) using (var group = ImRaii.Group())
{ {
DataInputInt(current); DataInputInt(current);
ImGui.TextUnformatted(_currentOption); ImGui.TextUnformatted(custom.Color == 0 ? $"{_currentOption} (Custom #{custom.Value})" : _currentOption);
} }
DrawColorPickerPopup(); DrawColorPickerPopup();
} }
@ -57,8 +72,8 @@ public partial class CustomizationDrawer
{ {
var current = _set.DataByValue(index, _customize[index], out var custom, _customize.Face); var current = _set.DataByValue(index, _customize[index], out var custom, _customize.Face);
if (_set.IsAvailable(index) && current < 0) 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); return (current, custom!.Value);
} }
} }

View file

@ -1,17 +1,21 @@
using System; using System;
using System.Numerics; using System.Numerics;
using System.Reflection; using System.Reflection;
using Dalamud.Interface;
using Dalamud.Plugin; using Dalamud.Plugin;
using Glamourer.Customization; using Glamourer.Customization;
using Glamourer.Services; using Glamourer.Services;
using ImGuiNET; using ImGuiNET;
using OtterGui; using OtterGui;
using OtterGui.Raii; using OtterGui.Raii;
using CustomizeData = Penumbra.GameData.Structs.CustomizeData;
namespace Glamourer.Gui.Customization; namespace Glamourer.Gui.Customization;
public partial class CustomizationDrawer : IDisposable public partial class CustomizationDrawer : IDisposable
{ {
private readonly CodeService _codes;
private readonly Vector4 _redTint = new(0.6f, 0.3f, 0.3f, 1f); private readonly Vector4 _redTint = new(0.6f, 0.3f, 0.3f, 1f);
private readonly ImGuiScene.TextureWrap? _legacyTattoo; private readonly ImGuiScene.TextureWrap? _legacyTattoo;
@ -38,9 +42,10 @@ public partial class CustomizationDrawer : IDisposable
private readonly CustomizationService _service; private readonly CustomizationService _service;
public CustomizationDrawer(DalamudPluginInterface pi, CustomizationService service) public CustomizationDrawer(DalamudPluginInterface pi, CustomizationService service, CodeService codes)
{ {
_service = service; _service = service;
_codes = codes;
_legacyTattoo = GetLegacyTattooIcon(pi); _legacyTattoo = GetLegacyTattooIcon(pi);
_customize = Customize.Default; _customize = Customize.Default;
} }
@ -100,6 +105,9 @@ public partial class CustomizationDrawer : IDisposable
try try
{ {
if (_codes.EnabledArtisan)
return DrawArtisan();
DrawRaceGenderSelector(); DrawRaceGenderSelector();
_set = _service.AwaitedService.GetList(_customize.Clan, _customize.Gender); _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<CustomizeIndex>())
{
var (j, mask) = flag.ToByteAndMask();
if (j == i)
Changed |= flag.ToFlag();
}
_customize.Data.Data[i] = newValue;
}
return Changed != 0;
}
private void UpdateSizes() private void UpdateSizes()
{ {
_iconSize = new Vector2(ImGui.GetTextLineHeightWithSpacing() * 2); _iconSize = new Vector2(ImGui.GetTextLineHeightWithSpacing() * 2);

View file

@ -8,6 +8,7 @@ using Glamourer.Designs;
using Glamourer.Services; using Glamourer.Services;
using ImGuiNET; using ImGuiNET;
using OtterGui; using OtterGui;
using OtterGui.Raii;
using OtterGui.Widgets; using OtterGui.Widgets;
using Penumbra.GameData.Data; using Penumbra.GameData.Data;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
@ -22,10 +23,13 @@ public class EquipmentDrawer
private readonly StainData _stainData; private readonly StainData _stainData;
private readonly ItemCombo[] _itemCombo; private readonly ItemCombo[] _itemCombo;
private readonly Dictionary<FullEquipType, WeaponCombo> _weaponCombo; private readonly Dictionary<FullEquipType, WeaponCombo> _weaponCombo;
private readonly CodeService _codes;
public EquipmentDrawer(DataManager gameData, ItemManager items)
public EquipmentDrawer(DataManager gameData, ItemManager items, CodeService codes)
{ {
_items = items; _items = items;
_codes = codes;
_stainData = items.Stains; _stainData = items.Stains;
_stainCombo = new FilterComboColors(140, _stainCombo = new FilterComboColors(140,
_stainData.Data.Prepend(new KeyValuePair<byte, (string Name, uint Dye, bool Gloss)>(0, ("None", 0, false)))); _stainData.Data.Prepend(new KeyValuePair<byte, (string Name, uint Dye, bool Gloss)>(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) 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}."); 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()]; var combo = _itemCombo[slot.ToIndex()];
armor = current; 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) if (armor.ModelId.Value != 0)
{ {
ImGuiUtil.HoverTooltip("Right-click to clear."); ImGuiUtil.HoverTooltip("Right-click to clear.");
@ -81,18 +88,82 @@ public class EquipmentDrawer
return change; 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); var change = _stainCombo.Draw($"##stain{slot}", stain.RgbaColor, stain.Name, found);
ImGuiUtil.HoverTooltip("Right-click to clear."); ImGuiUtil.HoverTooltip("Right-click to clear.");
if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
{ {
stain = Stain.None; stain = Stain.None;
ret = stain.RowIndex;
return true; 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) 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)) if (!_weaponCombo.TryGetValue(drawAll ? FullEquipType.Unknown : current.Type, out var combo))
return false; return false;
if (!combo.Draw(weapon.Name, weapon.Id, 320 * ImGuiHelpers.GlobalScale)) if (!combo.Draw(weapon.Name, weapon.ItemId, 320 * ImGuiHelpers.GlobalScale))
return false; return false;
weapon = combo.CurrentSelection; weapon = combo.CurrentSelection;
@ -118,7 +189,7 @@ public class EquipmentDrawer
if (!_weaponCombo.TryGetValue(offType, out var combo)) if (!_weaponCombo.TryGetValue(offType, out var combo))
return false; 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) if (!offType.IsOffhandType() && weapon.ModelId.Value != 0)
{ {
ImGuiUtil.HoverTooltip("Right-click to clear."); ImGuiUtil.HoverTooltip("Right-click to clear.");

View file

@ -34,10 +34,10 @@ public sealed class ItemCombo : FilterComboCache<EquipItem>
protected override int UpdateCurrentSelected(int currentSelected) protected override int UpdateCurrentSelected(int currentSelected)
{ {
if (CurrentSelection.Id == _currentItem) if (CurrentSelection.ItemId == _currentItem)
return currentSelected; return currentSelected;
CurrentSelectionIdx = Items.IndexOf(i => i.Id == _currentItem); CurrentSelectionIdx = Items.IndexOf(i => i.ItemId == _currentItem);
CurrentSelection = CurrentSelectionIdx >= 0 ? Items[CurrentSelectionIdx] : default; CurrentSelection = CurrentSelectionIdx >= 0 ? Items[CurrentSelectionIdx] : default;
return base.UpdateCurrentSelected(CurrentSelectionIdx); return base.UpdateCurrentSelected(CurrentSelectionIdx);

View file

@ -30,10 +30,10 @@ public sealed class WeaponCombo : FilterComboCache<EquipItem>
protected override int UpdateCurrentSelected(int currentSelected) protected override int UpdateCurrentSelected(int currentSelected)
{ {
if (CurrentSelection.Id == _currentItemId) if (CurrentSelection.ItemId == _currentItemId)
return currentSelected; return currentSelected;
CurrentSelectionIdx = Items.IndexOf(i => i.Id == _currentItemId); CurrentSelectionIdx = Items.IndexOf(i => i.ItemId == _currentItemId);
CurrentSelection = CurrentSelectionIdx >= 0 ? Items[CurrentSelectionIdx] : default; CurrentSelection = CurrentSelectionIdx >= 0 ? Items[CurrentSelectionIdx] : default;
return base.UpdateCurrentSelected(CurrentSelectionIdx); return base.UpdateCurrentSelected(CurrentSelectionIdx);
} }

View file

@ -210,7 +210,7 @@ public class PenumbraChangedItemTooltip : IDisposable
else else
{ {
var oldItem = state.ModelData.Item(slot); var oldItem = state.ModelData.Item(slot);
if (oldItem.Id != item.Id) if (oldItem.ItemId != item.ItemId)
_lastItems[slot.ToIndex()] = oldItem; _lastItems[slot.ToIndex()] = oldItem;
} }
} }

View file

@ -131,33 +131,33 @@ public class ActorPanel
{ {
var stain = _state.ModelData.Stain(slot); var stain = _state.ModelData.Stain(slot);
if (_equipmentDrawer.DrawStain(stain, slot, out var newStain)) 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(); ImGui.SameLine();
var armor = _state.ModelData.Item(slot); var armor = _state.ModelData.Item(slot);
if (_equipmentDrawer.DrawArmor(armor, slot, out var newArmor, _state.ModelData.Customize.Gender, _state.ModelData.Customize.Race)) 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); var mhStain = _state.ModelData.Stain(EquipSlot.MainHand);
if (_equipmentDrawer.DrawStain(mhStain, EquipSlot.MainHand, out var newMhStain)) 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(); ImGui.SameLine();
var mh = _state.ModelData.Item(EquipSlot.MainHand); var mh = _state.ModelData.Item(EquipSlot.MainHand);
if (_equipmentDrawer.DrawMainhand(mh, false, out var newMh)) 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) if (newMh.Type.Offhand() is not FullEquipType.Unknown)
{ {
var ohStain = _state.ModelData.Stain(EquipSlot.OffHand); var ohStain = _state.ModelData.Stain(EquipSlot.OffHand);
if (_equipmentDrawer.DrawStain(ohStain, EquipSlot.OffHand, out var newOhStain)) 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(); ImGui.SameLine();
var oh = _state.ModelData.Item(EquipSlot.OffHand); var oh = _state.ModelData.Item(EquipSlot.OffHand);
if (_equipmentDrawer.DrawMainhand(oh, false, out var newOh)) 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);
} }
} }

View file

@ -168,7 +168,7 @@ public class SetPanel
continue; continue;
var item = design.Design.DesignData.Item(slot); 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."); sb.AppendLine($"{item.Name} in {slot.ToName()} slot is not unlocked but should be applied.");
} }

View file

@ -155,6 +155,15 @@ public unsafe class DebugTab : ITab
ImGui.TableNextColumn(); ImGui.TableNextColumn();
ImGuiUtil.CopyOnClickSelectable(model.ToString()); ImGuiUtil.CopyOnClickSelectable(model.ToString());
ImGui.TableNextColumn(); 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("Mainhand");
ImGuiUtil.DrawTableColumn(actor.IsCharacter ? actor.GetMainhand().ToString() : "No Character"); ImGuiUtil.DrawTableColumn(actor.IsCharacter ? actor.GetMainhand().ToString() : "No Character");
@ -712,7 +721,7 @@ public unsafe class DebugTab : ITab
return; return;
disabled.Dispose(); 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(); ImGuiTreeNodeFlags.Leaf).Dispose();
DrawNameTable("All Items (Main)", ref _itemFilter, DrawNameTable("All Items (Main)", ref _itemFilter,
_items.ItemService.AwaitedService.AllItems(true).Select(p => (p.Item1, _items.ItemService.AwaitedService.AllItems(true).Select(p => (p.Item1,
@ -726,7 +735,7 @@ public unsafe class DebugTab : ITab
{ {
DrawNameTable(type.ToName(), ref _itemFilter, DrawNameTable(type.ToName(), ref _itemFilter,
_items.ItemService.AwaitedService[type] _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); var stain = data.Stain(slot);
ImGuiUtil.DrawTableColumn(slot.ToName()); ImGuiUtil.DrawTableColumn(slot.ToName());
ImGuiUtil.DrawTableColumn(item.Name); ImGuiUtil.DrawTableColumn(item.Name);
ImGuiUtil.DrawTableColumn(item.Id.ToString()); ImGuiUtil.DrawTableColumn(item.ItemId.ToString());
ImGuiUtil.DrawTableColumn(stain.ToString()); ImGuiUtil.DrawTableColumn(stain.ToString());
} }
@ -1175,7 +1184,7 @@ public unsafe class DebugTab : ITab
var applyStain = design.DoApplyStain(slot); var applyStain = design.DoApplyStain(slot);
ImGuiUtil.DrawTableColumn(slot.ToName()); ImGuiUtil.DrawTableColumn(slot.ToName());
ImGuiUtil.DrawTableColumn(item.Name); ImGuiUtil.DrawTableColumn(item.Name);
ImGuiUtil.DrawTableColumn(item.Id.ToString()); ImGuiUtil.DrawTableColumn(item.ItemId.ToString());
ImGuiUtil.DrawTableColumn(apply ? "Apply" : "Keep"); ImGuiUtil.DrawTableColumn(apply ? "Apply" : "Keep");
ImGuiUtil.DrawTableColumn(stain.ToString()); ImGuiUtil.DrawTableColumn(stain.ToString());
ImGuiUtil.DrawTableColumn(applyStain ? "Apply" : "Keep"); ImGuiUtil.DrawTableColumn(applyStain ? "Apply" : "Keep");

View file

@ -131,7 +131,7 @@ public class DesignPanel
{ {
var stain = _selector.Selected!.DesignData.Stain(slot); var stain = _selector.Selected!.DesignData.Stain(slot);
if (_equipmentDrawer.DrawStain(stain, slot, out var newStain)) if (_equipmentDrawer.DrawStain(stain, slot, out var newStain))
_manager.ChangeStain(_selector.Selected!, slot, newStain.RowIndex); _manager.ChangeStain(_selector.Selected!, slot, newStain);
ImGui.SameLine(); ImGui.SameLine();
var armor = _selector.Selected!.DesignData.Item(slot); var armor = _selector.Selected!.DesignData.Item(slot);
@ -142,7 +142,7 @@ public class DesignPanel
var mhStain = _selector.Selected!.DesignData.Stain(EquipSlot.MainHand); var mhStain = _selector.Selected!.DesignData.Stain(EquipSlot.MainHand);
if (_equipmentDrawer.DrawStain(mhStain, EquipSlot.MainHand, out var newMhStain)) 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(); ImGui.SameLine();
var mh = _selector.Selected!.DesignData.Item(EquipSlot.MainHand); var mh = _selector.Selected!.DesignData.Item(EquipSlot.MainHand);
@ -153,7 +153,7 @@ public class DesignPanel
{ {
var ohStain = _selector.Selected!.DesignData.Stain(EquipSlot.OffHand); var ohStain = _selector.Selected!.DesignData.Stain(EquipSlot.OffHand);
if (_equipmentDrawer.DrawStain(ohStain, EquipSlot.OffHand, out var newOhStain)) 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(); ImGui.SameLine();
var oh = _selector.Selected!.DesignData.Item(EquipSlot.OffHand); var oh = _selector.Selected!.DesignData.Item(EquipSlot.OffHand);

View file

@ -153,7 +153,7 @@ public class UnlockOverview
void DrawItem(EquipItem item) 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); var iconHandle = _textureCache.LoadIcon(item.IconId);
if (!iconHandle.HasValue) if (!iconHandle.HasValue)
return; return;
@ -179,7 +179,7 @@ public class UnlockOverview
ImGui.TextUnformatted($"{item.Type.ToName()} ({slot.ToName()})"); ImGui.TextUnformatted($"{item.Type.ToName()} ({slot.ToName()})");
if (item.Type.Offhand().IsOffhandType()) if (item.Type.Offhand().IsOffhandType())
ImGui.TextUnformatted( 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 else
ImGui.TextUnformatted(slot is EquipSlot.MainHand ? $"{item.Weapon()}" : $"{item.Armor()}"); ImGui.TextUnformatted(slot is EquipSlot.MainHand ? $"{item.Weapon()}" : $"{item.Armor()}");
ImGui.TextUnformatted( ImGui.TextUnformatted(

View file

@ -193,7 +193,7 @@ public class UnlockTable : Table<EquipItem>, IDisposable
public override void DrawColumn(EquipItem item, int idx) 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; return;
ImGui.AlignTextToFramePadding(); ImGui.AlignTextToFramePadding();
@ -202,8 +202,8 @@ public class UnlockTable : Table<EquipItem>, IDisposable
public override int Compare(EquipItem lhs, EquipItem rhs) public override int Compare(EquipItem lhs, EquipItem rhs)
{ {
var unlockedLhs = _unlocks.IsUnlocked(lhs.Id, out var timeLhs); var unlockedLhs = _unlocks.IsUnlocked(lhs.ItemId, out var timeLhs);
var unlockedRhs = _unlocks.IsUnlocked(rhs.Id, out var timeRhs); var unlockedRhs = _unlocks.IsUnlocked(rhs.ItemId, out var timeRhs);
var c1 = unlockedLhs.CompareTo(unlockedRhs); var c1 = unlockedLhs.CompareTo(unlockedRhs);
return c1 != 0 ? c1 : timeLhs.CompareTo(timeRhs); return c1 != 0 ? c1 : timeLhs.CompareTo(timeRhs);
} }
@ -215,15 +215,15 @@ public class UnlockTable : Table<EquipItem>, IDisposable
=> 70 * ImGuiHelpers.GlobalScale; => 70 * ImGuiHelpers.GlobalScale;
public override int Compare(EquipItem lhs, EquipItem rhs) public override int Compare(EquipItem lhs, EquipItem rhs)
=> lhs.Id.CompareTo(rhs.Id); => lhs.ItemId.CompareTo(rhs.ItemId);
public override string ToName(EquipItem item) public override string ToName(EquipItem item)
=> item.Id.ToString(); => item.ItemId.ToString();
public override void DrawColumn(EquipItem item, int _) public override void DrawColumn(EquipItem item, int _)
{ {
ImGui.AlignTextToFramePadding(); ImGui.AlignTextToFramePadding();
ImGuiUtil.RightAlign(item.Id.ToString()); ImGuiUtil.RightAlign(item.ItemId.ToString());
} }
} }
@ -243,7 +243,7 @@ public class UnlockTable : Table<EquipItem>, IDisposable
ImGuiUtil.RightAlign(item.ModelString); ImGuiUtil.RightAlign(item.ModelString);
if (ImGui.IsItemHovered() if (ImGui.IsItemHovered()
&& item.Type.Offhand().IsOffhandType() && 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(); using var tt = ImRaii.Tooltip();
ImGui.TextUnformatted("Offhand: " + offhand.ModelString); ImGui.TextUnformatted("Offhand: " + offhand.ModelString);
@ -261,7 +261,7 @@ public class UnlockTable : Table<EquipItem>, IDisposable
if (FilterRegex?.IsMatch(item.ModelString) ?? item.ModelString.Contains(FilterValue, StringComparison.OrdinalIgnoreCase)) if (FilterRegex?.IsMatch(item.ModelString) ?? item.ModelString.Contains(FilterValue, StringComparison.OrdinalIgnoreCase))
return true; 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) return FilterRegex?.IsMatch(offhand.ModelString)
?? offhand.ModelString.Contains(FilterValue, StringComparison.OrdinalIgnoreCase); ?? offhand.ModelString.Contains(FilterValue, StringComparison.OrdinalIgnoreCase);

View file

@ -17,7 +17,7 @@ public class JobService : IDisposable
public readonly IReadOnlyDictionary<byte, Job> Jobs; public readonly IReadOnlyDictionary<byte, Job> Jobs;
public readonly IReadOnlyDictionary<ushort, JobGroup> JobGroups; public readonly IReadOnlyDictionary<ushort, JobGroup> JobGroups;
public event Action<Actor, Job>? JobChanged; public event Action<Actor, Job, Job>? JobChanged;
public JobService(DataManager gameData) public JobService(DataManager gameData)
{ {
@ -40,10 +40,12 @@ public class JobService : IDisposable
private void ChangeJobDetour(nint data, uint jobIndex) private void ChangeJobDetour(nint data, uint jobIndex)
{ {
var old = ((Actor)(data - _characterDataOffset)).Job;
_changeJobHook.Original(data, jobIndex); _changeJobHook.Original(data, jobIndex);
var actor = (Actor)(data - _characterDataOffset); var actor = (Actor)(data - _characterDataOffset);
var job = Jobs.TryGetValue((byte) jobIndex, out var j) ? j : Jobs[0]; var job = Jobs.TryGetValue((byte)jobIndex, out var j) ? j : Jobs[0];
Glamourer.Log.Excessive($"{actor} changed job to {job}"); var oldJob = Jobs.TryGetValue(old, out var o) ? o : Jobs[0];
JobChanged?.Invoke(actor, job); Glamourer.Log.Excessive($"{actor} changed job from {oldJob} to {job}");
JobChanged?.Invoke(actor, oldJob, job);
} }
} }

View file

@ -26,9 +26,13 @@ public class CodeService
public Race EnabledOops { get; private set; } public Race EnabledOops { get; private set; }
public bool EnabledMesmer { get; private set; } public bool EnabledMesmer { get; private set; }
public bool EnabledInventory { get; private set; } public bool EnabledInventory { get; private set; }
public bool EnabledArtisan { get; private set; }
public CodeService(Configuration config) public CodeService(Configuration config)
=> _config = config; {
_config = config;
Load();
}
private void Load() private void Load()
{ {
@ -84,6 +88,7 @@ public class CodeService
_ when CodeOops7.SequenceEqual(sha) => v => EnabledOops = v ? Race.Hrothgar : Race.Unknown, _ when CodeOops7.SequenceEqual(sha) => v => EnabledOops = v ? Race.Hrothgar : Race.Unknown,
_ when CodeOops8.SequenceEqual(sha) => v => EnabledOops = v ? Race.Viera : Race.Unknown, _ when CodeOops8.SequenceEqual(sha) => v => EnabledOops = v ? Race.Viera : Race.Unknown,
_ when CodeInventory.SequenceEqual(sha) => v => EnabledInventory = v, _ when CodeInventory.SequenceEqual(sha) => v => EnabledInventory = v,
_ when CodeArtisan.SequenceEqual(sha) => v => EnabledArtisan = v,
_ => null, _ => null,
}; };
} }
@ -104,5 +109,6 @@ public class CodeService
private static ReadOnlySpan<byte> 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<byte> 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<byte> 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<byte> 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<byte> 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<byte> 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<byte> 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 // @formatter:on
} }

View file

@ -19,7 +19,7 @@ public sealed class CustomizationService : AsyncServiceWrapper<ICustomizationMan
=> HumanModels = humanModels; => HumanModels = humanModels;
public (Customize NewValue, CustomizeFlag Applied, CustomizeFlag Changed) Combine(Customize oldValues, Customize newValues, public (Customize NewValue, CustomizeFlag Applied, CustomizeFlag Changed) Combine(Customize oldValues, Customize newValues,
CustomizeFlag applyWhich) CustomizeFlag applyWhich, bool allowUnknown)
{ {
CustomizeFlag applied = 0; CustomizeFlag applied = 0;
CustomizeFlag changed = 0; CustomizeFlag changed = 0;
@ -46,7 +46,7 @@ public sealed class CustomizationService : AsyncServiceWrapper<ICustomizationMan
continue; continue;
var value = newValues[index]; var value = newValues[index];
if (IsCustomizationValid(set, ret.Face, index, value)) if (allowUnknown || IsCustomizationValid(set, ret.Face, index, value))
{ {
if (ret[index].Value != value.Value) if (ret[index].Value != value.Value)
changed |= flag; changed |= flag;
@ -194,7 +194,7 @@ public sealed class CustomizationService : AsyncServiceWrapper<ICustomizationMan
/// <summary> /// <summary>
/// Check that the given model id is valid. /// 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. /// The return value is an empty string if everything was correct and a warning otherwise.
/// </summary> /// </summary>
public string ValidateModelId(uint modelId, out uint actualModelId, out bool isHuman) public string ValidateModelId(uint modelId, out uint actualModelId, out bool isHuman)
@ -217,9 +217,9 @@ public sealed class CustomizationService : AsyncServiceWrapper<ICustomizationMan
/// The return value is an empty string or a warning message. /// The return value is an empty string or a warning message.
/// </summary> /// </summary>
public static string ValidateCustomizeValue(CustomizationSet set, CustomizeValue face, CustomizeIndex index, CustomizeValue value, 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; actualValue = value;
return string.Empty; return string.Empty;
@ -279,7 +279,7 @@ public sealed class CustomizationService : AsyncServiceWrapper<ICustomizationMan
CustomizeFlag flags = 0; CustomizeFlag flags = 0;
foreach (var idx in Enum.GetValues<CustomizeIndex>().Where(set.IsAvailable)) foreach (var idx in Enum.GetValues<CustomizeIndex>().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; customize[idx] = fixedValue;
flags |= idx.ToFlag(); flags |= idx.ToFlag();

View file

@ -7,6 +7,7 @@ using Lumina.Excel;
using Penumbra.GameData.Data; using Penumbra.GameData.Data;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs; using Penumbra.GameData.Structs;
using static OtterGui.Raii.ImRaii;
using Race = Penumbra.GameData.Enums.Race; using Race = Penumbra.GameData.Enums.Race;
namespace Glamourer.Services; namespace Glamourer.Services;
@ -111,7 +112,7 @@ public class ItemManager : IDisposable
var item = IdentifierService.AwaitedService.Identify(id, variant, slot).FirstOrDefault(); var item = IdentifierService.AwaitedService.Identify(id, variant, slot).FirstOrDefault();
return item.Valid return item.Valid
? item ? 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(); var item = IdentifierService.AwaitedService.Identify(id, type, variant, slot).FirstOrDefault();
return item.Valid return item.Valid
? item ? 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);
} }
/// <summary> Returns whether an item id represents a valid item for a slot and gives the item. </summary> /// <summary> Returns whether an item id represents a valid item for a slot and gives the item. </summary>
@ -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 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. /// The return value is an empty string if there was no problem and a warning otherwise.
/// </summary> /// </summary>
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) if (slot is EquipSlot.MainHand or EquipSlot.OffHand)
throw new Exception("Internal Error: Used armor functionality for weapons."); 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; return string.Empty;
item = NothingItem(slot); item = NothingItem(slot);
@ -169,9 +178,9 @@ public class ItemManager : IDisposable
/// The returned stain id is either the input or 0. /// The returned stain id is either the input or 0.
/// The return value is an empty string if there was no problem and a warning otherwise. /// The return value is an empty string if there was no problem and a warning otherwise.
/// </summary> /// </summary>
public string ValidateStain(StainId stain, out StainId ret) public string ValidateStain(StainId stain, out StainId ret, bool allowUnknown)
{ {
if (IsStainValid(stain)) if (allowUnknown || IsStainValid(stain))
{ {
ret = stain; ret = stain;
return string.Empty; return string.Empty;

View file

@ -23,10 +23,16 @@ public class StateEditor
} }
/// <summary> Change the model id. If the actor is changed from a human to another human, customize and equipData are unused. </summary> /// <summary> Change the model id. If the actor is changed from a human to another human, customize and equipData are unused. </summary>
/// <remarks> We currently only allow changing things to humans, not humans to monsters. </remarks>
public bool ChangeModelId(ActorState state, uint modelId, in Customize customize, nint equipData, StateChanged.Source source, public bool ChangeModelId(ActorState state, uint modelId, in Customize customize, nint equipData, StateChanged.Source source,
out uint oldModelId, uint key = 0) out uint oldModelId, uint key = 0)
{ {
oldModelId = state.ModelData.ModelId; oldModelId = state.ModelData.ModelId;
// TODO think about this.
if (modelId != 0)
return false;
if (!state.CanUnlock(key)) if (!state.CanUnlock(key))
return false; return false;
@ -94,7 +100,7 @@ public class StateEditor
if (!state.CanUnlock(key)) if (!state.CanUnlock(key))
return false; 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) if (changed == 0)
return false; return false;
@ -130,6 +136,11 @@ public class StateEditor
if (!state.CanUnlock(key)) if (!state.CanUnlock(key))
return false; 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.SetItem(slot, item);
state.ModelData.SetStain(slot, stain); state.ModelData.SetStain(slot, stain);
state[slot, false] = source; state[slot, false] = source;

View file

@ -245,7 +245,7 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
? _applier.ChangeArmor(state, slot, source is StateChanged.Source.Manual or StateChanged.Source.Ipc) ? _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); : _applier.ChangeWeapon(state, slot, source is StateChanged.Source.Manual or StateChanged.Source.Ipc);
Glamourer.Log.Verbose( 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)); _event.Invoke(type, source, state, actors, (old, item, slot));
} }
@ -260,7 +260,7 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
? _applier.ChangeArmor(state, slot, source is StateChanged.Source.Manual or StateChanged.Source.Ipc) ? _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); : _applier.ChangeWeapon(state, slot, source is StateChanged.Source.Manual or StateChanged.Source.Ipc);
Glamourer.Log.Verbose( 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(type, source, state, actors, (old, item, slot));
_event.Invoke(StateChanged.Type.Stain, source, state, actors, (oldStain, stain, slot)); _event.Invoke(StateChanged.Type.Stain, source, state, actors, (oldStain, stain, slot));
} }

View file

@ -125,10 +125,10 @@ public class ItemUnlockManager : ISavable, IDisposable
bool AddItem(uint itemId) 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; return false;
_event.Invoke(ObjectUnlocked.Type.Item, equip.Id, DateTimeOffset.FromUnixTimeMilliseconds(time)); _event.Invoke(ObjectUnlocked.Type.Item, equip.ItemId, DateTimeOffset.FromUnixTimeMilliseconds(time));
return true; return true;
} }
@ -274,7 +274,7 @@ public class ItemUnlockManager : ISavable, IDisposable
foreach (var row in cabinet) foreach (var row in cabinet)
{ {
if (items.ItemService.AwaitedService.TryGetValue(row.Item.Row, out var item)) 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<GilShopItem>()!; var gilShop = gameData.GetExcelSheet<GilShopItem>()!;
@ -290,7 +290,7 @@ public class ItemUnlockManager : ISavable, IDisposable
var type = (quest1 != 0 ? UnlockType.Quest1 : 0) var type = (quest1 != 0 ? UnlockType.Quest1 : 0)
| (quest2 != 0 ? UnlockType.Quest2 : 0) | (quest2 != 0 ? UnlockType.Quest2 : 0)
| (achievement != 0 ? UnlockType.Achievement : 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; return ret;