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.EventNpc => manager.Data.ENpcs,
_ => throw new NotImplementedException(),
_ => new Dictionary<uint, string>(),
};
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();
}

View file

@ -168,11 +168,11 @@ public class DesignBase
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()
{
["ItemId"] = id,
["Stain"] = stain.Value,
["ItemId"] = id.Id,
["Stain"] = stain.Id,
["Apply"] = apply,
["ApplyStain"] = applyStain,
};
@ -267,9 +267,9 @@ public class DesignBase
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 apply = item?["Apply"]?.ToObject<bool>() ?? false;
var applyStain = item?["ApplyStain"]?.ToObject<bool>() ?? false;
@ -302,7 +302,7 @@ public class DesignBase
if (id == ItemManager.NothingId(EquipSlot.OffHand))
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(stainOff, out stainOff, allowUnknown));
design.DesignData.SetItem(EquipSlot.MainHand, main);

View file

@ -113,42 +113,42 @@ public class DesignBase64Migration
var mdl = eq[idx];
var item = items.Identify(slot, mdl.Set, mdl.Variant);
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.SetStain(slot, mdl.Stain);
}
var main = cur[0].Set.Value == 0
var main = cur[0].Set.Id == 0
? 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)
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.SetStain(EquipSlot.MainHand, cur[0].Stain);
EquipItem off;
// 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);
var gauntlet = items.Identify(EquipSlot.Hands, cur[1].Set, (byte)cur[1].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, (Variant)cur[1].Type.Id);
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.SetStain(EquipSlot.Hands, cur[0].Stain);
}
else
{
off = cur[0].Set.Value == 0
off = cur[0].Set.Id == 0
? 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)
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.SetStain(EquipSlot.OffHand, cur[1].Stain);

View file

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

View file

@ -398,7 +398,7 @@ public class DesignManager
design.LastEdit = DateTimeOffset.UtcNow;
_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));
}

View file

