Use more specific ID types in most places, fix issues with actor identifiers.

This commit is contained in:
Ottermandias 2023-07-28 18:21:53 +02:00
parent c7f9d3a3c0
commit a0456e7ae7
26 changed files with 184 additions and 170 deletions

View file

@ -513,10 +513,11 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>
{ {
ObjectKind.BattleNpc => manager.Data.BNpcs, ObjectKind.BattleNpc => manager.Data.BNpcs,
ObjectKind.EventNpc => manager.Data.ENpcs, ObjectKind.EventNpc => manager.Data.ENpcs,
_ => throw new NotImplementedException(), _ => new Dictionary<uint, string>(),
}; };
return table.Where(kvp => kvp.Value == name) return table.Where(kvp => kvp.Value == name)
.Select(kvp => manager.CreateIndividualUnchecked(identifier.Type, identifier.PlayerName, identifier.HomeWorld, identifier.Kind, .Select(kvp => manager.CreateIndividualUnchecked(identifier.Type, identifier.PlayerName, identifier.HomeWorld.Id,
identifier.Kind,
kvp.Key)).ToArray(); kvp.Key)).ToArray();
} }

View file

@ -168,11 +168,11 @@ public class DesignBase
protected JObject SerializeEquipment() protected JObject SerializeEquipment()
{ {
static JObject Serialize(ulong id, StainId stain, bool apply, bool applyStain) static JObject Serialize(CustomItemId id, StainId stain, bool apply, bool applyStain)
=> new() => new()
{ {
["ItemId"] = id, ["ItemId"] = id.Id,
["Stain"] = stain.Value, ["Stain"] = stain.Id,
["Apply"] = apply, ["Apply"] = apply,
["ApplyStain"] = applyStain, ["ApplyStain"] = applyStain,
}; };
@ -267,9 +267,9 @@ public class DesignBase
return; return;
} }
static (ulong, StainId, bool, bool) ParseItem(EquipSlot slot, JToken? item) static (CustomItemId, StainId, bool, bool) ParseItem(EquipSlot slot, JToken? item)
{ {
var id = item?["ItemId"]?.ToObject<ulong>() ?? ItemManager.NothingId(slot); var id = item?["ItemId"]?.ToObject<ulong>() ?? ItemManager.NothingId(slot).Id;
var stain = (StainId)(item?["Stain"]?.ToObject<byte>() ?? 0); var stain = (StainId)(item?["Stain"]?.ToObject<byte>() ?? 0);
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;
@ -302,7 +302,7 @@ public class DesignBase
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((uint)id, (uint)idOff, out var main, out var off)); PrintWarning(items.ValidateWeapons(id.Item, idOff.Item, out var main, out var off));
PrintWarning(items.ValidateStain(stain, out stain, allowUnknown)); PrintWarning(items.ValidateStain(stain, out stain, allowUnknown));
PrintWarning(items.ValidateStain(stainOff, out stainOff, allowUnknown)); PrintWarning(items.ValidateStain(stainOff, out stainOff, allowUnknown));
design.DesignData.SetItem(EquipSlot.MainHand, main); design.DesignData.SetItem(EquipSlot.MainHand, main);

View file

@ -113,42 +113,42 @@ public class DesignBase64Migration
var mdl = eq[idx]; var mdl = eq[idx];
var item = items.Identify(slot, mdl.Set, mdl.Variant); var item = items.Identify(slot, mdl.Set, mdl.Variant);
if (!item.Valid) if (!item.Valid)
throw new Exception($"Base64 string invalid, item could not be identified."); throw new Exception("Base64 string invalid, item could not be identified.");
data.SetItem(slot, item); data.SetItem(slot, item);
data.SetStain(slot, mdl.Stain); data.SetStain(slot, mdl.Stain);
} }
var main = cur[0].Set.Value == 0 var main = cur[0].Set.Id == 0
? items.DefaultSword ? items.DefaultSword
: items.Identify(EquipSlot.MainHand, cur[0].Set, cur[0].Type, (byte)cur[0].Variant); : items.Identify(EquipSlot.MainHand, cur[0].Set, cur[0].Type, cur[0].Variant);
if (!main.Valid) if (!main.Valid)
throw new Exception($"Base64 string invalid, weapon could not be identified."); throw new Exception("Base64 string invalid, weapon could not be identified.");
data.SetItem(EquipSlot.MainHand, main); data.SetItem(EquipSlot.MainHand, main);
data.SetStain(EquipSlot.MainHand, cur[0].Stain); data.SetStain(EquipSlot.MainHand, cur[0].Stain);
EquipItem off; EquipItem off;
// Fist weapon hack // Fist weapon hack
if (main.ModelId.Value is > 1600 and < 1651 && cur[1].Variant == 0) if (main.ModelId.Id is > 1600 and < 1651 && cur[1].Variant == 0)
{ {
off = items.Identify(EquipSlot.OffHand, (SetId)(main.ModelId.Value + 50), main.WeaponType, main.Variant, main.Type); off = items.Identify(EquipSlot.OffHand, (SetId)(main.ModelId.Id + 50), main.WeaponType, main.Variant, main.Type);
var gauntlet = items.Identify(EquipSlot.Hands, cur[1].Set, (byte)cur[1].Type); var gauntlet = items.Identify(EquipSlot.Hands, cur[1].Set, (Variant)cur[1].Type.Id);
if (!gauntlet.Valid) if (!gauntlet.Valid)
throw new Exception($"Base64 string invalid, item could not be identified."); throw new Exception("Base64 string invalid, item could not be identified.");
data.SetItem(EquipSlot.Hands, gauntlet); data.SetItem(EquipSlot.Hands, gauntlet);
data.SetStain(EquipSlot.Hands, cur[0].Stain); data.SetStain(EquipSlot.Hands, cur[0].Stain);
} }
else else
{ {
off = cur[0].Set.Value == 0 off = cur[0].Set.Id == 0
? ItemManager.NothingItem(FullEquipType.Shield) ? ItemManager.NothingItem(FullEquipType.Shield)
: items.Identify(EquipSlot.OffHand, cur[1].Set, cur[1].Type, (byte)cur[1].Variant, main.Type); : items.Identify(EquipSlot.OffHand, cur[1].Set, cur[1].Type, cur[1].Variant, main.Type);
} }
if (main.Type.ValidOffhand() != FullEquipType.Unknown && !off.Valid) if (main.Type.ValidOffhand() != FullEquipType.Unknown && !off.Valid)
throw new Exception($"Base64 string invalid, weapon could not be identified."); throw new Exception("Base64 string invalid, weapon could not be identified.");
data.SetItem(EquipSlot.OffHand, off); data.SetItem(EquipSlot.OffHand, off);
data.SetStain(EquipSlot.OffHand, cur[1].Stain); data.SetStain(EquipSlot.OffHand, cur[1].Stain);

View file

@ -95,11 +95,11 @@ public unsafe struct DesignData
if (index > 11) if (index > 11)
return false; return false;
_itemIds[index] = item.ItemId; _itemIds[index] = item.ItemId.Id;
_iconIds[index] = item.IconId; _iconIds[index] = item.IconId.Id;
_equipmentBytes[4 * index + 0] = (byte)item.ModelId; _equipmentBytes[4 * index + 0] = (byte)item.ModelId.Id;
_equipmentBytes[4 * index + 1] = (byte)(item.ModelId.Value >> 8); _equipmentBytes[4 * index + 1] = (byte)(item.ModelId.Id >> 8);
_equipmentBytes[4 * index + 2] = item.Variant; _equipmentBytes[4 * index + 2] = item.Variant.Id;
switch (index) switch (index)
{ {
// @formatter:off // @formatter:off
@ -132,18 +132,18 @@ public unsafe struct DesignData
public bool SetStain(EquipSlot slot, StainId stain) public bool SetStain(EquipSlot slot, StainId stain)
=> slot.ToIndex() switch => slot.ToIndex() switch
{ {
0 => SetIfDifferent(ref _equipmentBytes[3], stain.Value), 0 => SetIfDifferent(ref _equipmentBytes[3], stain.Id),
1 => SetIfDifferent(ref _equipmentBytes[7], stain.Value), 1 => SetIfDifferent(ref _equipmentBytes[7], stain.Id),
2 => SetIfDifferent(ref _equipmentBytes[11], stain.Value), 2 => SetIfDifferent(ref _equipmentBytes[11], stain.Id),
3 => SetIfDifferent(ref _equipmentBytes[15], stain.Value), 3 => SetIfDifferent(ref _equipmentBytes[15], stain.Id),
4 => SetIfDifferent(ref _equipmentBytes[19], stain.Value), 4 => SetIfDifferent(ref _equipmentBytes[19], stain.Id),
5 => SetIfDifferent(ref _equipmentBytes[23], stain.Value), 5 => SetIfDifferent(ref _equipmentBytes[23], stain.Id),
6 => SetIfDifferent(ref _equipmentBytes[27], stain.Value), 6 => SetIfDifferent(ref _equipmentBytes[27], stain.Id),
7 => SetIfDifferent(ref _equipmentBytes[31], stain.Value), 7 => SetIfDifferent(ref _equipmentBytes[31], stain.Id),
8 => SetIfDifferent(ref _equipmentBytes[35], stain.Value), 8 => SetIfDifferent(ref _equipmentBytes[35], stain.Id),
9 => SetIfDifferent(ref _equipmentBytes[39], stain.Value), 9 => SetIfDifferent(ref _equipmentBytes[39], stain.Id),
10 => SetIfDifferent(ref _equipmentBytes[43], stain.Value), 10 => SetIfDifferent(ref _equipmentBytes[43], stain.Id),
11 => SetIfDifferent(ref _equipmentBytes[47], stain.Value), 11 => SetIfDifferent(ref _equipmentBytes[47], stain.Id),
_ => false, _ => false,
}; };

View file

@ -398,7 +398,7 @@ public class DesignManager
design.LastEdit = DateTimeOffset.UtcNow; design.LastEdit = DateTimeOffset.UtcNow;
_saveService.QueueSave(design); _saveService.QueueSave(design);
Glamourer.Log.Debug($"Set stain of {slot} equipment piece to {stain.Value}."); Glamourer.Log.Debug($"Set stain of {slot} equipment piece to {stain.Id}.");
_event.Invoke(DesignChanged.Type.Stain, design, (oldStain, stain, slot)); _event.Invoke(DesignChanged.Type.Stain, design, (oldStain, stain, slot));
} }