@ -43,7 +43,7 @@ public class EquipmentDrawer
_stainData = items.Stains;
_stainCombo = new FilterComboColors(DefaultWidth - 20,
_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);
foreach (var type in Enum.GetValues<FullEquipType>())
{
@ -118,7 +118,7 @@ public class EquipmentDrawer
bool allWeapons,
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;
rMainhand = cMainhand;
@ -239,7 +239,7 @@ public class EquipmentDrawer
if (change)
armor = combo.CurrentSelection;
if (!locked && armor.ModelId.Value != 0)
if (!locked && armor.ModelId.Id != 0)
{
ImGuiUtil.HoverTooltip("Right-click to clear.");
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>
private bool DrawArmorArtisan(EquipSlot slot, EquipItem current, out EquipItem armor)
{
int setId = current.ModelId.Value;
int variant = current.Variant;
int setId = current.ModelId.Id;
int variant = current.Variant.Id;
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)
if (newSetId.Id != current.ModelId.Id)
{
armor = _items.Identify(slot, newSetId, current.Variant);
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>
private bool DrawStainArtisan(EquipSlot slot, StainId current, out StainId stain)
{
int stainId = current.Value;
int stainId = current.Id;
ImGui.SetNextItemWidth(40 * ImGuiHelpers.GlobalScale);
if (ImGui.InputInt("##stain", ref stainId, 0, 0))
{

View file

@ -15,16 +15,13 @@ namespace Glamourer.Gui.Equipment;
public sealed class ItemCombo : FilterComboCache<EquipItem>
{
private readonly TextureService _textures;
public readonly string Label;
private uint _currentItem;
private ItemId _currentItem;
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))
{
_textures = textures;
Label = GetLabel(gameData, slot);
_currentItem = ItemManager.NothingId(slot);
SearchByParts = true;
@ -47,7 +44,7 @@ public sealed class ItemCombo : FilterComboCache<EquipItem>
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;
_currentItem = previewIdx;
@ -64,12 +61,12 @@ public sealed class ItemCombo : FilterComboCache<EquipItem>
var ret = ImGui.Selectable(name, selected);
ImGui.SameLine();
using var color = ImRaii.PushColor(ImGuiCol.Text, 0xFF808080);
ImGuiUtil.RightAlign($"({obj.ModelId.Value}-{obj.Variant})");
ImGuiUtil.RightAlign($"({obj.ModelString})");
return ret;
}
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)
=> obj.Name;

View file

@ -15,7 +15,7 @@ namespace Glamourer.Gui.Equipment;
public sealed class WeaponCombo : FilterComboCache<EquipItem>
{
public readonly string Label;
private uint _currentItemId;
private ItemId _currentItemId;
private float _innerWidth;
public WeaponCombo(ItemManager items, FullEquipType type)
@ -45,7 +45,7 @@ public sealed class WeaponCombo : FilterComboCache<EquipItem>
protected override float GetFilterWidth()
=> _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;
_innerWidth = innerWidth;
@ -59,12 +59,12 @@ public sealed class WeaponCombo : FilterComboCache<EquipItem>
var ret = ImGui.Selectable(name, selected);
ImGui.SameLine();
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;
}
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)
=> obj.Name;

View file

@ -173,7 +173,7 @@ public class PenumbraChangedItemTooltip : IDisposable
private bool CanApplyWeapon(EquipSlot slot, EquipItem item)
{
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)
return item.Type == mainItem.Type;

View file

@ -78,7 +78,7 @@ public sealed class HumanNpcCombo : FilterComboCache<(string Name, ObjectKind Ki
{
case ObjectKind.BattleNpc:
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;
case ObjectKind.EventNpc:
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)
.Select(g => (g.Key.Name, g.Key.Kind, g.Select(g => g.Id).ToArray())).ToList();
return ret.GroupBy(t => (t.Name, t.Kind))
.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();

View file

@ -640,16 +640,17 @@ public unsafe class DebugTab : ITab
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);
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);
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()
@ -672,7 +673,7 @@ public unsafe class DebugTab : ITab
foreach (var slot in EquipSlotExtensions.EqdpSlots)
{
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)
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()})",
ImGuiTreeNodeFlags.Leaf).Dispose();
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())})"))
.OrderBy(p => p.Item1));
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())})"))
.OrderBy(p => p.Item1));
foreach (var type in Enum.GetValues<FullEquipType>().Skip(1))
{
DrawNameTable(type.ToName(), ref _itemFilter,
_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);
ImGui.TableNextRow();
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 =>
{
ImGuiUtil.DrawTableColumn(p.Key.Value.ToString("D3"));
ImGuiUtil.DrawTableColumn(p.Key.Id.ToString("D3"));
ImGui.TableNextColumn();
ImGui.GetWindowDrawList().AddRectFilled(ImGui.GetCursorScreenPos(),
ImGui.GetCursorScreenPos() + new Vector2(ImGui.GetTextLineHeight()),
@ -1038,9 +1039,9 @@ public unsafe class DebugTab : ITab
try
{
_clipboardText = ImGui.GetClipboardText();
_clipboardData = Convert.FromBase64String(_clipboardText);
_version = _clipboardData[0];
_clipboardText = ImGui.GetClipboardText();
_clipboardData = Convert.FromBase64String(_clipboardText);
_version = _clipboardData[0];
if (_version == 5)
_clipboardData = _clipboardData[DesignBase64Migration.Base64SizeV4..];
_version = _clipboardData.Decompress(out _dataUncompressed);
@ -1116,7 +1117,7 @@ public unsafe class DebugTab : ITab
static string ItemString(in DesignData data, EquipSlot 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]);
@ -1137,8 +1138,8 @@ public unsafe class DebugTab : ITab
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]);
ImGuiUtil.DrawTableColumn(state.BaseData.Stain(slot).Value.ToString());
ImGuiUtil.DrawTableColumn(state.ModelData.Stain(slot).Value.ToString());
ImGuiUtil.DrawTableColumn(state.BaseData.Stain(slot).Id.ToString());
ImGuiUtil.DrawTableColumn(state.ModelData.Stain(slot).Id.ToString());
ImGuiUtil.DrawTableColumn(state[slot, true].ToString());
}
@ -1453,7 +1454,7 @@ public unsafe class DebugTab : ITab
ImGui.TableNextColumn();
var skips = ImGuiClip.GetNecessarySkips(ImGui.GetTextLineHeightWithSpacing());
ImGui.TableNextRow();
var remainder = ImGuiClip.ClippedDraw(_itemUnlocks.Unlocked, skips, t =>
var remainder = ImGuiClip.ClippedDraw(_itemUnlocks, skips, t =>
{
ImGuiUtil.DrawTableColumn(t.Key.ToString());
if (_items.ItemService.AwaitedService.TryGetValue(t.Key, EquipSlot.MainHand, out var equip))
@ -1474,7 +1475,7 @@ public unsafe class DebugTab : ITab
? "Always"
: time.LocalDateTime.ToString("g")
: "Never");
}, _itemUnlocks.Unlocked.Count);
}, _itemUnlocks.Count);
ImGuiClip.DrawEndDummy(remainder, ImGui.GetTextLineHeight());
}

View file

@ -154,7 +154,7 @@ public class UnlockOverview
void DrawItem(EquipItem item)
{
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)
return;

View file

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

View file

@ -27,7 +27,7 @@ public static class UiHelpers
{
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);
if (empty)
{

View file

@ -119,7 +119,7 @@ public class ContextMenuService : IDisposable
_state.ChangeEquip(state, slot, item, 0, StateChanged.Source.Manual);
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))
_state.ChangeEquip(state, EquipSlot.Hands, gauntlets, 0, StateChanged.Source.Manual);
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);
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))
_state.ChangeEquip(state, EquipSlot.Hands, gauntlets, 0, StateChanged.Source.Manual);
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;
return !weapon.IsWeapon
? (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));
}
@ -112,7 +112,7 @@ public readonly unsafe struct Model : IEquatable<Model>
if (offhand == mainhand || !offhand.IsWeapon)
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));
}
@ -124,13 +124,14 @@ public readonly unsafe struct Model : IEquatable<Model>
{
case 0: return (Null, Null, CharacterWeapon.Empty, CharacterWeapon.Empty);
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);
default:
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);
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);
return (main, off, mainData, offData);
}
@ -145,14 +146,14 @@ public readonly unsafe struct Model : IEquatable<Model>
Model main = *((nint*)&actor.AsCharacter->DrawData.MainHand + 1);
var mainData = CharacterWeapon.Empty;
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);
else
main = Null;
Model off = *((nint*)&actor.AsCharacter->DrawData.OffHand + 1);
var offData = CharacterWeapon.Empty;
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);
else
off = Null;

View file

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

View file

@ -58,7 +58,7 @@ public unsafe class WeaponService : IDisposable
_loadWeaponHook.Original(drawData, slot, weapon.Value, redrawOnEquality, unk2, skipGameObject, unk4);
if (tmpWeapon.Value != weapon.Value)
{
if (tmpWeapon.Set.Value == 0)
if (tmpWeapon.Set.Id == 0)
tmpWeapon.Stain = 0;
_loadWeaponHook.Original(drawData, slot, tmpWeapon.Value, 1, unk2, 1, unk4);
}
@ -91,7 +91,7 @@ public unsafe class WeaponService : IDisposable
var mdl = character.Model;
var (_, _, mh, oh) = mdl.GetWeapons(character);
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);
}
}

View file

@ -48,13 +48,13 @@ public class ItemManager : IDisposable
public (bool, CharacterArmor) ResolveRestrictedGear(CharacterArmor armor, EquipSlot slot, Race race, Gender gender)
=> _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();
public static uint SmallclothesId(EquipSlot slot)
public static ItemId SmallclothesId(EquipSlot slot)
=> uint.MaxValue - 256 - (uint)slot.ToSlot();
public static uint NothingId(FullEquipType type)
public static ItemId NothingId(FullEquipType type)
=> uint.MaxValue - 384 - (uint)type;
public static EquipItem NothingItem(EquipSlot slot)
@ -66,7 +66,7 @@ public class ItemManager : IDisposable
public static EquipItem SmallClothesItem(EquipSlot slot)
=> 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();
if (itemId == NothingId(slot))
@ -83,7 +83,7 @@ public class ItemManager : IDisposable
return item;
}
public EquipItem Resolve(FullEquipType type, uint itemId)
public EquipItem Resolve(FullEquipType type, ItemId itemId)
{
if (itemId == NothingId(type))
return NothingItem(type);
@ -97,13 +97,13 @@ public class ItemManager : IDisposable
return item;
}
public EquipItem Identify(EquipSlot slot, SetId id, byte variant)
public EquipItem Identify(EquipSlot slot, SetId id, Variant variant)
{
slot = slot.ToSlot();
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 SmallClothesNpcModel: return SmallClothesItem(slot);
@ -125,17 +125,17 @@ public class ItemManager : IDisposable
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)
{
var weaponType = mainhandType.ValidOffhand();
if (id.Value == 0)
if (id.Id == 0)
return NothingItem(weaponType);
}
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();
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>
[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);
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 return value is an empty string if there was no problem and a warning otherwise.
/// </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)
throw new Exception("Internal Error: Used armor functionality for weapons.");
if (itemId > uint.MaxValue)
if (!itemId.IsItem)
{
var id = (SetId)(itemId & ushort.MaxValue);
var variant = (byte)(itemId >> 32);
var (id, _, variant, _) = itemId.Split;
item = Identify(slot, id, variant);
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;
item = NothingItem(slot);
@ -179,7 +178,7 @@ public class ItemManager : IDisposable
/// <summary> Returns whether a stain id is a valid stain. </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public bool IsStainValid(StainId stain)
=> stain.Value == 0 || Stains.ContainsKey(stain);
=> stain.Id == 0 || Stains.ContainsKey(stain);
/// <summary>
/// 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>
[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);
return offType == FullEquipType.Unknown || off.Valid;
@ -208,7 +207,7 @@ public class ItemManager : IDisposable
/// <summary> Returns whether an offhand is valid given mainhand. </summary>
[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);
/// <summary>
@ -218,7 +217,7 @@ public class ItemManager : IDisposable
/// or the default sword and a nothing offhand.
/// The return value is an empty string if there was no problem and a warning otherwise.
/// </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;
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)
{
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);
var idx = item.Type.ToSlot().ToIndex();