View file

@ -43,7 +43,7 @@ public class EquipmentDrawer
_stainData = items.Stains; _stainData = items.Stains;
_stainCombo = new FilterComboColors(DefaultWidth - 20, _stainCombo = new FilterComboColors(DefaultWidth - 20,
_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))));
_itemCombo = EquipSlotExtensions.EqdpSlots.Select(e => new ItemCombo(gameData, items, e, textures)).ToArray(); _itemCombo = EquipSlotExtensions.EqdpSlots.Select(e => new ItemCombo(gameData, items, e)).ToArray();
_weaponCombo = new Dictionary<FullEquipType, WeaponCombo>(FullEquipTypeExtensions.WeaponTypes.Count * 2); _weaponCombo = new Dictionary<FullEquipType, WeaponCombo>(FullEquipTypeExtensions.WeaponTypes.Count * 2);
foreach (var type in Enum.GetValues<FullEquipType>()) foreach (var type in Enum.GetValues<FullEquipType>())
{ {
@ -118,7 +118,7 @@ public class EquipmentDrawer
bool allWeapons, bool allWeapons,
out bool rApplyMainhand, out bool rApplyMainhandStain, out bool rApplyOffhand, out bool rApplyOffhandStain, bool locked) out bool rApplyMainhand, out bool rApplyMainhandStain, out bool rApplyOffhand, out bool rApplyOffhandStain, bool locked)
{ {
if (cMainhand.ModelId.Value == 0) if (cMainhand.ModelId.Id == 0)
{ {
rOffhand = cOffhand; rOffhand = cOffhand;
rMainhand = cMainhand; rMainhand = cMainhand;
@ -239,7 +239,7 @@ public class EquipmentDrawer
if (change) if (change)
armor = combo.CurrentSelection; armor = combo.CurrentSelection;
if (!locked && armor.ModelId.Value != 0) if (!locked && armor.ModelId.Id != 0)
{ {
ImGuiUtil.HoverTooltip("Right-click to clear."); ImGuiUtil.HoverTooltip("Right-click to clear.");
if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
@ -282,15 +282,15 @@ public class EquipmentDrawer
/// <summary> Draw an input for armor that can set arbitrary values instead of choosing items. </summary> /// <summary> Draw an input for armor that can set arbitrary values instead of choosing items. </summary>
private bool DrawArmorArtisan(EquipSlot slot, EquipItem current, out EquipItem armor) private bool DrawArmorArtisan(EquipSlot slot, EquipItem current, out EquipItem armor)
{ {
int setId = current.ModelId.Value; int setId = current.ModelId.Id;
int variant = current.Variant; int variant = current.Variant.Id;
var ret = false; var ret = false;
armor = current; armor = current;
ImGui.SetNextItemWidth(80 * ImGuiHelpers.GlobalScale); ImGui.SetNextItemWidth(80 * ImGuiHelpers.GlobalScale);
if (ImGui.InputInt("##setId", ref setId, 0, 0)) if (ImGui.InputInt("##setId", ref setId, 0, 0))
{ {
var newSetId = (SetId)Math.Clamp(setId, 0, ushort.MaxValue); var newSetId = (SetId)Math.Clamp(setId, 0, ushort.MaxValue);
if (newSetId.Value != current.ModelId.Value) if (newSetId.Id != current.ModelId.Id)
{ {
armor = _items.Identify(slot, newSetId, current.Variant); armor = _items.Identify(slot, newSetId, current.Variant);
ret = true; ret = true;
@ -315,7 +315,7 @@ public class EquipmentDrawer
/// <summary> Draw an input for stain that can set arbitrary values instead of choosing valid stains. </summary> /// <summary> Draw an input for stain that can set arbitrary values instead of choosing valid stains. </summary>
private bool DrawStainArtisan(EquipSlot slot, StainId current, out StainId stain) private bool DrawStainArtisan(EquipSlot slot, StainId current, out StainId stain)
{ {
int stainId = current.Value; int stainId = current.Id;
ImGui.SetNextItemWidth(40 * ImGuiHelpers.GlobalScale); ImGui.SetNextItemWidth(40 * ImGuiHelpers.GlobalScale);
if (ImGui.InputInt("##stain", ref stainId, 0, 0)) if (ImGui.InputInt("##stain", ref stainId, 0, 0))
{ {

View file

@ -15,16 +15,13 @@ namespace Glamourer.Gui.Equipment;
public sealed class ItemCombo : FilterComboCache<EquipItem> public sealed class ItemCombo : FilterComboCache<EquipItem>
{ {
private readonly TextureService _textures;
public readonly string Label; public readonly string Label;
private uint _currentItem; private ItemId _currentItem;
private float _innerWidth; private float _innerWidth;
public ItemCombo(DataManager gameData, ItemManager items, EquipSlot slot, TextureService textures) public ItemCombo(DataManager gameData, ItemManager items, EquipSlot slot)
: base(() => GetItems(items, slot)) : base(() => GetItems(items, slot))
{ {
_textures = textures;
Label = GetLabel(gameData, slot); Label = GetLabel(gameData, slot);
_currentItem = ItemManager.NothingId(slot); _currentItem = ItemManager.NothingId(slot);
SearchByParts = true; SearchByParts = true;
@ -47,7 +44,7 @@ public sealed class ItemCombo : FilterComboCache<EquipItem>
return base.UpdateCurrentSelected(CurrentSelectionIdx); return base.UpdateCurrentSelected(CurrentSelectionIdx);
} }
public bool Draw(string previewName, uint previewIdx, float width, float innerWidth) public bool Draw(string previewName, ItemId previewIdx, float width, float innerWidth)
{ {
_innerWidth = innerWidth; _innerWidth = innerWidth;
_currentItem = previewIdx; _currentItem = previewIdx;
@ -64,12 +61,12 @@ public sealed class ItemCombo : FilterComboCache<EquipItem>
var ret = ImGui.Selectable(name, selected); var ret = ImGui.Selectable(name, selected);
ImGui.SameLine(); ImGui.SameLine();
using var color = ImRaii.PushColor(ImGuiCol.Text, 0xFF808080); using var color = ImRaii.PushColor(ImGuiCol.Text, 0xFF808080);
ImGuiUtil.RightAlign($"({obj.ModelId.Value}-{obj.Variant})"); ImGuiUtil.RightAlign($"({obj.ModelString})");
return ret; return ret;
} }
protected override bool IsVisible(int globalIndex, LowerString filter) protected override bool IsVisible(int globalIndex, LowerString filter)
=> base.IsVisible(globalIndex, filter) || filter.IsContained(Items[globalIndex].ModelId.Value.ToString()); => base.IsVisible(globalIndex, filter) || filter.IsContained(Items[globalIndex].ModelId.Id.ToString());
protected override string ToString(EquipItem obj) protected override string ToString(EquipItem obj)
=> obj.Name; => obj.Name;

View file

@ -15,7 +15,7 @@ namespace Glamourer.Gui.Equipment;
public sealed class WeaponCombo : FilterComboCache<EquipItem> public sealed class WeaponCombo : FilterComboCache<EquipItem>
{ {
public readonly string Label; public readonly string Label;
private uint _currentItemId; private ItemId _currentItemId;
private float _innerWidth; private float _innerWidth;
public WeaponCombo(ItemManager items, FullEquipType type) public WeaponCombo(ItemManager items, FullEquipType type)
@ -45,7 +45,7 @@ public sealed class WeaponCombo : FilterComboCache<EquipItem>
protected override float GetFilterWidth() protected override float GetFilterWidth()
=> _innerWidth - 2 * ImGui.GetStyle().FramePadding.X; => _innerWidth - 2 * ImGui.GetStyle().FramePadding.X;
public bool Draw(string previewName, uint previewId, float width, float innerWidth) public bool Draw(string previewName, ItemId previewId, float width, float innerWidth)
{ {
_currentItemId = previewId; _currentItemId = previewId;
_innerWidth = innerWidth; _innerWidth = innerWidth;
@ -59,12 +59,12 @@ public sealed class WeaponCombo : FilterComboCache<EquipItem>
var ret = ImGui.Selectable(name, selected); var ret = ImGui.Selectable(name, selected);
ImGui.SameLine(); ImGui.SameLine();
using var color = ImRaii.PushColor(ImGuiCol.Text, 0xFF808080); using var color = ImRaii.PushColor(ImGuiCol.Text, 0xFF808080);
ImGuiUtil.RightAlign($"({obj.ModelId.Value}-{obj.WeaponType.Value}-{obj.Variant})"); ImGuiUtil.RightAlign($"({obj.ModelId.Id}-{obj.WeaponType.Id}-{obj.Variant})");
return ret; return ret;
} }
protected override bool IsVisible(int globalIndex, LowerString filter) protected override bool IsVisible(int globalIndex, LowerString filter)
=> base.IsVisible(globalIndex, filter) || filter.IsContained(Items[globalIndex].ModelId.Value.ToString()); => base.IsVisible(globalIndex, filter) || filter.IsContained(Items[globalIndex].ModelId.Id.ToString());
protected override string ToString(EquipItem obj) protected override string ToString(EquipItem obj)
=> obj.Name; => obj.Name;

View file

@ -173,7 +173,7 @@ public class PenumbraChangedItemTooltip : IDisposable
private bool CanApplyWeapon(EquipSlot slot, EquipItem item) private bool CanApplyWeapon(EquipSlot slot, EquipItem item)
{ {
var main = _objects.Player.GetMainhand(); var main = _objects.Player.GetMainhand();
var mainItem = _items.Identify(slot, main.Set, main.Type, (byte)main.Variant); var mainItem = _items.Identify(slot, main.Set, main.Type, main.Variant);
if (slot == EquipSlot.MainHand) if (slot == EquipSlot.MainHand)
return item.Type == mainItem.Type; return item.Type == mainItem.Type;

View file

@ -78,7 +78,7 @@ public sealed class HumanNpcCombo : FilterComboCache<(string Name, ObjectKind Ki
{ {
case ObjectKind.BattleNpc: case ObjectKind.BattleNpc:
var nameIds = service.AwaitedService.GetBnpcNames(id); var nameIds = service.AwaitedService.GetBnpcNames(id);
ret.AddRange(nameIds.Select(nameId => (name, kind, nameId))); ret.AddRange(nameIds.Select(nameId => (service.AwaitedService.Name(ObjectKind.BattleNpc, nameId), kind, nameId.Id)));
break; break;
case ObjectKind.EventNpc: case ObjectKind.EventNpc:
ret.Add((name, kind, id)); ret.Add((name, kind, id));
@ -87,8 +87,10 @@ public sealed class HumanNpcCombo : FilterComboCache<(string Name, ObjectKind Ki
} }
} }
return ret.GroupBy(t => (t.Name, t.Kind)).OrderBy(g => g.Key, Comparer) return ret.GroupBy(t => (t.Name, t.Kind))
.Select(g => (g.Key.Name, g.Key.Kind, g.Select(g => g.Id).ToArray())).ToList(); .OrderBy(g => g.Key, Comparer)
.Select(g => (g.Key.Name, g.Key.Kind, g.Select(g => g.Id).Distinct().ToArray()))
.ToList();
} }
private static readonly NameComparer Comparer = new(); private static readonly NameComparer Comparer = new();

View file

@ -640,16 +640,17 @@ public unsafe class DebugTab : ITab
foreach (var slot in EquipSlotExtensions.EqdpSlots) foreach (var slot in EquipSlotExtensions.EqdpSlots)
{ {
var identified = _items.Identify(slot, (SetId)_setId, (byte)_variant); var identified = _items.Identify(slot, (SetId)_setId, (Variant)_variant);
Text(identified.Name); Text(identified.Name);
ImGuiUtil.HoverTooltip(string.Join("\n", ImGuiUtil.HoverTooltip(string.Join("\n",
_items.IdentifierService.AwaitedService.Identify((SetId)_setId, (ushort)_variant, slot).Select(i => $"{i.Name} {i.Id} {i.ItemId} {i.IconId}"))); _items.IdentifierService.AwaitedService.Identify((SetId)_setId, (Variant)_variant, slot)
.Select(i => $"{i.Name} {i.Id} {i.ItemId} {i.IconId}")));
} }
var weapon = _items.Identify(EquipSlot.MainHand, (SetId)_setId, (WeaponType)_secondaryId, (byte)_variant); var weapon = _items.Identify(EquipSlot.MainHand, (SetId)_setId, (WeaponType)_secondaryId, (Variant)_variant);
Text(weapon.Name); Text(weapon.Name);
ImGuiUtil.HoverTooltip(string.Join("\n", ImGuiUtil.HoverTooltip(string.Join("\n",
_items.IdentifierService.AwaitedService.Identify((SetId)_setId, (WeaponType)_secondaryId, (ushort)_variant, EquipSlot.MainHand))); _items.IdentifierService.AwaitedService.Identify((SetId)_setId, (WeaponType)_secondaryId, (Variant)_variant, EquipSlot.MainHand)));
} }
private void DrawRestrictedGear() private void DrawRestrictedGear()
@ -672,7 +673,7 @@ public unsafe class DebugTab : ITab
foreach (var slot in EquipSlotExtensions.EqdpSlots) foreach (var slot in EquipSlotExtensions.EqdpSlots)
{ {
var (replaced, model) = var (replaced, model) =
_items.RestrictedGear.ResolveRestricted(new CharacterArmor((SetId)_setId, (byte)_variant, 0), slot, race, gender); _items.RestrictedGear.ResolveRestricted(new CharacterArmor((SetId)_setId, (Variant)_variant, 0), slot, race, gender);
if (replaced) if (replaced)
ImGui.TextUnformatted($"{race.ToName()} - {gender} - {slot.ToName()} resolves to {model}."); ImGui.TextUnformatted($"{race.ToName()} - {gender} - {slot.ToName()} resolves to {model}.");
} }
@ -765,18 +766,18 @@ public unsafe class DebugTab : ITab
ImRaii.TreeNode($"Default Sword: {_items.DefaultSword.Name} ({_items.DefaultSword.ItemId}) ({_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.Id,
$"{p.Item2.Name} ({(p.Item2.WeaponType == 0 ? p.Item2.Armor().ToString() : p.Item2.Weapon().ToString())})")) $"{p.Item2.Name} ({(p.Item2.WeaponType == 0 ? p.Item2.Armor().ToString() : p.Item2.Weapon().ToString())})"))
.OrderBy(p => p.Item1)); .OrderBy(p => p.Item1));
DrawNameTable("All Items (Off)", ref _itemFilter, DrawNameTable("All Items (Off)", ref _itemFilter,
_items.ItemService.AwaitedService.AllItems(false).Select(p => (p.Item1, _items.ItemService.AwaitedService.AllItems(false).Select(p => (p.Item1.Id,
$"{p.Item2.Name} ({(p.Item2.WeaponType == 0 ? p.Item2.Armor().ToString() : p.Item2.Weapon().ToString())})")) $"{p.Item2.Name} ({(p.Item2.WeaponType == 0 ? p.Item2.Armor().ToString() : p.Item2.Weapon().ToString())})"))
.OrderBy(p => p.Item1)); .OrderBy(p => p.Item1));
foreach (var type in Enum.GetValues<FullEquipType>().Skip(1)) foreach (var type in Enum.GetValues<FullEquipType>().Skip(1))
{ {
DrawNameTable(type.ToName(), ref _itemFilter, DrawNameTable(type.ToName(), ref _itemFilter,
_items.ItemService.AwaitedService[type] _items.ItemService.AwaitedService[type]
.Select(p => (Id: p.ItemId, $"{p.Name} ({(p.WeaponType == 0 ? p.Armor().ToString() : p.Weapon().ToString())})"))); .Select(p => (Id: p.ItemId.Id, $"{p.Name} ({(p.WeaponType == 0 ? p.Armor().ToString() : p.Weapon().ToString())})")));
} }
} }
@ -803,10 +804,10 @@ public unsafe class DebugTab : ITab
var skips = ImGuiClip.GetNecessarySkips(height); var skips = ImGuiClip.GetNecessarySkips(height);
ImGui.TableNextRow(); ImGui.TableNextRow();
var remainder = ImGuiClip.FilteredClippedDraw(_items.Stains, skips, var remainder = ImGuiClip.FilteredClippedDraw(_items.Stains, skips,
p => p.Key.Value.ToString().Contains(_stainFilter) || p.Value.Name.Contains(_stainFilter, StringComparison.OrdinalIgnoreCase), p => p.Key.Id.ToString().Contains(_stainFilter) || p.Value.Name.Contains(_stainFilter, StringComparison.OrdinalIgnoreCase),
p => p =>
{ {
ImGuiUtil.DrawTableColumn(p.Key.Value.ToString("D3")); ImGuiUtil.DrawTableColumn(p.Key.Id.ToString("D3"));
ImGui.TableNextColumn(); ImGui.TableNextColumn();
ImGui.GetWindowDrawList().AddRectFilled(ImGui.GetCursorScreenPos(), ImGui.GetWindowDrawList().AddRectFilled(ImGui.GetCursorScreenPos(),
ImGui.GetCursorScreenPos() + new Vector2(ImGui.GetTextLineHeight()), ImGui.GetCursorScreenPos() + new Vector2(ImGui.GetTextLineHeight()),
@ -1038,9 +1039,9 @@ public unsafe class DebugTab : ITab
try try
{ {
_clipboardText = ImGui.GetClipboardText(); _clipboardText = ImGui.GetClipboardText();
_clipboardData = Convert.FromBase64String(_clipboardText); _clipboardData = Convert.FromBase64String(_clipboardText);
_version = _clipboardData[0]; _version = _clipboardData[0];
if (_version == 5) if (_version == 5)
_clipboardData = _clipboardData[DesignBase64Migration.Base64SizeV4..]; _clipboardData = _clipboardData[DesignBase64Migration.Base64SizeV4..];
_version = _clipboardData.Decompress(out _dataUncompressed); _version = _clipboardData.Decompress(out _dataUncompressed);
@ -1116,7 +1117,7 @@ public unsafe class DebugTab : ITab
static string ItemString(in DesignData data, EquipSlot slot) static string ItemString(in DesignData data, EquipSlot slot)
{ {
var item = data.Item(slot); var item = data.Item(slot);
return $"{item.Name} ({item.ModelId.Value}{(item.WeaponType != 0 ? $"-{item.WeaponType.Value}" : string.Empty)}-{item.Variant})"; return $"{item.Name} ({item.ModelId.Id}{(item.WeaponType != 0 ? $"-{item.WeaponType.Id}" : string.Empty)}-{item.Variant})";
} }
PrintRow("Model ID", state.BaseData.ModelId, state.ModelData.ModelId, state[ActorState.MetaIndex.ModelId]); PrintRow("Model ID", state.BaseData.ModelId, state.ModelData.ModelId, state[ActorState.MetaIndex.ModelId]);
@ -1137,8 +1138,8 @@ public unsafe class DebugTab : ITab
foreach (var slot in EquipSlotExtensions.EqdpSlots.Prepend(EquipSlot.OffHand).Prepend(EquipSlot.MainHand)) foreach (var slot in EquipSlotExtensions.EqdpSlots.Prepend(EquipSlot.OffHand).Prepend(EquipSlot.MainHand))
{ {
PrintRow(slot.ToName(), ItemString(state.BaseData, slot), ItemString(state.ModelData, slot), state[slot, false]); PrintRow(slot.ToName(), ItemString(state.BaseData, slot), ItemString(state.ModelData, slot), state[slot, false]);
ImGuiUtil.DrawTableColumn(state.BaseData.Stain(slot).Value.ToString()); ImGuiUtil.DrawTableColumn(state.BaseData.Stain(slot).Id.ToString());
ImGuiUtil.DrawTableColumn(state.ModelData.Stain(slot).Value.ToString()); ImGuiUtil.DrawTableColumn(state.ModelData.Stain(slot).Id.ToString());
ImGuiUtil.DrawTableColumn(state[slot, true].ToString()); ImGuiUtil.DrawTableColumn(state[slot, true].ToString());
} }
@ -1453,7 +1454,7 @@ public unsafe class DebugTab : ITab
ImGui.TableNextColumn(); ImGui.TableNextColumn();
var skips = ImGuiClip.GetNecessarySkips(ImGui.GetTextLineHeightWithSpacing()); var skips = ImGuiClip.GetNecessarySkips(ImGui.GetTextLineHeightWithSpacing());
ImGui.TableNextRow(); ImGui.TableNextRow();
var remainder = ImGuiClip.ClippedDraw(_itemUnlocks.Unlocked, skips, t => var remainder = ImGuiClip.ClippedDraw(_itemUnlocks, skips, t =>
{ {
ImGuiUtil.DrawTableColumn(t.Key.ToString()); ImGuiUtil.DrawTableColumn(t.Key.ToString());
if (_items.ItemService.AwaitedService.TryGetValue(t.Key, EquipSlot.MainHand, out var equip)) if (_items.ItemService.AwaitedService.TryGetValue(t.Key, EquipSlot.MainHand, out var equip))
@ -1474,7 +1475,7 @@ public unsafe class DebugTab : ITab
? "Always" ? "Always"
: time.LocalDateTime.ToString("g") : time.LocalDateTime.ToString("g")
: "Never"); : "Never");
}, _itemUnlocks.Unlocked.Count); }, _itemUnlocks.Count);
ImGuiClip.DrawEndDummy(remainder, ImGui.GetTextLineHeight()); ImGuiClip.DrawEndDummy(remainder, ImGui.GetTextLineHeight());
} }

View file

@ -154,7 +154,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.Id, out var time);
var iconHandle = _textures.LoadIcon(item.IconId); var iconHandle = _textures.LoadIcon(item.IconId.Id);
if (!iconHandle.HasValue) if (!iconHandle.HasValue)
return; return;

View file

@ -61,7 +61,7 @@ public class UnlockTable : Table<EquipItem>, IDisposable
public override void DrawColumn(EquipItem item, int _) public override void DrawColumn(EquipItem item, int _)
{ {
var iconHandle = _textures.LoadIcon(item.IconId); var iconHandle = _textures.LoadIcon(item.IconId.Id);
if (iconHandle.HasValue) if (iconHandle.HasValue)
ImGuiUtil.HoverIcon(iconHandle.Value, new Vector2(ImGui.GetFrameHeight())); ImGuiUtil.HoverIcon(iconHandle.Value, new Vector2(ImGui.GetFrameHeight()));
else else
@ -214,7 +214,7 @@ 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.ItemId.CompareTo(rhs.ItemId); => lhs.ItemId.Id.CompareTo(rhs.ItemId.Id);
public override string ToName(EquipItem item) public override string ToName(EquipItem item)
=> item.ItemId.ToString(); => item.ItemId.ToString();

View file

@ -27,7 +27,7 @@ public static class UiHelpers
{ {
public static void DrawIcon(this EquipItem item, TextureService textures, Vector2 size) public static void DrawIcon(this EquipItem item, TextureService textures, Vector2 size)
{ {
var isEmpty = item.ModelId.Value == 0; var isEmpty = item.ModelId.Id == 0;
var (ptr, textureSize, empty) = textures.GetIcon(item); var (ptr, textureSize, empty) = textures.GetIcon(item);
if (empty) if (empty)
{ {

View file

@ -119,7 +119,7 @@ public class ContextMenuService : IDisposable
_state.ChangeEquip(state, slot, item, 0, StateChanged.Source.Manual); _state.ChangeEquip(state, slot, item, 0, StateChanged.Source.Manual);
if (item.Type.ValidOffhand().IsOffhandType()) if (item.Type.ValidOffhand().IsOffhandType())
{ {
if (item.ModelId.Value is > 1600 and < 1651 if (item.ModelId.Id is > 1600 and < 1651
&& _items.ItemService.AwaitedService.TryGetValue(item.ItemId, EquipSlot.Hands, out var gauntlets)) && _items.ItemService.AwaitedService.TryGetValue(item.ItemId, EquipSlot.Hands, out var gauntlets))
_state.ChangeEquip(state, EquipSlot.Hands, gauntlets, 0, StateChanged.Source.Manual); _state.ChangeEquip(state, EquipSlot.Hands, gauntlets, 0, StateChanged.Source.Manual);
if (_items.ItemService.AwaitedService.TryGetValue(item.ItemId, EquipSlot.OffHand, out var offhand)) if (_items.ItemService.AwaitedService.TryGetValue(item.ItemId, EquipSlot.OffHand, out var offhand))
@ -143,7 +143,7 @@ public class ContextMenuService : IDisposable
_state.ChangeEquip(state, slot, item, 0, StateChanged.Source.Manual); _state.ChangeEquip(state, slot, item, 0, StateChanged.Source.Manual);
if (item.Type.ValidOffhand().IsOffhandType()) if (item.Type.ValidOffhand().IsOffhandType())
{ {
if (item.ModelId.Value is > 1600 and < 1651 if (item.ModelId.Id is > 1600 and < 1651
&& _items.ItemService.AwaitedService.TryGetValue(item.ItemId, EquipSlot.Hands, out var gauntlets)) && _items.ItemService.AwaitedService.TryGetValue(item.ItemId, EquipSlot.Hands, out var gauntlets))
_state.ChangeEquip(state, EquipSlot.Hands, gauntlets, 0, StateChanged.Source.Manual); _state.ChangeEquip(state, EquipSlot.Hands, gauntlets, 0, StateChanged.Source.Manual);
if (_items.ItemService.AwaitedService.TryGetValue(item.ItemId, EquipSlot.OffHand, out var offhand)) if (_items.ItemService.AwaitedService.TryGetValue(item.ItemId, EquipSlot.OffHand, out var offhand))

View file

@ -98,7 +98,7 @@ public readonly unsafe struct Model : IEquatable<Model>
Model weapon = AsDrawObject->Object.ChildObject; Model weapon = AsDrawObject->Object.ChildObject;
return !weapon.IsWeapon return !weapon.IsWeapon
? (Null, CharacterWeapon.Empty) ? (Null, CharacterWeapon.Empty)
: (weapon, new CharacterWeapon(weapon.AsWeapon->ModelSetId, weapon.AsWeapon->SecondaryId, weapon.AsWeapon->Variant, : (weapon, new CharacterWeapon(weapon.AsWeapon->ModelSetId, weapon.AsWeapon->SecondaryId, (Variant)weapon.AsWeapon->Variant,
(StainId)weapon.AsWeapon->ModelUnknown)); (StainId)weapon.AsWeapon->ModelUnknown));
} }
@ -112,7 +112,7 @@ public readonly unsafe struct Model : IEquatable<Model>
if (offhand == mainhand || !offhand.IsWeapon) if (offhand == mainhand || !offhand.IsWeapon)
return (Null, CharacterWeapon.Empty); return (Null, CharacterWeapon.Empty);
return (offhand, new CharacterWeapon(offhand.AsWeapon->ModelSetId, offhand.AsWeapon->SecondaryId, offhand.AsWeapon->Variant, return (offhand, new CharacterWeapon(offhand.AsWeapon->ModelSetId, offhand.AsWeapon->SecondaryId, (Variant)offhand.AsWeapon->Variant,
(StainId)offhand.AsWeapon->ModelUnknown)); (StainId)offhand.AsWeapon->ModelUnknown));
} }
@ -124,13 +124,14 @@ public readonly unsafe struct Model : IEquatable<Model>
{ {
case 0: return (Null, Null, CharacterWeapon.Empty, CharacterWeapon.Empty); case 0: return (Null, Null, CharacterWeapon.Empty, CharacterWeapon.Empty);
case 1: case 1:
return (first, Null, new CharacterWeapon(first.AsWeapon->ModelSetId, first.AsWeapon->SecondaryId, first.AsWeapon->Variant, return (first, Null, new CharacterWeapon(first.AsWeapon->ModelSetId, first.AsWeapon->SecondaryId,
(Variant)first.AsWeapon->Variant,
(StainId)first.AsWeapon->ModelUnknown), CharacterWeapon.Empty); (StainId)first.AsWeapon->ModelUnknown), CharacterWeapon.Empty);
default: default:
var (main, off) = DetermineMainhand(first, second); var (main, off) = DetermineMainhand(first, second);
var mainData = new CharacterWeapon(main.AsWeapon->ModelSetId, main.AsWeapon->SecondaryId, main.AsWeapon->Variant, var mainData = new CharacterWeapon(main.AsWeapon->ModelSetId, main.AsWeapon->SecondaryId, (Variant)main.AsWeapon->Variant,
(StainId)main.AsWeapon->ModelUnknown); (StainId)main.AsWeapon->ModelUnknown);
var offData = new CharacterWeapon(off.AsWeapon->ModelSetId, off.AsWeapon->SecondaryId, off.AsWeapon->Variant, var offData = new CharacterWeapon(off.AsWeapon->ModelSetId, off.AsWeapon->SecondaryId, (Variant)off.AsWeapon->Variant,
(StainId)off.AsWeapon->ModelUnknown); (StainId)off.AsWeapon->ModelUnknown);
return (main, off, mainData, offData); return (main, off, mainData, offData);
} }
@ -145,14 +146,14 @@ public readonly unsafe struct Model : IEquatable<Model>
Model main = *((nint*)&actor.AsCharacter->DrawData.MainHand + 1); Model main = *((nint*)&actor.AsCharacter->DrawData.MainHand + 1);
var mainData = CharacterWeapon.Empty; var mainData = CharacterWeapon.Empty;
if (main.IsWeapon) if (main.IsWeapon)
mainData = new CharacterWeapon(main.AsWeapon->ModelSetId, main.AsWeapon->SecondaryId, main.AsWeapon->Variant, mainData = new CharacterWeapon(main.AsWeapon->ModelSetId, main.AsWeapon->SecondaryId, (Variant)main.AsWeapon->Variant,
(StainId)main.AsWeapon->ModelUnknown); (StainId)main.AsWeapon->ModelUnknown);
else else
main = Null; main = Null;
Model off = *((nint*)&actor.AsCharacter->DrawData.OffHand + 1); Model off = *((nint*)&actor.AsCharacter->DrawData.OffHand + 1);
var offData = CharacterWeapon.Empty; var offData = CharacterWeapon.Empty;
if (off.IsWeapon) if (off.IsWeapon)
offData = new CharacterWeapon(off.AsWeapon->ModelSetId, off.AsWeapon->SecondaryId, off.AsWeapon->Variant, offData = new CharacterWeapon(off.AsWeapon->ModelSetId, off.AsWeapon->SecondaryId, (Variant)off.AsWeapon->Variant,
(StainId)off.AsWeapon->ModelUnknown); (StainId)off.AsWeapon->ModelUnknown);
else else
off = Null; off = Null;

View file

@ -40,7 +40,7 @@ public class VisorService : IDisposable
if (oldState == on) if (oldState == on)
return false; return false;
SetupVisorHook(human, human.GetArmor(EquipSlot.Head).Set.Value, on); SetupVisorHook(human, human.GetArmor(EquipSlot.Head).Set.Id, on);
return true; return true;
} }

View file

@ -58,7 +58,7 @@ public unsafe class WeaponService : IDisposable
_loadWeaponHook.Original(drawData, slot, weapon.Value, redrawOnEquality, unk2, skipGameObject, unk4); _loadWeaponHook.Original(drawData, slot, weapon.Value, redrawOnEquality, unk2, skipGameObject, unk4);
if (tmpWeapon.Value != weapon.Value) if (tmpWeapon.Value != weapon.Value)
{ {
if (tmpWeapon.Set.Value == 0) if (tmpWeapon.Set.Id == 0)
tmpWeapon.Stain = 0; tmpWeapon.Stain = 0;
_loadWeaponHook.Original(drawData, slot, tmpWeapon.Value, 1, unk2, 1, unk4); _loadWeaponHook.Original(drawData, slot, tmpWeapon.Value, 1, unk2, 1, unk4);
} }
@ -91,7 +91,7 @@ public unsafe class WeaponService : IDisposable
var mdl = character.Model; var mdl = character.Model;
var (_, _, mh, oh) = mdl.GetWeapons(character); var (_, _, mh, oh) = mdl.GetWeapons(character);
var value = slot == EquipSlot.OffHand ? oh : mh; var value = slot == EquipSlot.OffHand ? oh : mh;
var weapon = value.With(value.Set.Value == 0 ? 0 : stain); var weapon = value.With(value.Set.Id == 0 ? 0 : stain);
LoadWeapon(character, slot, weapon); LoadWeapon(character, slot, weapon);
} }
} }

View file

@ -48,13 +48,13 @@ public class ItemManager : IDisposable
public (bool, CharacterArmor) ResolveRestrictedGear(CharacterArmor armor, EquipSlot slot, Race race, Gender gender) public (bool, CharacterArmor) ResolveRestrictedGear(CharacterArmor armor, EquipSlot slot, Race race, Gender gender)
=> _config.UseRestrictedGearProtection ? RestrictedGear.ResolveRestricted(armor, slot, race, gender) : (false, armor); => _config.UseRestrictedGearProtection ? RestrictedGear.ResolveRestricted(armor, slot, race, gender) : (false, armor);
public static uint NothingId(EquipSlot slot) public static ItemId NothingId(EquipSlot slot)
=> uint.MaxValue - 128 - (uint)slot.ToSlot(); => uint.MaxValue - 128 - (uint)slot.ToSlot();
public static uint SmallclothesId(EquipSlot slot) public static ItemId SmallclothesId(EquipSlot slot)
=> uint.MaxValue - 256 - (uint)slot.ToSlot(); => uint.MaxValue - 256 - (uint)slot.ToSlot();
public static uint NothingId(FullEquipType type) public static ItemId NothingId(FullEquipType type)
=> uint.MaxValue - 384 - (uint)type; => uint.MaxValue - 384 - (uint)type;
public static EquipItem NothingItem(EquipSlot slot) public static EquipItem NothingItem(EquipSlot slot)
@ -66,7 +66,7 @@ public class ItemManager : IDisposable
public static EquipItem SmallClothesItem(EquipSlot slot) public static EquipItem SmallClothesItem(EquipSlot slot)
=> new(SmallClothesNpc, SmallclothesId(slot), 0, SmallClothesNpcModel, 0, 1, slot.ToEquipType()); => new(SmallClothesNpc, SmallclothesId(slot), 0, SmallClothesNpcModel, 0, 1, slot.ToEquipType());
public EquipItem Resolve(EquipSlot slot, uint itemId) public EquipItem Resolve(EquipSlot slot, ItemId itemId)
{ {
slot = slot.ToSlot(); slot = slot.ToSlot();
if (itemId == NothingId(slot)) if (itemId == NothingId(slot))
@ -83,7 +83,7 @@ public class ItemManager : IDisposable
return item; return item;
} }
public EquipItem Resolve(FullEquipType type, uint itemId) public EquipItem Resolve(FullEquipType type, ItemId itemId)
{ {
if (itemId == NothingId(type)) if (itemId == NothingId(type))
return NothingItem(type); return NothingItem(type);
@ -97,13 +97,13 @@ public class ItemManager : IDisposable
return item; return item;
} }
public EquipItem Identify(EquipSlot slot, SetId id, byte variant) public EquipItem Identify(EquipSlot slot, SetId id, Variant variant)
{ {
slot = slot.ToSlot(); slot = slot.ToSlot();
if (slot.ToIndex() == uint.MaxValue) if (slot.ToIndex() == uint.MaxValue)
return new EquipItem($"Invalid ({id.Value}-{variant})", 0, 0, id, 0, variant, 0); return new EquipItem($"Invalid ({id.Id}-{variant})", 0, 0, id, 0, variant, 0);
switch (id.Value) switch (id.Id)
{ {
case 0: return NothingItem(slot); case 0: return NothingItem(slot);
case SmallClothesNpcModel: return SmallClothesItem(slot); case SmallClothesNpcModel: return SmallClothesItem(slot);
@ -125,17 +125,17 @@ public class ItemManager : IDisposable
return NothingItem(offhandType); return NothingItem(offhandType);
} }
public EquipItem Identify(EquipSlot slot, SetId id, WeaponType type, byte variant, FullEquipType mainhandType = FullEquipType.Unknown) public EquipItem Identify(EquipSlot slot, SetId id, WeaponType type, Variant variant, FullEquipType mainhandType = FullEquipType.Unknown)
{ {
if (slot is EquipSlot.OffHand) if (slot is EquipSlot.OffHand)
{ {
var weaponType = mainhandType.ValidOffhand(); var weaponType = mainhandType.ValidOffhand();
if (id.Value == 0) if (id.Id == 0)
return NothingItem(weaponType); return NothingItem(weaponType);
} }
if (slot is not EquipSlot.MainHand and not EquipSlot.OffHand) if (slot is not EquipSlot.MainHand and not EquipSlot.OffHand)
return new EquipItem($"Invalid ({id.Value}-{type.Value}-{variant})", 0, 0, id, type, variant, 0); return new EquipItem($"Invalid ({id.Id}-{type.Id}-{variant})", 0, 0, id, type, variant, 0);
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
@ -145,7 +145,7 @@ public class ItemManager : IDisposable
/// <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>
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public bool IsItemValid(EquipSlot slot, uint itemId, out EquipItem item) public bool IsItemValid(EquipSlot slot, ItemId itemId, out EquipItem item)
{ {
item = Resolve(slot, itemId); item = Resolve(slot, itemId);
return item.Valid; return item.Valid;
@ -156,20 +156,19 @@ 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, ulong itemId, out EquipItem item, bool allowUnknown) public string ValidateItem(EquipSlot slot, CustomItemId 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 (itemId > uint.MaxValue) if (!itemId.IsItem)
{ {
var id = (SetId)(itemId & ushort.MaxValue); var (id, _, variant, _) = itemId.Split;
var variant = (byte)(itemId >> 32);
item = Identify(slot, id, variant); item = Identify(slot, id, variant);
return allowUnknown ? string.Empty : $"The item {itemId} yields an unknown item."; return allowUnknown ? string.Empty : $"The item {itemId} yields an unknown item.";
} }
if (IsItemValid(slot, (uint)itemId, out item)) if (IsItemValid(slot, itemId.Item, out item))
return string.Empty; return string.Empty;
item = NothingItem(slot); item = NothingItem(slot);
@ -179,7 +178,7 @@ public class ItemManager : IDisposable
/// <summary> Returns whether a stain id is a valid stain. </summary> /// <summary> Returns whether a stain id is a valid stain. </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public bool IsStainValid(StainId stain) public bool IsStainValid(StainId stain)
=> stain.Value == 0 || Stains.ContainsKey(stain); => stain.Id == 0 || Stains.ContainsKey(stain);
/// <summary> /// <summary>
/// Check whether a stain id is an existing stain. /// Check whether a stain id is an existing stain.
@ -200,7 +199,7 @@ public class ItemManager : IDisposable
/// <summary> Returns whether an offhand is valid given the required offhand type. </summary> /// <summary> Returns whether an offhand is valid given the required offhand type. </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public bool IsOffhandValid(FullEquipType offType, uint offId, out EquipItem off) public bool IsOffhandValid(FullEquipType offType, ItemId offId, out EquipItem off)
{ {
off = Resolve(offType, offId); off = Resolve(offType, offId);
return offType == FullEquipType.Unknown || off.Valid; return offType == FullEquipType.Unknown || off.Valid;
@ -208,7 +207,7 @@ public class ItemManager : IDisposable
/// <summary> Returns whether an offhand is valid given mainhand. </summary> /// <summary> Returns whether an offhand is valid given mainhand. </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public bool IsOffhandValid(in EquipItem main, uint offId, out EquipItem off) public bool IsOffhandValid(in EquipItem main, ItemId offId, out EquipItem off)
=> IsOffhandValid(main.Type.ValidOffhand(), offId, out off); => IsOffhandValid(main.Type.ValidOffhand(), offId, out off);
/// <summary> /// <summary>
@ -218,7 +217,7 @@ public class ItemManager : IDisposable
/// or the default sword and a nothing offhand. /// or the default sword and a nothing offhand.
/// 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 ValidateWeapons(uint mainId, uint offId, out EquipItem main, out EquipItem off) public string ValidateWeapons(ItemId mainId, ItemId offId, out EquipItem main, out EquipItem off)
{ {
var ret = string.Empty; var ret = string.Empty;
if (!IsItemValid(EquipSlot.MainHand, mainId, out main)) if (!IsItemValid(EquipSlot.MainHand, mainId, out main))

View file

@ -21,7 +21,7 @@ public sealed class TextureService : TextureCache, IDisposable
public (nint, Vector2, bool) GetIcon(EquipItem item) public (nint, Vector2, bool) GetIcon(EquipItem item)
{ {
if (item.IconId != 0 && TryLoadIcon(item.IconId, out var ret)) if (item.IconId.Id != 0 && TryLoadIcon(item.IconId.Id, out var ret))
return (ret.Value.Texture, ret.Value.Dimensions, false); return (ret.Value.Texture, ret.Value.Dimensions, false);
var idx = item.Type.ToSlot().ToIndex(); var idx = item.Type.ToSlot().ToIndex();

View file

@ -189,7 +189,7 @@ public class StateApplier
/// <summary> Apply a weapon to the offhand. </summary> /// <summary> Apply a weapon to the offhand. </summary>
public void ChangeOffhand(ActorData data, EquipItem weapon, StainId stain) public void ChangeOffhand(ActorData data, EquipItem weapon, StainId stain)
{ {
stain = weapon.ModelId.Value == 0 ? 0 : stain; stain = weapon.ModelId.Id == 0 ? 0 : stain;
foreach (var actor in data.Objects.Where(a => a.Model.IsHuman)) foreach (var actor in data.Objects.Where(a => a.Model.IsHuman))
_weapon.LoadWeapon(actor, EquipSlot.OffHand, weapon.Weapon().With(stain)); _weapon.LoadWeapon(actor, EquipSlot.OffHand, weapon.Weapon().With(stain));
} }

View file

@ -260,7 +260,7 @@ public class StateListener : IDisposable
private void OnWeaponLoading(Actor actor, EquipSlot slot, Ref<CharacterWeapon> weapon) private void OnWeaponLoading(Actor actor, EquipSlot slot, Ref<CharacterWeapon> weapon)
{ {
// Fist weapon gauntlet hack. // Fist weapon gauntlet hack.
if (slot is EquipSlot.OffHand && weapon.Value.Variant == 0 && weapon.Value.Set.Value != 0 && _lastFistOffhand.Set.Value != 0) if (slot is EquipSlot.OffHand && weapon.Value.Variant == 0 && weapon.Value.Set.Id != 0 && _lastFistOffhand.Set.Id != 0)
weapon.Value = _lastFistOffhand; weapon.Value = _lastFistOffhand;
if (!actor.Identifier(_actors.AwaitedService, out var identifier) if (!actor.Identifier(_actors.AwaitedService, out var identifier)
@ -297,13 +297,13 @@ public class StateListener : IDisposable
var newWeapon = state.ModelData.Weapon(slot); var newWeapon = state.ModelData.Weapon(slot);
if (baseType is FullEquipType.Unknown || baseType == state.ModelData.Item(slot).Type || _gPose.InGPose && actor.IsGPoseOrCutscene) if (baseType is FullEquipType.Unknown || baseType == state.ModelData.Item(slot).Type || _gPose.InGPose && actor.IsGPoseOrCutscene)
actorWeapon = newWeapon; actorWeapon = newWeapon;
else if (actorWeapon.Set.Value != 0) else if (actorWeapon.Set.Id != 0)
actorWeapon = actorWeapon.With(newWeapon.Stain); actorWeapon = actorWeapon.With(newWeapon.Stain);
} }
// Fist Weapon Offhand hack. // Fist Weapon Offhand hack.
if (slot is EquipSlot.MainHand && weapon.Value.Set.Value is > 1600 and < 1651) if (slot is EquipSlot.MainHand && weapon.Value.Set.Id is > 1600 and < 1651)
_lastFistOffhand = new CharacterWeapon((SetId)(weapon.Value.Set.Value + 50), weapon.Value.Type, weapon.Value.Variant, _lastFistOffhand = new CharacterWeapon((SetId)(weapon.Value.Set.Id + 50), weapon.Value.Type, weapon.Value.Variant,
weapon.Value.Stain); weapon.Value.Stain);
} }
@ -316,7 +316,7 @@ public class StateListener : IDisposable
return false; return false;
var offhand = actor.GetOffhand(); var offhand = actor.GetOffhand();
return offhand.Variant == 0 && offhand.Set.Value != 0 && armor.Set.Value == offhand.Set.Value; return offhand.Variant == 0 && offhand.Set.Id != 0 && armor.Set.Id == offhand.Set.Id;
} }
var actorArmor = actor.GetArmor(slot); var actorArmor = actor.GetArmor(slot);
@ -333,7 +333,7 @@ public class StateListener : IDisposable
change = UpdateState.Change; change = UpdateState.Change;
} }
if (baseData.Set.Value != armor.Set.Value || baseData.Variant != armor.Variant) if (baseData.Set.Id != armor.Set.Id || baseData.Variant != armor.Variant)
{ {
var item = _items.Identify(slot, armor.Set, armor.Variant); var item = _items.Identify(slot, armor.Set, armor.Variant);
state.BaseData.SetItem(slot, item); state.BaseData.SetItem(slot, item);
@ -387,9 +387,9 @@ public class StateListener : IDisposable
change = UpdateState.Change; change = UpdateState.Change;
} }
if (baseData.Set.Value != weapon.Set.Value || baseData.Type.Value != weapon.Type.Value || baseData.Variant != weapon.Variant) if (baseData.Set.Id != weapon.Set.Id || baseData.Type.Id != weapon.Type.Id || baseData.Variant != weapon.Variant)
{ {
var item = _items.Identify(slot, weapon.Set, weapon.Type, (byte)weapon.Variant, var item = _items.Identify(slot, weapon.Set, weapon.Type, weapon.Variant,
slot is EquipSlot.OffHand ? state.BaseData.Item(EquipSlot.MainHand).Type : FullEquipType.Unknown); slot is EquipSlot.OffHand ? state.BaseData.Item(EquipSlot.MainHand).Type : FullEquipType.Unknown);
state.BaseData.SetItem(slot, item); state.BaseData.SetItem(slot, item);
change = UpdateState.Change; change = UpdateState.Change;

View file

@ -174,8 +174,8 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
} }
// Set the weapons regardless of source. // Set the weapons regardless of source.
var mainItem = _items.Identify(EquipSlot.MainHand, main.Set, main.Type, (byte)main.Variant); var mainItem = _items.Identify(EquipSlot.MainHand, main.Set, main.Type, main.Variant);
var offItem = _items.Identify(EquipSlot.OffHand, off.Set, off.Type, (byte)off.Variant, mainItem.Type); var offItem = _items.Identify(EquipSlot.OffHand, off.Set, off.Type, off.Variant, mainItem.Type);
ret.SetItem(EquipSlot.MainHand, mainItem); ret.SetItem(EquipSlot.MainHand, mainItem);
ret.SetStain(EquipSlot.MainHand, main.Stain); ret.SetStain(EquipSlot.MainHand, main.Stain);
ret.SetItem(EquipSlot.OffHand, offItem); ret.SetItem(EquipSlot.OffHand, offItem);
@ -194,11 +194,11 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
/// <summary> This is hardcoded in the game. </summary> /// <summary> This is hardcoded in the game. </summary>
private void FistWeaponHack(ref DesignData ret, ref CharacterWeapon mainhand, ref CharacterWeapon offhand) private void FistWeaponHack(ref DesignData ret, ref CharacterWeapon mainhand, ref CharacterWeapon offhand)
{ {
if (mainhand.Set.Value is < 1601 or >= 1651) if (mainhand.Set.Id is < 1601 or >= 1651)
return; return;
var gauntlets = _items.Identify(EquipSlot.Hands, offhand.Set, 0, (byte)offhand.Variant); var gauntlets = _items.Identify(EquipSlot.Hands, offhand.Set, 0, offhand.Variant);
offhand.Set = (SetId)(mainhand.Set.Value + 50); offhand.Set = (SetId)(mainhand.Set.Id + 50);
offhand.Variant = mainhand.Variant; offhand.Variant = mainhand.Variant;
offhand.Type = mainhand.Type; offhand.Type = mainhand.Type;
ret.SetItem(EquipSlot.Hands, gauntlets); ret.SetItem(EquipSlot.Hands, gauntlets);
@ -275,7 +275,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, item.Type != (slot is EquipSlot.MainHand ? state.BaseData.MainhandType : state.BaseData.OffhandType)); : _applier.ChangeWeapon(state, slot, source is StateChanged.Source.Manual or StateChanged.Source.Ipc, item.Type != (slot is EquipSlot.MainHand ? state.BaseData.MainhandType : state.BaseData.OffhandType));
Glamourer.Log.Verbose( Glamourer.Log.Verbose(
$"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")}.]"); $"Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.ItemId}) to {item.Name} ({item.ItemId}) and its stain from {oldStain.Id} to {stain.Id}. [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));
} }
@ -289,7 +289,7 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
var actors = _applier.ChangeStain(state, slot, source is StateChanged.Source.Manual or StateChanged.Source.Ipc); var actors = _applier.ChangeStain(state, slot, source is StateChanged.Source.Manual or StateChanged.Source.Ipc);
Glamourer.Log.Verbose( Glamourer.Log.Verbose(
$"Set {slot.ToName()} stain in state {state.Identifier.Incognito(null)} from {old.Value} to {stain.Value}. [Affecting {actors.ToLazyString("nothing")}.]"); $"Set {slot.ToName()} stain in state {state.Identifier.Incognito(null)} from {old.Id} to {stain.Id}. [Affecting {actors.ToLazyString("nothing")}.]");
_event.Invoke(StateChanged.Type.Stain, source, state, actors, (old, stain, slot)); _event.Invoke(StateChanged.Type.Stain, source, state, actors, (old, stain, slot));
} }

View file

@ -1,4 +1,5 @@
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@ -12,14 +13,12 @@ using Glamourer.Events;
using Glamourer.Services; using Glamourer.Services;
using Lumina.Excel.GeneratedSheets; using Lumina.Excel.GeneratedSheets;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using static OtterGui.Raii.ImRaii; using Penumbra.GameData.Structs;
using static Penumbra.GameData.Files.ShpkFile;
using Cabinet = Lumina.Excel.GeneratedSheets.Cabinet; using Cabinet = Lumina.Excel.GeneratedSheets.Cabinet;
using Item = Lumina.Excel.GeneratedSheets.Item;
namespace Glamourer.Unlocks; namespace Glamourer.Unlocks;
public class ItemUnlockManager : ISavable, IDisposable public class ItemUnlockManager : ISavable, IDisposable, IReadOnlyDictionary<ItemId, long>
{ {
private readonly SaveService _saveService; private readonly SaveService _saveService;
private readonly ItemManager _items; private readonly ItemManager _items;
@ -46,10 +45,7 @@ public class ItemUnlockManager : ISavable, IDisposable
Cabinet = 0x08, Cabinet = 0x08,
} }
public readonly IReadOnlyDictionary<uint, UnlockRequirements> Unlockable; public readonly IReadOnlyDictionary<ItemId, UnlockRequirements> Unlockable;
public IReadOnlyDictionary<uint, long> Unlocked
=> _unlocked;
public ItemUnlockManager(SaveService saveService, ItemManager items, ClientState clientState, DataManager gameData, Framework framework, public ItemUnlockManager(SaveService saveService, ItemManager items, ClientState clientState, DataManager gameData, Framework framework,
ObjectUnlocked @event, IdentifierService identifier) ObjectUnlocked @event, IdentifierService identifier)
@ -104,18 +100,19 @@ public class ItemUnlockManager : ISavable, IDisposable
InventoryType.RetainerMarket, InventoryType.RetainerMarket,
}; };
private bool AddItem(uint itemId, long time) private bool AddItem(ItemId itemId, long time)
{ {
itemId = HandleHq(itemId); itemId = itemId.StripModifiers;
if (!_items.ItemService.AwaitedService.TryGetValue(itemId, EquipSlot.MainHand, out var equip) || !_unlocked.TryAdd(equip.ItemId, time)) if (!_items.ItemService.AwaitedService.TryGetValue(itemId, EquipSlot.MainHand, out var equip)
|| !_unlocked.TryAdd(equip.ItemId.Id, time))
return false; return false;
_event.Invoke(ObjectUnlocked.Type.Item, equip.ItemId, DateTimeOffset.FromUnixTimeMilliseconds(time)); _event.Invoke(ObjectUnlocked.Type.Item, equip.ItemId.Id, DateTimeOffset.FromUnixTimeMilliseconds(time));
var ident = _identifier.AwaitedService.Identify(equip.ModelId, equip.WeaponType, equip.Variant, equip.Type.ToSlot()); var ident = _identifier.AwaitedService.Identify(equip.ModelId, equip.WeaponType, equip.Variant, equip.Type.ToSlot());
foreach (var item in ident) foreach (var item in ident)
{ {
if (_unlocked.TryAdd(item.ItemId, time)) if (_unlocked.TryAdd(item.ItemId.Id, time))
_event.Invoke(ObjectUnlocked.Type.Item, item.ItemId, DateTimeOffset.FromUnixTimeMilliseconds(time)); _event.Invoke(ObjectUnlocked.Type.Item, item.ItemId.Id, DateTimeOffset.FromUnixTimeMilliseconds(time));
} }
return true; return true;
@ -201,27 +198,28 @@ public class ItemUnlockManager : ISavable, IDisposable
Save(); Save();
} }
public bool IsUnlocked(ulong itemId, out DateTimeOffset time) public bool IsUnlocked(CustomItemId itemId, out DateTimeOffset time)
{ {
// Pseudo items are always unlocked. // Pseudo items are always unlocked.
if (itemId >= _items.ItemSheet.RowCount) if (itemId.Id >= _items.ItemSheet.RowCount)
{ {
time = DateTimeOffset.MinValue; time = DateTimeOffset.MinValue;
return true; return true;
} }
if (_unlocked.TryGetValue((uint) itemId, out var t)) var id = itemId.Item.Id;
if (_unlocked.TryGetValue(id, out var t))
{ {
time = DateTimeOffset.FromUnixTimeMilliseconds(t); time = DateTimeOffset.FromUnixTimeMilliseconds(t);
return true; return true;
} }
if (IsGameUnlocked((uint) itemId)) if (IsGameUnlocked(id))
{ {
time = DateTimeOffset.UtcNow; time = DateTimeOffset.UtcNow;
if (_unlocked.TryAdd((uint) itemId, time.ToUnixTimeMilliseconds())) if (_unlocked.TryAdd(id, time.ToUnixTimeMilliseconds()))
{ {
_event.Invoke(ObjectUnlocked.Type.Item, (uint) itemId, time); _event.Invoke(ObjectUnlocked.Type.Item, id, time);
Save(); Save();
} }
@ -232,7 +230,7 @@ public class ItemUnlockManager : ISavable, IDisposable
return false; return false;
} }
public unsafe bool IsGameUnlocked(uint itemId) public unsafe bool IsGameUnlocked(ItemId itemId)
{ {
if (Unlockable.TryGetValue(itemId, out var req)) if (Unlockable.TryGetValue(itemId, out var req))
return req.IsUnlocked(this); return req.IsUnlocked(this);
@ -253,15 +251,14 @@ public class ItemUnlockManager : ISavable, IDisposable
var changes = false; var changes = false;
foreach (var (itemId, unlock) in Unlockable) foreach (var (itemId, unlock) in Unlockable)
{ {
if (unlock.IsUnlocked(this) && _unlocked.TryAdd(itemId, time)) if (unlock.IsUnlocked(this) && _unlocked.TryAdd(itemId.Id, time))
{ {
_event.Invoke(ObjectUnlocked.Type.Item, itemId, DateTimeOffset.FromUnixTimeMilliseconds(time)); _event.Invoke(ObjectUnlocked.Type.Item, itemId.Id, DateTimeOffset.FromUnixTimeMilliseconds(time));
changes = true; changes = true;
} }
} }
// TODO inventories // TODO inventories
if (changes) if (changes)
Save(); Save();
} }
@ -273,7 +270,7 @@ public class ItemUnlockManager : ISavable, IDisposable
=> _saveService.DelaySave(this, TimeSpan.FromSeconds(10)); => _saveService.DelaySave(this, TimeSpan.FromSeconds(10));
public void Save(StreamWriter writer) public void Save(StreamWriter writer)
=> UnlockDictionaryHelpers.Save(writer, Unlocked); => UnlockDictionaryHelpers.Save(writer, _unlocked);
private void Load() private void Load()
{ {
@ -285,9 +282,9 @@ public class ItemUnlockManager : ISavable, IDisposable
private void OnLogin(object? _, EventArgs _2) private void OnLogin(object? _, EventArgs _2)
=> Scan(); => Scan();
private static Dictionary<uint, UnlockRequirements> CreateUnlockData(DataManager gameData, ItemManager items) private static Dictionary<ItemId, UnlockRequirements> CreateUnlockData(DataManager gameData, ItemManager items)
{ {
var ret = new Dictionary<uint, UnlockRequirements>(); var ret = new Dictionary<ItemId, UnlockRequirements>();
var cabinet = gameData.GetExcelSheet<Cabinet>()!; var cabinet = gameData.GetExcelSheet<Cabinet>()!;
foreach (var row in cabinet) foreach (var row in cabinet)
{ {
@ -338,17 +335,33 @@ public class ItemUnlockManager : ISavable, IDisposable
var ident = _identifier.AwaitedService.Identify(equip.ModelId, equip.WeaponType, equip.Variant, equip.Type.ToSlot()); var ident = _identifier.AwaitedService.Identify(equip.ModelId, equip.WeaponType, equip.Variant, equip.Type.ToSlot());
foreach (var item2 in ident) foreach (var item2 in ident)
{ {
if (_unlocked.TryAdd(item2.ItemId, time)) if (_unlocked.TryAdd(item2.ItemId.Id, time))
_event.Invoke(ObjectUnlocked.Type.Item, item2.ItemId, DateTimeOffset.FromUnixTimeMilliseconds(time)); _event.Invoke(ObjectUnlocked.Type.Item, item2.ItemId.Id, DateTimeOffset.FromUnixTimeMilliseconds(time));
} }
} }
} }
private uint HandleHq(uint itemId) public IEnumerator<KeyValuePair<ItemId, long>> GetEnumerator()
=> itemId switch => _unlocked.Select(kvp => new KeyValuePair<ItemId, long>(kvp.Key, kvp.Value)).GetEnumerator();
{
> 1000000 => itemId - 1000000, IEnumerator IEnumerable.GetEnumerator()
> 500000 => itemId - 500000, => GetEnumerator();
_ => itemId,
}; public int Count
=> _unlocked.Count;
public bool ContainsKey(ItemId key)
=> _unlocked.ContainsKey(key.Id);
public bool TryGetValue(ItemId key, out long value)
=> _unlocked.TryGetValue(key.Id, out value);
public long this[ItemId key]
=> _unlocked[key.Id];
public IEnumerable<ItemId> Keys
=> _unlocked.Keys.Select(i => (ItemId)i);
public IEnumerable<long> Values
=> _unlocked.Values;
} }

@ -1 +1 @@
Subproject commit e3d26f16234a4295bf3c7802d87ce43293c6ffe0 Subproject commit 03b6b17fee66488fff7f598e444fa99454098767

@ -1 +1 @@
Subproject commit 5dd2b440e69b1725fa214b005b7179f2414a4053 Subproject commit dee0ab36fac204b9da12d45b66b52e8cfb1fdc08