View file

@ -189,7 +189,7 @@ public class StateApplier
/// <summary> Apply a weapon to the offhand. </summary>
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))
_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)
{
// 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;
if (!actor.Identifier(_actors.AwaitedService, out var identifier)
@ -297,13 +297,13 @@ public class StateListener : IDisposable
var newWeapon = state.ModelData.Weapon(slot);
if (baseType is FullEquipType.Unknown || baseType == state.ModelData.Item(slot).Type || _gPose.InGPose && actor.IsGPoseOrCutscene)
actorWeapon = newWeapon;
else if (actorWeapon.Set.Value != 0)
else if (actorWeapon.Set.Id != 0)
actorWeapon = actorWeapon.With(newWeapon.Stain);
}
// Fist Weapon Offhand hack.
if (slot is EquipSlot.MainHand && weapon.Value.Set.Value is > 1600 and < 1651)
_lastFistOffhand = new CharacterWeapon((SetId)(weapon.Value.Set.Value + 50), weapon.Value.Type, weapon.Value.Variant,
if (slot is EquipSlot.MainHand && weapon.Value.Set.Id is > 1600 and < 1651)
_lastFistOffhand = new CharacterWeapon((SetId)(weapon.Value.Set.Id + 50), weapon.Value.Type, weapon.Value.Variant,
weapon.Value.Stain);
}
@ -316,7 +316,7 @@ public class StateListener : IDisposable
return false;
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);
@ -333,7 +333,7 @@ public class StateListener : IDisposable
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);
state.BaseData.SetItem(slot, item);
@ -387,9 +387,9 @@ public class StateListener : IDisposable
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);
state.BaseData.SetItem(slot, item);
change = UpdateState.Change;

View file

@ -174,8 +174,8 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
}
// Set the weapons regardless of source.
var mainItem = _items.Identify(EquipSlot.MainHand, main.Set, main.Type, (byte)main.Variant);
var offItem = _items.Identify(EquipSlot.OffHand, off.Set, off.Type, (byte)off.Variant, mainItem.Type);
var mainItem = _items.Identify(EquipSlot.MainHand, main.Set, main.Type, main.Variant);
var offItem = _items.Identify(EquipSlot.OffHand, off.Set, off.Type, off.Variant, mainItem.Type);
ret.SetItem(EquipSlot.MainHand, mainItem);
ret.SetStain(EquipSlot.MainHand, main.Stain);
ret.SetItem(EquipSlot.OffHand, offItem);
@ -194,11 +194,11 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
/// <summary> This is hardcoded in the game. </summary>
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;
var gauntlets = _items.Identify(EquipSlot.Hands, offhand.Set, 0, (byte)offhand.Variant);
offhand.Set = (SetId)(mainhand.Set.Value + 50);
var gauntlets = _items.Identify(EquipSlot.Hands, offhand.Set, 0, offhand.Variant);
offhand.Set = (SetId)(mainhand.Set.Id + 50);
offhand.Variant = mainhand.Variant;
offhand.Type = mainhand.Type;
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.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(
$"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(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);
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));
}

View file

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