mirror of
https://github.com/Ottermandias/Glamourer.git
synced 2025-12-12 10:17:23 +01:00
.
This commit is contained in:
parent
60443f6a53
commit
5c003d8cd4
14 changed files with 566 additions and 198 deletions
|
|
@ -3,37 +3,39 @@ using Dalamud.Data;
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
|
|
||||||
namespace Glamourer.Customization
|
namespace Glamourer.Customization;
|
||||||
|
|
||||||
|
public class CustomizationManager : ICustomizationManager
|
||||||
{
|
{
|
||||||
public class CustomizationManager : ICustomizationManager
|
private static CustomizationOptions? _options;
|
||||||
|
|
||||||
|
private CustomizationManager()
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public static ICustomizationManager Create(DalamudPluginInterface pi, DataManager gameData)
|
||||||
{
|
{
|
||||||
private static CustomizationOptions? _options;
|
_options ??= new CustomizationOptions(pi, gameData);
|
||||||
|
return new CustomizationManager();
|
||||||
private CustomizationManager()
|
|
||||||
{ }
|
|
||||||
|
|
||||||
public static ICustomizationManager Create(DalamudPluginInterface pi, DataManager gameData)
|
|
||||||
{
|
|
||||||
_options ??= new CustomizationOptions(pi, gameData);
|
|
||||||
return new CustomizationManager();
|
|
||||||
}
|
|
||||||
|
|
||||||
public IReadOnlyList<Race> Races
|
|
||||||
=> CustomizationOptions.Races;
|
|
||||||
|
|
||||||
public IReadOnlyList<SubRace> Clans
|
|
||||||
=> CustomizationOptions.Clans;
|
|
||||||
|
|
||||||
public IReadOnlyList<Gender> Genders
|
|
||||||
=> CustomizationOptions.Genders;
|
|
||||||
|
|
||||||
public CustomizationSet GetList(SubRace clan, Gender gender)
|
|
||||||
=> _options!.GetList(clan, gender);
|
|
||||||
|
|
||||||
public ImGuiScene.TextureWrap GetIcon(uint iconId)
|
|
||||||
=> _options!.GetIcon(iconId);
|
|
||||||
|
|
||||||
public string GetName(CustomName name)
|
|
||||||
=> _options!.GetName(name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IReadOnlyList<Race> Races
|
||||||
|
=> CustomizationOptions.Races;
|
||||||
|
|
||||||
|
public IReadOnlyList<SubRace> Clans
|
||||||
|
=> CustomizationOptions.Clans;
|
||||||
|
|
||||||
|
public IReadOnlyList<Gender> Genders
|
||||||
|
=> CustomizationOptions.Genders;
|
||||||
|
|
||||||
|
public CustomizationSet GetList(SubRace clan, Gender gender)
|
||||||
|
=> _options!.GetList(clan, gender);
|
||||||
|
|
||||||
|
public ImGuiScene.TextureWrap GetIcon(uint iconId)
|
||||||
|
=> _options!.GetIcon(iconId);
|
||||||
|
|
||||||
|
public void RemoveIcon(uint iconId)
|
||||||
|
=> _options!.RemoveIcon(iconId);
|
||||||
|
|
||||||
|
public string GetName(CustomName name)
|
||||||
|
=> _options!.GetName(name);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using Dalamud;
|
using Dalamud;
|
||||||
using Dalamud.Data;
|
using Dalamud.Data;
|
||||||
using Dalamud.Logging;
|
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
using Lumina.Excel;
|
using Lumina.Excel;
|
||||||
|
|
@ -39,6 +38,9 @@ public partial class CustomizationOptions
|
||||||
internal ImGuiScene.TextureWrap GetIcon(uint id)
|
internal ImGuiScene.TextureWrap GetIcon(uint id)
|
||||||
=> _icons.LoadIcon(id);
|
=> _icons.LoadIcon(id);
|
||||||
|
|
||||||
|
internal void RemoveIcon(uint id)
|
||||||
|
=> _icons.RemoveIcon(id);
|
||||||
|
|
||||||
private readonly IconStorage _icons;
|
private readonly IconStorage _icons;
|
||||||
|
|
||||||
private static readonly int ListSize = Clans.Length * Genders.Length;
|
private static readonly int ListSize = Clans.Length * Genders.Length;
|
||||||
|
|
@ -416,7 +418,7 @@ public partial class CustomizationOptions
|
||||||
// Obtain available hairstyles via reflection from the Hair sheet for the given subrace and gender.
|
// Obtain available hairstyles via reflection from the Hair sheet for the given subrace and gender.
|
||||||
private CustomizeData[] GetHairStyles(SubRace race, Gender gender)
|
private CustomizeData[] GetHairStyles(SubRace race, Gender gender)
|
||||||
{
|
{
|
||||||
var row = _hairSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender)!;
|
var row = _hairSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender)!;
|
||||||
// Unknown30 is the number of available hairstyles.
|
// Unknown30 is the number of available hairstyles.
|
||||||
var hairList = new List<CustomizeData>(row.Unknown30);
|
var hairList = new List<CustomizeData>(row.Unknown30);
|
||||||
// Hairstyles can be found starting at Unknown66.
|
// Hairstyles can be found starting at Unknown66.
|
||||||
|
|
@ -431,14 +433,10 @@ public partial class CustomizationOptions
|
||||||
// Hair Row from CustomizeSheet might not be set in case of unlockable hair.
|
// Hair Row from CustomizeSheet might not be set in case of unlockable hair.
|
||||||
var hairRow = _customizeSheet.GetRow(customizeIdx);
|
var hairRow = _customizeSheet.GetRow(customizeIdx);
|
||||||
if (hairRow == null)
|
if (hairRow == null)
|
||||||
{
|
|
||||||
hairList.Add(new CustomizeData(CustomizeIndex.Hairstyle, (CustomizeValue)i, customizeIdx));
|
hairList.Add(new CustomizeData(CustomizeIndex.Hairstyle, (CustomizeValue)i, customizeIdx));
|
||||||
}
|
|
||||||
else if (_options._icons.IconExists(hairRow.Icon))
|
else if (_options._icons.IconExists(hairRow.Icon))
|
||||||
{
|
|
||||||
hairList.Add(new CustomizeData(CustomizeIndex.Hairstyle, (CustomizeValue)hairRow.FeatureID, hairRow.Icon,
|
hairList.Add(new CustomizeData(CustomizeIndex.Hairstyle, (CustomizeValue)hairRow.FeatureID, hairRow.Icon,
|
||||||
(ushort)hairRow.RowId));
|
(ushort)hairRow.RowId));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return hairList.ToArray();
|
return hairList.ToArray();
|
||||||
|
|
@ -464,8 +462,8 @@ public partial class CustomizationOptions
|
||||||
// Get face paints from the hair sheet via reflection.
|
// Get face paints from the hair sheet via reflection.
|
||||||
private CustomizeData[] GetFacePaints(SubRace race, Gender gender)
|
private CustomizeData[] GetFacePaints(SubRace race, Gender gender)
|
||||||
{
|
{
|
||||||
var row = _hairSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender)!;
|
var row = _hairSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender)!;
|
||||||
var paintList = new List<CustomizeData>(row.Unknown37);
|
var paintList = new List<CustomizeData>(row.Unknown37);
|
||||||
// Number of available face paints is at Unknown37.
|
// Number of available face paints is at Unknown37.
|
||||||
for (var i = 0; i < row.Unknown37; ++i)
|
for (var i = 0; i < row.Unknown37; ++i)
|
||||||
{
|
{
|
||||||
|
|
@ -480,13 +478,12 @@ public partial class CustomizationOptions
|
||||||
var paintRow = _customizeSheet.GetRow(customizeIdx);
|
var paintRow = _customizeSheet.GetRow(customizeIdx);
|
||||||
// Facepaint Row from CustomizeSheet might not be set in case of unlockable facepaints.
|
// Facepaint Row from CustomizeSheet might not be set in case of unlockable facepaints.
|
||||||
if (paintRow != null)
|
if (paintRow != null)
|
||||||
{
|
|
||||||
paintList.Add(new CustomizeData(CustomizeIndex.FacePaint, (CustomizeValue)paintRow.FeatureID, paintRow.Icon,
|
paintList.Add(new CustomizeData(CustomizeIndex.FacePaint, (CustomizeValue)paintRow.FeatureID, paintRow.Icon,
|
||||||
(ushort)paintRow.RowId));
|
(ushort)paintRow.RowId));
|
||||||
}
|
|
||||||
else
|
else
|
||||||
paintList.Add(new CustomizeData(CustomizeIndex.FacePaint, (CustomizeValue)i, customizeIdx));
|
paintList.Add(new CustomizeData(CustomizeIndex.FacePaint, (CustomizeValue)i, customizeIdx));
|
||||||
}
|
}
|
||||||
|
|
||||||
return paintList.ToArray();
|
return paintList.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
using System.Runtime.CompilerServices;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace Glamourer.Customization;
|
namespace Glamourer.Customization;
|
||||||
|
|
||||||
|
|
@ -44,7 +46,10 @@ public enum CustomizeIndex : byte
|
||||||
|
|
||||||
public static class CustomizationExtensions
|
public static class CustomizationExtensions
|
||||||
{
|
{
|
||||||
public const int NumIndices = ((int)CustomizeIndex.FacePaintColor + 1);
|
public const int NumIndices = (int)CustomizeIndex.FacePaintColor + 1;
|
||||||
|
|
||||||
|
public static readonly CustomizeIndex[] All = Enum.GetValues<CustomizeIndex>()
|
||||||
|
.Where(v => v is not CustomizeIndex.Race and not CustomizeIndex.BodyType).ToArray();
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||||
public static (int ByteIdx, byte Mask) ToByteAndMask(this CustomizeIndex index)
|
public static (int ByteIdx, byte Mask) ToByteAndMask(this CustomizeIndex index)
|
||||||
|
|
|
||||||
|
|
@ -12,5 +12,6 @@ public interface ICustomizationManager
|
||||||
public CustomizationSet GetList(SubRace race, Gender gender);
|
public CustomizationSet GetList(SubRace race, Gender gender);
|
||||||
|
|
||||||
public ImGuiScene.TextureWrap GetIcon(uint iconId);
|
public ImGuiScene.TextureWrap GetIcon(uint iconId);
|
||||||
|
public void RemoveIcon(uint iconId);
|
||||||
public string GetName(CustomName name);
|
public string GetName(CustomName name);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
34
Glamourer/Events/ObjectUnlocked.cs
Normal file
34
Glamourer/Events/ObjectUnlocked.cs
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
using System;
|
||||||
|
using OtterGui.Classes;
|
||||||
|
|
||||||
|
namespace Glamourer.Events;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Triggered when a new item or customization is unlocked.
|
||||||
|
/// <list type="number">
|
||||||
|
/// <item>Parameter is the type of the unlocked object </item>
|
||||||
|
/// <item>Parameter is the id of the unlocked object. </item>
|
||||||
|
/// <item>Parameter is the timestamp of the unlock. </item>
|
||||||
|
/// </list>
|
||||||
|
/// </summary>
|
||||||
|
public sealed class ObjectUnlocked : EventWrapper<Action<ObjectUnlocked.Type, uint, DateTimeOffset>, ObjectUnlocked.Priority>
|
||||||
|
{
|
||||||
|
public enum Type
|
||||||
|
{
|
||||||
|
Item,
|
||||||
|
Customization,
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Priority
|
||||||
|
{
|
||||||
|
/// <seealso cref="Gui.Tabs.UnlocksTab.UnlockTable.OnObjectUnlock"/>
|
||||||
|
UnlockTable = 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObjectUnlocked()
|
||||||
|
: base(nameof(ObjectUnlocked))
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public void Invoke(Type type, uint id, DateTimeOffset timestamp)
|
||||||
|
=> Invoke(this, type, id, timestamp);
|
||||||
|
}
|
||||||
|
|
@ -3,7 +3,6 @@ using Dalamud.Plugin;
|
||||||
using Glamourer.Gui;
|
using Glamourer.Gui;
|
||||||
using Glamourer.Interop;
|
using Glamourer.Interop;
|
||||||
using Glamourer.Services;
|
using Glamourer.Services;
|
||||||
using Lumina.Excel.GeneratedSheets;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using OtterGui.Classes;
|
using OtterGui.Classes;
|
||||||
using OtterGui.Log;
|
using OtterGui.Log;
|
||||||
|
|
@ -36,7 +35,7 @@ public class Glamourer : IDalamudPlugin
|
||||||
_services.GetRequiredService<BackupService>(); // call backup service.
|
_services.GetRequiredService<BackupService>(); // call backup service.
|
||||||
_services.GetRequiredService<GlamourerWindowSystem>(); // initialize ui.
|
_services.GetRequiredService<GlamourerWindowSystem>(); // initialize ui.
|
||||||
_services.GetRequiredService<CommandService>(); // initialize commands.
|
_services.GetRequiredService<CommandService>(); // initialize commands.
|
||||||
_services.GetRequiredService<VisorService>();
|
_services.GetRequiredService<VisorService>();
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -2,22 +2,32 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using System.Text;
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
using Glamourer.Automation;
|
using Glamourer.Automation;
|
||||||
|
using Glamourer.Customization;
|
||||||
using Glamourer.Designs;
|
using Glamourer.Designs;
|
||||||
using Glamourer.Interop;
|
using Glamourer.Interop;
|
||||||
|
using Glamourer.Services;
|
||||||
using Glamourer.Structs;
|
using Glamourer.Structs;
|
||||||
|
using Glamourer.Unlocks;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
|
using Lumina.Excel.GeneratedSheets;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
using OtterGui.Raii;
|
using OtterGui.Raii;
|
||||||
using OtterGui.Widgets;
|
using OtterGui.Widgets;
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
|
using Action = System.Action;
|
||||||
|
|
||||||
namespace Glamourer.Gui.Tabs.AutomationTab;
|
namespace Glamourer.Gui.Tabs.AutomationTab;
|
||||||
|
|
||||||
public class SetPanel
|
public class SetPanel
|
||||||
{
|
{
|
||||||
private readonly AutoDesignManager _manager;
|
private readonly AutoDesignManager _manager;
|
||||||
private readonly SetSelector _selector;
|
private readonly SetSelector _selector;
|
||||||
|
private readonly ItemUnlockManager _itemUnlocks;
|
||||||
|
private readonly CustomizeUnlockManager _customizeUnlocks;
|
||||||
|
private readonly CustomizationService _customizations;
|
||||||
|
|
||||||
private readonly DesignCombo _designCombo;
|
private readonly DesignCombo _designCombo;
|
||||||
private readonly JobGroupCombo _jobGroupCombo;
|
private readonly JobGroupCombo _jobGroupCombo;
|
||||||
|
|
@ -27,12 +37,16 @@ public class SetPanel
|
||||||
|
|
||||||
private Action? _endAction;
|
private Action? _endAction;
|
||||||
|
|
||||||
public SetPanel(SetSelector selector, AutoDesignManager manager, DesignManager designs, JobService jobs)
|
public SetPanel(SetSelector selector, AutoDesignManager manager, DesignManager designs, JobService jobs, ItemUnlockManager itemUnlocks,
|
||||||
|
CustomizeUnlockManager customizeUnlocks, CustomizationService customizations)
|
||||||
{
|
{
|
||||||
_selector = selector;
|
_selector = selector;
|
||||||
_manager = manager;
|
_manager = manager;
|
||||||
_designCombo = new DesignCombo(_manager, designs);
|
_itemUnlocks = itemUnlocks;
|
||||||
_jobGroupCombo = new JobGroupCombo(manager, jobs);
|
_customizeUnlocks = customizeUnlocks;
|
||||||
|
_customizations = customizations;
|
||||||
|
_designCombo = new DesignCombo(_manager, designs);
|
||||||
|
_jobGroupCombo = new JobGroupCombo(manager, jobs);
|
||||||
}
|
}
|
||||||
|
|
||||||
private AutoDesignSet Selection
|
private AutoDesignSet Selection
|
||||||
|
|
@ -92,7 +106,7 @@ public class SetPanel
|
||||||
|
|
||||||
private void DrawDesignTable()
|
private void DrawDesignTable()
|
||||||
{
|
{
|
||||||
using var table = ImRaii.Table("SetTable", 5, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollX | ImGuiTableFlags.ScrollY);
|
using var table = ImRaii.Table("SetTable", 6, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollX | ImGuiTableFlags.ScrollY);
|
||||||
if (!table)
|
if (!table)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
@ -101,6 +115,7 @@ public class SetPanel
|
||||||
ImGui.TableSetupColumn("Design", ImGuiTableColumnFlags.WidthFixed, 220 * ImGuiHelpers.GlobalScale);
|
ImGui.TableSetupColumn("Design", ImGuiTableColumnFlags.WidthFixed, 220 * ImGuiHelpers.GlobalScale);
|
||||||
ImGui.TableSetupColumn("Application", ImGuiTableColumnFlags.WidthFixed, 5 * ImGui.GetFrameHeight() + 4 * 2 * ImGuiHelpers.GlobalScale);
|
ImGui.TableSetupColumn("Application", ImGuiTableColumnFlags.WidthFixed, 5 * ImGui.GetFrameHeight() + 4 * 2 * ImGuiHelpers.GlobalScale);
|
||||||
ImGui.TableSetupColumn("Job Restrictions", ImGuiTableColumnFlags.WidthStretch);
|
ImGui.TableSetupColumn("Job Restrictions", ImGuiTableColumnFlags.WidthStretch);
|
||||||
|
ImGui.TableSetupColumn("Warnings", ImGuiTableColumnFlags.WidthFixed, 4 * ImGui.GetFrameHeight() + 3 * 2 * ImGuiHelpers.GlobalScale);
|
||||||
ImGui.TableHeadersRow();
|
ImGui.TableHeadersRow();
|
||||||
|
|
||||||
foreach (var (design, idx) in Selection.Designs.WithIndex())
|
foreach (var (design, idx) in Selection.Designs.WithIndex())
|
||||||
|
|
@ -120,6 +135,8 @@ public class SetPanel
|
||||||
DrawApplicationTypeBoxes(Selection, design, idx);
|
DrawApplicationTypeBoxes(Selection, design, idx);
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
_jobGroupCombo.Draw(Selection, design, idx);
|
_jobGroupCombo.Draw(Selection, design, idx);
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
DrawWarnings(design, idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
|
|
@ -135,6 +152,79 @@ public class SetPanel
|
||||||
_endAction = null;
|
_endAction = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void DrawWarnings(AutoDesign design, int idx)
|
||||||
|
{
|
||||||
|
var size = new Vector2(ImGui.GetFrameHeight());
|
||||||
|
size.X += ImGuiHelpers.GlobalScale;
|
||||||
|
|
||||||
|
var (equipFlags, customizeFlags, _, _, _, _) = design.ApplyWhat();
|
||||||
|
equipFlags &= design.Design.ApplyEquip;
|
||||||
|
customizeFlags &= design.Design.ApplyCustomize;
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
foreach (var slot in EquipSlotExtensions.EqdpSlots.Append(EquipSlot.MainHand).Append(EquipSlot.OffHand))
|
||||||
|
{
|
||||||
|
var flag = slot.ToFlag();
|
||||||
|
if (!equipFlags.HasFlag(flag))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var item = design.Design.DesignData.Item(slot);
|
||||||
|
if (!_itemUnlocks.IsUnlocked(item.Id, out _))
|
||||||
|
sb.AppendLine($"{item.Name} in {slot.ToName()} slot is not unlocked but should be applied.");
|
||||||
|
}
|
||||||
|
|
||||||
|
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, new Vector2(2 * ImGuiHelpers.GlobalScale, 0));
|
||||||
|
|
||||||
|
|
||||||
|
static void DrawWarning(StringBuilder sb, uint color, Vector2 size, string suffix, string good)
|
||||||
|
{
|
||||||
|
using var style = ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, ImGuiHelpers.GlobalScale);
|
||||||
|
if (sb.Length > 0)
|
||||||
|
{
|
||||||
|
sb.Append(suffix);
|
||||||
|
using (var font = ImRaii.PushFont(UiBuilder.IconFont))
|
||||||
|
{
|
||||||
|
ImGuiUtil.DrawTextButton(FontAwesomeIcon.ExclamationCircle.ToIconString(), size, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGuiUtil.HoverTooltip(sb.ToString());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ImGuiUtil.DrawTextButton(string.Empty, size, 0);
|
||||||
|
ImGuiUtil.HoverTooltip(good);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawWarning(sb, 0xA03030F0, size, "\nThese items will be skipped when applied automatically. To change this, see",
|
||||||
|
"All equipment to be applied is unlocked."); // TODO
|
||||||
|
|
||||||
|
sb.Clear();
|
||||||
|
var sb2 = new StringBuilder();
|
||||||
|
var customize = design.Design.DesignData.Customize;
|
||||||
|
var set = _customizations.AwaitedService.GetList(customize.Clan, customize.Gender);
|
||||||
|
foreach (var type in CustomizationExtensions.All)
|
||||||
|
{
|
||||||
|
var flag = type.ToFlag();
|
||||||
|
if (!customizeFlags.HasFlag(flag))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (flag.RequiresRedraw())
|
||||||
|
sb.AppendLine($"{type.ToDefaultName()} Customization can not be changed automatically."); // TODO
|
||||||
|
else if (type is CustomizeIndex.Hairstyle or CustomizeIndex.FacePaint
|
||||||
|
&& set.DataByValue(type, customize[type], out var data, customize.Face) >= 0
|
||||||
|
&& !_customizeUnlocks.IsUnlocked(data!.Value, out _))
|
||||||
|
sb2.AppendLine(
|
||||||
|
$"{type.ToDefaultName()} Customization {_customizeUnlocks.Unlockable[data.Value].Name} is not unlocked but should be applied.");
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
DrawWarning(sb2, 0xA03030F0, size, "\nThese customizations will be skipped when applied automatically. To change this, see",
|
||||||
|
"All customizations to be applied are unlocked."); // TODO
|
||||||
|
ImGui.SameLine();
|
||||||
|
DrawWarning(sb, 0xA030F0F0, size, "\nThese customizations will be skipped when applied automatically.",
|
||||||
|
"No customizations unable to be applied automatically are set to be applied."); // TODO
|
||||||
|
}
|
||||||
|
|
||||||
private void DrawDragDrop(AutoDesignSet set, int index)
|
private void DrawDragDrop(AutoDesignSet set, int index)
|
||||||
{
|
{
|
||||||
const string dragDropLabel = "DesignDragDrop";
|
const string dragDropLabel = "DesignDragDrop";
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,11 @@ using Glamourer.Customization;
|
||||||
using Glamourer.Services;
|
using Glamourer.Services;
|
||||||
using Glamourer.Unlocks;
|
using Glamourer.Unlocks;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
|
using OtterGui;
|
||||||
|
using OtterGui.Classes;
|
||||||
using OtterGui.Raii;
|
using OtterGui.Raii;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
|
using Penumbra.GameData.Structs;
|
||||||
|
|
||||||
namespace Glamourer.Gui.Tabs.UnlocksTab;
|
namespace Glamourer.Gui.Tabs.UnlocksTab;
|
||||||
|
|
||||||
|
|
@ -18,55 +21,110 @@ public class UnlockOverview
|
||||||
private readonly CustomizationService _customizations;
|
private readonly CustomizationService _customizations;
|
||||||
private readonly CustomizeUnlockManager _customizeUnlocks;
|
private readonly CustomizeUnlockManager _customizeUnlocks;
|
||||||
private readonly PenumbraChangedItemTooltip _tooltip;
|
private readonly PenumbraChangedItemTooltip _tooltip;
|
||||||
|
private readonly TextureCache _textureCache;
|
||||||
|
|
||||||
private static readonly Vector4 UnavailableTint = new(0.3f, 0.3f, 0.3f, 1.0f);
|
private static readonly Vector4 UnavailableTint = new(0.3f, 0.3f, 0.3f, 1.0f);
|
||||||
|
|
||||||
|
private FullEquipType _selected1 = FullEquipType.Unknown;
|
||||||
|
private SubRace _selected2 = SubRace.Unknown;
|
||||||
|
private Gender _selected3 = Gender.Unknown;
|
||||||
|
|
||||||
|
private void DrawSelector()
|
||||||
|
{
|
||||||
|
using var child = ImRaii.Child("Selector", new Vector2(200 * ImGuiHelpers.GlobalScale, -1), true);
|
||||||
|
if (!child)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var type in Enum.GetValues<FullEquipType>())
|
||||||
|
{
|
||||||
|
if (type.IsOffhandType() || !_items.ItemService.AwaitedService.TryGetValue(type, out var items) || items.Count == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (ImGui.Selectable(type.ToName(), _selected1 == type))
|
||||||
|
{
|
||||||
|
ClearIcons(_selected1);
|
||||||
|
_selected1 = type;
|
||||||
|
_selected2 = SubRace.Unknown;
|
||||||
|
_selected3 = Gender.Unknown;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var clan in _customizations.AwaitedService.Clans)
|
||||||
|
{
|
||||||
|
foreach (var gender in _customizations.AwaitedService.Genders)
|
||||||
|
{
|
||||||
|
if (_customizations.AwaitedService.GetList(clan, gender).HairStyles.Count == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (ImGui.Selectable($"{(gender is Gender.Male ? '♂' : '♀')} {clan.ToShortName()} Hair & Paint",
|
||||||
|
_selected2 == clan && _selected3 == gender))
|
||||||
|
{
|
||||||
|
ClearIcons(_selected1);
|
||||||
|
_selected1 = FullEquipType.Unknown;
|
||||||
|
_selected2 = clan;
|
||||||
|
_selected3 = gender;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ClearIcons(FullEquipType type)
|
||||||
|
{
|
||||||
|
if (!_items.ItemService.AwaitedService.TryGetValue(type, out var items))
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var item in items)
|
||||||
|
_customizations.AwaitedService.RemoveIcon(item.IconId);
|
||||||
|
}
|
||||||
|
|
||||||
public UnlockOverview(ItemManager items, CustomizationService customizations, ItemUnlockManager itemUnlocks,
|
public UnlockOverview(ItemManager items, CustomizationService customizations, ItemUnlockManager itemUnlocks,
|
||||||
CustomizeUnlockManager customizeUnlocks, PenumbraChangedItemTooltip tooltip)
|
CustomizeUnlockManager customizeUnlocks, PenumbraChangedItemTooltip tooltip, TextureCache textureCache)
|
||||||
{
|
{
|
||||||
_items = items;
|
_items = items;
|
||||||
_customizations = customizations;
|
_customizations = customizations;
|
||||||
_itemUnlocks = itemUnlocks;
|
_itemUnlocks = itemUnlocks;
|
||||||
_customizeUnlocks = customizeUnlocks;
|
_customizeUnlocks = customizeUnlocks;
|
||||||
_tooltip = tooltip;
|
_tooltip = tooltip;
|
||||||
|
_textureCache = textureCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Draw()
|
public void Draw()
|
||||||
{
|
{
|
||||||
using var color = ImRaii.PushColor(ImGuiCol.Border, ImGui.GetColorU32(ImGuiCol.TableBorderStrong));
|
using var color = ImRaii.PushColor(ImGuiCol.Border, ImGui.GetColorU32(ImGuiCol.TableBorderStrong));
|
||||||
|
DrawSelector();
|
||||||
|
ImGui.SameLine();
|
||||||
|
DrawPanel();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawPanel()
|
||||||
|
{
|
||||||
using var child = ImRaii.Child("Panel", -Vector2.One, true);
|
using var child = ImRaii.Child("Panel", -Vector2.One, true);
|
||||||
if (!child)
|
if (!child)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var iconSize = ImGuiHelpers.ScaledVector2(32);
|
if (_selected1 is not FullEquipType.Unknown)
|
||||||
foreach (var type in Enum.GetValues<FullEquipType>())
|
DrawItems();
|
||||||
DrawEquipTypeHeader(iconSize, type);
|
else if (_selected2 is not SubRace.Unknown && _selected3 is not Gender.Unknown)
|
||||||
|
DrawCustomizations();
|
||||||
iconSize = ImGuiHelpers.ScaledVector2(64);
|
|
||||||
foreach (var gender in _customizations.AwaitedService.Genders)
|
|
||||||
{
|
|
||||||
foreach (var clan in _customizations.AwaitedService.Clans)
|
|
||||||
DrawCustomizationHeader(iconSize, clan, gender);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawCustomizationHeader(Vector2 iconSize, SubRace subRace, Gender gender)
|
private void DrawCustomizations()
|
||||||
{
|
{
|
||||||
var set = _customizations.AwaitedService.GetList(subRace, gender);
|
var set = _customizations.AwaitedService.GetList(_selected2, _selected3);
|
||||||
if (set.HairStyles.Count == 0 && set.FacePaints.Count == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!ImGui.CollapsingHeader($"Unlockable {subRace.ToName()} {gender.ToName()} Customizations"))
|
var spacing = IconSpacing;
|
||||||
return;
|
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing);
|
||||||
|
var iconSize = ImGuiHelpers.ScaledVector2(128);
|
||||||
|
var iconsPerRow = IconsPerRow(iconSize.X, spacing.X);
|
||||||
|
|
||||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
|
var counter = 0;
|
||||||
foreach (var customization in set.HairStyles.Concat(set.FacePaints))
|
foreach (var customize in set.HairStyles.Concat(set.FacePaints))
|
||||||
{
|
{
|
||||||
if (!_customizeUnlocks.Unlockable.TryGetValue(customization, out var unlockData))
|
if (!_customizeUnlocks.Unlockable.TryGetValue(customize, out var unlockData))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var unlocked = _customizeUnlocks.IsUnlocked(customization, out var time);
|
var unlocked = _customizeUnlocks.IsUnlocked(customize, out var time);
|
||||||
var icon = _customizations.AwaitedService.GetIcon(customization.IconId);
|
var icon = _customizations.AwaitedService.GetIcon(customize.IconId);
|
||||||
|
|
||||||
ImGui.Image(icon.ImGuiHandle, iconSize, Vector2.Zero, Vector2.One, unlocked ? Vector4.One : UnavailableTint);
|
ImGui.Image(icon.ImGuiHandle, iconSize, Vector2.Zero, Vector2.One, unlocked ? Vector4.One : UnavailableTint);
|
||||||
if (ImGui.IsItemHovered())
|
if (ImGui.IsItemHovered())
|
||||||
|
|
@ -76,37 +134,44 @@ public class UnlockOverview
|
||||||
if (size.X >= iconSize.X && size.Y >= iconSize.Y)
|
if (size.X >= iconSize.X && size.Y >= iconSize.Y)
|
||||||
ImGui.Image(icon.ImGuiHandle, size);
|
ImGui.Image(icon.ImGuiHandle, size);
|
||||||
ImGui.TextUnformatted(unlockData.Name);
|
ImGui.TextUnformatted(unlockData.Name);
|
||||||
ImGui.TextUnformatted($"{customization.Index.ToDefaultName()} {customization.Value.Value}");
|
ImGui.TextUnformatted($"{customize.Index.ToDefaultName()} {customize.Value.Value}");
|
||||||
ImGui.TextUnformatted(unlocked ? $"Unlocked on {time:g}" : "Not unlocked.");
|
ImGui.TextUnformatted(unlocked ? $"Unlocked on {time:g}" : "Not unlocked.");
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.SameLine();
|
if (counter != iconsPerRow - 1)
|
||||||
if (ImGui.GetContentRegionAvail().X < iconSize.X)
|
{
|
||||||
ImGui.NewLine();
|
ImGui.SameLine();
|
||||||
|
++counter;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
counter = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui.GetCursorPosX() != 0)
|
|
||||||
ImGui.NewLine();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawEquipTypeHeader(Vector2 iconSize, FullEquipType type)
|
private void DrawItems()
|
||||||
{
|
{
|
||||||
if (type.IsOffhandType() || !_items.ItemService.AwaitedService.TryGetValue(type, out var items) || items.Count == 0)
|
if (!_items.ItemService.AwaitedService.TryGetValue(_selected1, out var items))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!ImGui.CollapsingHeader($"{type.ToName()}s"))
|
var spacing = IconSpacing;
|
||||||
return;
|
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing);
|
||||||
|
var iconSize = ImGuiHelpers.ScaledVector2(64);
|
||||||
|
var iconsPerRow = IconsPerRow(iconSize.X, spacing.X);
|
||||||
|
var numRows = (items.Count + iconsPerRow - 1) / iconsPerRow;
|
||||||
|
var numVisibleRows = (int)(Math.Ceiling(ImGui.GetContentRegionAvail().Y / (iconSize.Y + spacing.Y)) + 0.5f);
|
||||||
|
|
||||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, new Vector2(2 * ImGuiHelpers.GlobalScale));
|
void DrawItem(EquipItem item)
|
||||||
foreach (var item in items)
|
|
||||||
{
|
{
|
||||||
if (!ImGui.IsItemVisible())
|
var unlocked = _itemUnlocks.IsUnlocked(item.Id, out var time);
|
||||||
{ }
|
var iconHandle = _textureCache.LoadIcon(item.IconId);
|
||||||
|
if (!iconHandle.HasValue)
|
||||||
|
return;
|
||||||
|
|
||||||
var unlocked = _itemUnlocks.IsUnlocked(item.Id, out var time);
|
var (icon, size) = iconHandle.Value.Value;
|
||||||
var icon = _customizations.AwaitedService.GetIcon(item.IconId);
|
|
||||||
|
|
||||||
ImGui.Image(icon.ImGuiHandle, iconSize, Vector2.Zero, Vector2.One, unlocked ? Vector4.One : UnavailableTint);
|
ImGui.Image(icon, iconSize, Vector2.Zero, Vector2.One, unlocked ? Vector4.One : UnavailableTint);
|
||||||
if (ImGui.IsItemClicked())
|
if (ImGui.IsItemClicked())
|
||||||
{
|
{
|
||||||
// TODO link
|
// TODO link
|
||||||
|
|
@ -117,10 +182,9 @@ public class UnlockOverview
|
||||||
|
|
||||||
if (ImGui.IsItemHovered())
|
if (ImGui.IsItemHovered())
|
||||||
{
|
{
|
||||||
using var tt = ImRaii.Tooltip();
|
using var tt = ImRaii.Tooltip();
|
||||||
var size = new Vector2(icon.Width, icon.Height);
|
|
||||||
if (size.X >= iconSize.X && size.Y >= iconSize.Y)
|
if (size.X >= iconSize.X && size.Y >= iconSize.Y)
|
||||||
ImGui.Image(icon.ImGuiHandle, size);
|
ImGui.Image(icon, size);
|
||||||
ImGui.TextUnformatted(item.Name);
|
ImGui.TextUnformatted(item.Name);
|
||||||
var slot = item.Type.ToSlot();
|
var slot = item.Type.ToSlot();
|
||||||
ImGui.TextUnformatted($"{item.Type.ToName()} ({slot.ToName()})");
|
ImGui.TextUnformatted($"{item.Type.ToName()} ({slot.ToName()})");
|
||||||
|
|
@ -133,13 +197,35 @@ public class UnlockOverview
|
||||||
unlocked ? time == DateTimeOffset.MinValue ? "Always Unlocked" : $"Unlocked on {time:g}" : "Not Unlocked.");
|
unlocked ? time == DateTimeOffset.MinValue ? "Always Unlocked" : $"Unlocked on {time:g}" : "Not Unlocked.");
|
||||||
_tooltip.CreateTooltip(item, string.Empty, false);
|
_tooltip.CreateTooltip(item, string.Empty, false);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ImGui.SameLine();
|
var skips = ImGuiClip.GetNecessarySkips(iconSize.Y + spacing.Y);
|
||||||
if (ImGui.GetContentRegionAvail().X < iconSize.X)
|
var end = Math.Min(numVisibleRows * iconsPerRow + skips * iconsPerRow, items.Count);
|
||||||
ImGui.NewLine();
|
var counter = 0;
|
||||||
|
for (var idx = skips * iconsPerRow; idx < end; ++idx)
|
||||||
|
{
|
||||||
|
DrawItem(items[idx]);
|
||||||
|
if (counter != iconsPerRow - 1)
|
||||||
|
{
|
||||||
|
ImGui.SameLine();
|
||||||
|
++counter;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
counter = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui.GetCursorPosX() != 0)
|
if (ImGui.GetCursorPosX() != 0)
|
||||||
ImGui.NewLine();
|
ImGui.NewLine();
|
||||||
|
var remainder = numRows - numVisibleRows - skips;
|
||||||
|
if (remainder > 0)
|
||||||
|
ImGuiClip.DrawEndDummy(remainder, iconSize.Y + spacing.Y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Vector2 IconSpacing
|
||||||
|
=> ImGuiHelpers.ScaledVector2(2);
|
||||||
|
|
||||||
|
private static int IconsPerRow(float iconWidth, float iconSpacing)
|
||||||
|
=> (int)(ImGui.GetContentRegionAvail().X / (iconWidth + iconSpacing));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,14 @@ using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using Dalamud.Game.ClientState.Objects.SubKinds;
|
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
|
using Glamourer.Events;
|
||||||
using Glamourer.Services;
|
using Glamourer.Services;
|
||||||
using Glamourer.Structs;
|
using Glamourer.Structs;
|
||||||
using Glamourer.Unlocks;
|
using Glamourer.Unlocks;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
|
using OtterGui.Classes;
|
||||||
using OtterGui.Raii;
|
using OtterGui.Raii;
|
||||||
using OtterGui.Table;
|
using OtterGui.Table;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
|
|
@ -17,35 +18,43 @@ using Penumbra.GameData.Structs;
|
||||||
|
|
||||||
namespace Glamourer.Gui.Tabs.UnlocksTab;
|
namespace Glamourer.Gui.Tabs.UnlocksTab;
|
||||||
|
|
||||||
public class UnlockTable : Table<EquipItem>
|
public class UnlockTable : Table<EquipItem>, IDisposable
|
||||||
{
|
{
|
||||||
public UnlockTable(ItemManager items, CustomizationService customizations, ItemUnlockManager itemUnlocks,
|
private readonly ObjectUnlocked _event;
|
||||||
PenumbraChangedItemTooltip tooltip)
|
|
||||||
|
public UnlockTable(ItemManager items, TextureCache cache, ItemUnlockManager itemUnlocks,
|
||||||
|
PenumbraChangedItemTooltip tooltip, ObjectUnlocked @event)
|
||||||
: base("ItemUnlockTable", new ItemList(items),
|
: base("ItemUnlockTable", new ItemList(items),
|
||||||
new NameColumn(customizations, tooltip) { Label = "Item Name..." },
|
new NameColumn(cache, tooltip) { Label = "Item Name..." },
|
||||||
new SlotColumn() { Label = "Equip Slot" },
|
new SlotColumn() { Label = "Equip Slot" },
|
||||||
new TypeColumn() { Label = "Item Type..." },
|
new TypeColumn() { Label = "Item Type..." },
|
||||||
new UnlockDateColumn(itemUnlocks) { Label = "Unlocked" },
|
new UnlockDateColumn(itemUnlocks) { Label = "Unlocked" },
|
||||||
new ItemIdColumn() { Label = "Item Id..." },
|
new ItemIdColumn() { Label = "Item Id..." },
|
||||||
new ModelDataColumn(items) { Label = "Model Data..." })
|
new ModelDataColumn(items) { Label = "Model Data..." })
|
||||||
{
|
{
|
||||||
|
_event = @event;
|
||||||
Sortable = true;
|
Sortable = true;
|
||||||
Flags |= ImGuiTableFlags.Hideable;
|
Flags |= ImGuiTableFlags.Hideable;
|
||||||
|
_event.Subscribe(OnObjectUnlock, ObjectUnlocked.Priority.UnlockTable);
|
||||||
|
cache.Logger = Glamourer.Log;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
=> _event.Unsubscribe(OnObjectUnlock);
|
||||||
|
|
||||||
private sealed class NameColumn : ColumnString<EquipItem>
|
private sealed class NameColumn : ColumnString<EquipItem>
|
||||||
{
|
{
|
||||||
private readonly CustomizationService _customizations;
|
private readonly TextureCache _textures;
|
||||||
private readonly PenumbraChangedItemTooltip _tooltip;
|
private readonly PenumbraChangedItemTooltip _tooltip;
|
||||||
|
|
||||||
public override float Width
|
public override float Width
|
||||||
=> 400 * ImGuiHelpers.GlobalScale;
|
=> 400 * ImGuiHelpers.GlobalScale;
|
||||||
|
|
||||||
public NameColumn(CustomizationService customizations, PenumbraChangedItemTooltip tooltip)
|
public NameColumn(TextureCache textures, PenumbraChangedItemTooltip tooltip)
|
||||||
{
|
{
|
||||||
_customizations = customizations;
|
_textures = textures;
|
||||||
_tooltip = tooltip;
|
_tooltip = tooltip;
|
||||||
Flags |= ImGuiTableColumnFlags.NoHide | ImGuiTableColumnFlags.NoReorder;
|
Flags |= ImGuiTableColumnFlags.NoHide | ImGuiTableColumnFlags.NoReorder;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToName(EquipItem item)
|
public override string ToName(EquipItem item)
|
||||||
|
|
@ -53,8 +62,11 @@ public class UnlockTable : Table<EquipItem>
|
||||||
|
|
||||||
public override void DrawColumn(EquipItem item, int _)
|
public override void DrawColumn(EquipItem item, int _)
|
||||||
{
|
{
|
||||||
var icon = _customizations.AwaitedService.GetIcon(item.IconId);
|
var iconHandle = _textures.LoadIcon(item.IconId);
|
||||||
ImGui.Image(icon.ImGuiHandle, new Vector2(ImGui.GetFrameHeight()));
|
if (iconHandle.HasValue)
|
||||||
|
ImGuiUtil.HoverIcon(iconHandle.Value, new Vector2(ImGui.GetFrameHeight()));
|
||||||
|
else
|
||||||
|
ImGui.Dummy(new Vector2(ImGui.GetFrameHeight()));
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImGui.AlignTextToFramePadding();
|
ImGui.AlignTextToFramePadding();
|
||||||
if (ImGui.Selectable(item.Name))
|
if (ImGui.Selectable(item.Name))
|
||||||
|
|
@ -191,7 +203,7 @@ public class UnlockTable : Table<EquipItem>
|
||||||
public override int Compare(EquipItem lhs, EquipItem rhs)
|
public override int Compare(EquipItem lhs, EquipItem rhs)
|
||||||
{
|
{
|
||||||
var unlockedLhs = _unlocks.IsUnlocked(lhs.Id, out var timeLhs);
|
var unlockedLhs = _unlocks.IsUnlocked(lhs.Id, out var timeLhs);
|
||||||
var unlockedRhs = _unlocks.IsUnlocked(lhs.Id, out var timeRhs);
|
var unlockedRhs = _unlocks.IsUnlocked(rhs.Id, out var timeRhs);
|
||||||
var c1 = unlockedLhs.CompareTo(unlockedRhs);
|
var c1 = unlockedLhs.CompareTo(unlockedRhs);
|
||||||
return c1 != 0 ? c1 : timeLhs.CompareTo(timeRhs);
|
return c1 != 0 ? c1 : timeLhs.CompareTo(timeRhs);
|
||||||
}
|
}
|
||||||
|
|
@ -273,4 +285,10 @@ public class UnlockTable : Table<EquipItem>
|
||||||
public int Count
|
public int Count
|
||||||
=> _items.ItemService.AwaitedService.TotalItemCount(true);
|
=> _items.ItemService.AwaitedService.TotalItemCount(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnObjectUnlock(ObjectUnlocked.Type _1, uint _2, DateTimeOffset _3)
|
||||||
|
{
|
||||||
|
FilterDirty = true;
|
||||||
|
SortDirty = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,8 @@ public static class ServiceManager
|
||||||
.AddSingleton<SaveService>()
|
.AddSingleton<SaveService>()
|
||||||
.AddSingleton<CodeService>()
|
.AddSingleton<CodeService>()
|
||||||
.AddSingleton<ConfigMigrationService>()
|
.AddSingleton<ConfigMigrationService>()
|
||||||
.AddSingleton<Configuration>();
|
.AddSingleton<Configuration>()
|
||||||
|
.AddSingleton<TextureCache>();
|
||||||
|
|
||||||
private static IServiceCollection AddEvents(this IServiceCollection services)
|
private static IServiceCollection AddEvents(this IServiceCollection services)
|
||||||
=> services.AddSingleton<VisorStateChanged>()
|
=> services.AddSingleton<VisorStateChanged>()
|
||||||
|
|
@ -64,7 +65,8 @@ public static class ServiceManager
|
||||||
.AddSingleton<StateChanged>()
|
.AddSingleton<StateChanged>()
|
||||||
.AddSingleton<WeaponLoading>()
|
.AddSingleton<WeaponLoading>()
|
||||||
.AddSingleton<HeadGearVisibilityChanged>()
|
.AddSingleton<HeadGearVisibilityChanged>()
|
||||||
.AddSingleton<WeaponVisibilityChanged>();
|
.AddSingleton<WeaponVisibilityChanged>()
|
||||||
|
.AddSingleton<ObjectUnlocked>();
|
||||||
|
|
||||||
private static IServiceCollection AddData(this IServiceCollection services)
|
private static IServiceCollection AddData(this IServiceCollection services)
|
||||||
=> services.AddSingleton<IdentifierService>()
|
=> services.AddSingleton<IdentifierService>()
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ using Dalamud.Utility;
|
||||||
using Dalamud.Utility.Signatures;
|
using Dalamud.Utility.Signatures;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
||||||
using Glamourer.Customization;
|
using Glamourer.Customization;
|
||||||
|
using Glamourer.Events;
|
||||||
using Glamourer.Services;
|
using Glamourer.Services;
|
||||||
using Lumina.Excel.GeneratedSheets;
|
using Lumina.Excel.GeneratedSheets;
|
||||||
|
|
||||||
|
|
@ -17,8 +18,10 @@ namespace Glamourer.Unlocks;
|
||||||
|
|
||||||
public class CustomizeUnlockManager : IDisposable, ISavable
|
public class CustomizeUnlockManager : IDisposable, ISavable
|
||||||
{
|
{
|
||||||
private readonly SaveService _saveService;
|
private readonly SaveService _saveService;
|
||||||
private readonly ClientState _clientState;
|
private readonly ClientState _clientState;
|
||||||
|
private readonly ObjectUnlocked _event;
|
||||||
|
|
||||||
private readonly Dictionary<uint, long> _unlocked = new();
|
private readonly Dictionary<uint, long> _unlocked = new();
|
||||||
|
|
||||||
public readonly IReadOnlyDictionary<CustomizeData, (uint Data, string Name)> Unlockable;
|
public readonly IReadOnlyDictionary<CustomizeData, (uint Data, string Name)> Unlockable;
|
||||||
|
|
@ -27,11 +30,12 @@ public class CustomizeUnlockManager : IDisposable, ISavable
|
||||||
=> _unlocked;
|
=> _unlocked;
|
||||||
|
|
||||||
public unsafe CustomizeUnlockManager(SaveService saveService, CustomizationService customizations, DataManager gameData,
|
public unsafe CustomizeUnlockManager(SaveService saveService, CustomizationService customizations, DataManager gameData,
|
||||||
ClientState clientState)
|
ClientState clientState, ObjectUnlocked @event)
|
||||||
{
|
{
|
||||||
SignatureHelper.Initialise(this);
|
SignatureHelper.Initialise(this);
|
||||||
_saveService = saveService;
|
_saveService = saveService;
|
||||||
_clientState = clientState;
|
_clientState = clientState;
|
||||||
|
_event = @event;
|
||||||
Unlockable = CreateUnlockableCustomizations(customizations, gameData);
|
Unlockable = CreateUnlockableCustomizations(customizations, gameData);
|
||||||
Load();
|
Load();
|
||||||
_setUnlockLinkValueHook.Enable();
|
_setUnlockLinkValueHook.Enable();
|
||||||
|
|
@ -51,13 +55,13 @@ public class CustomizeUnlockManager : IDisposable, ISavable
|
||||||
// All other customizations are not unlockable.
|
// All other customizations are not unlockable.
|
||||||
if (data.Index is not CustomizeIndex.Hairstyle and not CustomizeIndex.FacePaint)
|
if (data.Index is not CustomizeIndex.Hairstyle and not CustomizeIndex.FacePaint)
|
||||||
{
|
{
|
||||||
time = DateTime.MinValue;
|
time = DateTimeOffset.MinValue;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Unlockable.TryGetValue(data, out var pair))
|
if (!Unlockable.TryGetValue(data, out var pair))
|
||||||
{
|
{
|
||||||
time = DateTime.MinValue;
|
time = DateTimeOffset.MinValue;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -74,8 +78,9 @@ public class CustomizeUnlockManager : IDisposable, ISavable
|
||||||
}
|
}
|
||||||
|
|
||||||
_unlocked.TryAdd(pair.Data, DateTimeOffset.UtcNow.ToUnixTimeMilliseconds());
|
_unlocked.TryAdd(pair.Data, DateTimeOffset.UtcNow.ToUnixTimeMilliseconds());
|
||||||
Save();
|
|
||||||
time = DateTimeOffset.UtcNow;
|
time = DateTimeOffset.UtcNow;
|
||||||
|
_event.Invoke(ObjectUnlocked.Type.Customization, pair.Data, time);
|
||||||
|
Save();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -107,7 +112,10 @@ public class CustomizeUnlockManager : IDisposable, ISavable
|
||||||
foreach (var (_, (id, _)) in Unlockable)
|
foreach (var (_, (id, _)) in Unlockable)
|
||||||
{
|
{
|
||||||
if (instance->IsUnlockLinkUnlocked(id) && _unlocked.TryAdd(id, time))
|
if (instance->IsUnlockLinkUnlocked(id) && _unlocked.TryAdd(id, time))
|
||||||
|
{
|
||||||
|
_event.Invoke(ObjectUnlocked.Type.Customization, id, DateTimeOffset.FromUnixTimeMilliseconds(time));
|
||||||
++count;
|
++count;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (count <= 0)
|
if (count <= 0)
|
||||||
|
|
@ -141,6 +149,7 @@ public class CustomizeUnlockManager : IDisposable, ISavable
|
||||||
if (id != data || !_unlocked.TryAdd(id, time))
|
if (id != data || !_unlocked.TryAdd(id, time))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
_event.Invoke(ObjectUnlocked.Type.Customization, id, DateTimeOffset.FromUnixTimeMilliseconds(time));
|
||||||
Save();
|
Save();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -161,16 +170,11 @@ public class CustomizeUnlockManager : IDisposable, ISavable
|
||||||
=> _saveService.QueueSave(this);
|
=> _saveService.QueueSave(this);
|
||||||
|
|
||||||
public void Save(StreamWriter writer)
|
public void Save(StreamWriter writer)
|
||||||
{ }
|
=> UnlockDictionaryHelpers.Save(writer, Unlocked);
|
||||||
|
|
||||||
private void Load()
|
private void Load()
|
||||||
{
|
=> UnlockDictionaryHelpers.Load(ToFilename(_saveService.FileNames), _unlocked, id => Unlockable.Any(c => c.Value.Data == id),
|
||||||
var file = ToFilename(_saveService.FileNames);
|
"customization");
|
||||||
if (!File.Exists(file))
|
|
||||||
return;
|
|
||||||
|
|
||||||
_unlocked.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary> Create a list of all unlockable hairstyles and facepaints. </summary>
|
/// <summary> Create a list of all unlockable hairstyles and facepaints. </summary>
|
||||||
private static Dictionary<CustomizeData, (uint Data, string Name)> CreateUnlockableCustomizations(CustomizationService customizations,
|
private static Dictionary<CustomizeData, (uint Data, string Name)> CreateUnlockableCustomizations(CustomizationService customizations,
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ using Dalamud.Game.ClientState;
|
||||||
using Dalamud.Utility.Signatures;
|
using Dalamud.Utility.Signatures;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
||||||
|
using Glamourer.Events;
|
||||||
using Glamourer.Services;
|
using Glamourer.Services;
|
||||||
using Lumina.Excel.GeneratedSheets;
|
using Lumina.Excel.GeneratedSheets;
|
||||||
using Cabinet = Lumina.Excel.GeneratedSheets.Cabinet;
|
using Cabinet = Lumina.Excel.GeneratedSheets.Cabinet;
|
||||||
|
|
@ -15,10 +16,12 @@ namespace Glamourer.Unlocks;
|
||||||
|
|
||||||
public class ItemUnlockManager : ISavable, IDisposable
|
public class ItemUnlockManager : ISavable, IDisposable
|
||||||
{
|
{
|
||||||
private readonly SaveService _saveService;
|
private readonly SaveService _saveService;
|
||||||
private readonly ItemManager _items;
|
private readonly ItemManager _items;
|
||||||
private readonly ClientState _clientState;
|
private readonly ClientState _clientState;
|
||||||
private readonly Framework _framework;
|
private readonly Framework _framework;
|
||||||
|
private readonly ObjectUnlocked _event;
|
||||||
|
|
||||||
private readonly Dictionary<uint, long> _unlocked = new();
|
private readonly Dictionary<uint, long> _unlocked = new();
|
||||||
|
|
||||||
private bool _lastArmoireState;
|
private bool _lastArmoireState;
|
||||||
|
|
@ -37,65 +40,20 @@ public class ItemUnlockManager : ISavable, IDisposable
|
||||||
Cabinet = 0x08,
|
Cabinet = 0x08,
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly record struct UnlockRequirements(uint Quest1, uint Quest2, uint Achievement, ushort State, UnlockType Type)
|
|
||||||
{
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return Type switch
|
|
||||||
{
|
|
||||||
UnlockType.Quest1 => $"Quest {Quest1}",
|
|
||||||
UnlockType.Quest1 | UnlockType.Quest2 => $"Quests {Quest1} & {Quest2}",
|
|
||||||
UnlockType.Achievement => $"Achievement {Achievement}",
|
|
||||||
UnlockType.Quest1 | UnlockType.Achievement => $"Quest {Quest1} & Achievement {Achievement}",
|
|
||||||
UnlockType.Quest1 | UnlockType.Quest2 | UnlockType.Achievement => $"Quests {Quest1} & {Quest2}, Achievement {Achievement}",
|
|
||||||
UnlockType.Cabinet => $"Cabinet {Quest1}",
|
|
||||||
_ => string.Empty,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public unsafe bool IsUnlocked(ItemUnlockManager manager)
|
|
||||||
{
|
|
||||||
if (Type == 0)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
var uiState = UIState.Instance();
|
|
||||||
if (uiState == null)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
bool CheckQuest(uint quest)
|
|
||||||
=> uiState->IsUnlockLinkUnlockedOrQuestCompleted(quest);
|
|
||||||
|
|
||||||
// TODO ClientStructs
|
|
||||||
bool CheckAchievement(uint achievement)
|
|
||||||
=> false;
|
|
||||||
|
|
||||||
return Type switch
|
|
||||||
{
|
|
||||||
UnlockType.Quest1 => CheckQuest(Quest1),
|
|
||||||
UnlockType.Quest1 | UnlockType.Quest2 => CheckQuest(Quest1) && CheckQuest(Quest2),
|
|
||||||
UnlockType.Achievement => CheckAchievement(Achievement),
|
|
||||||
UnlockType.Quest1 | UnlockType.Achievement => CheckQuest(Quest1) && CheckAchievement(Achievement),
|
|
||||||
UnlockType.Quest1 | UnlockType.Quest2 | UnlockType.Achievement => CheckQuest(Quest1)
|
|
||||||
&& CheckQuest(Quest2)
|
|
||||||
&& CheckAchievement(Achievement),
|
|
||||||
UnlockType.Cabinet => uiState->Cabinet.IsCabinetLoaded() && uiState->Cabinet.IsItemInCabinet((int)Quest1),
|
|
||||||
_ => false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public readonly IReadOnlyDictionary<uint, UnlockRequirements> Unlockable;
|
public readonly IReadOnlyDictionary<uint, UnlockRequirements> Unlockable;
|
||||||
|
|
||||||
public IReadOnlyDictionary<uint, long> Unlocked
|
public IReadOnlyDictionary<uint, long> Unlocked
|
||||||
=> _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)
|
||||||
{
|
{
|
||||||
SignatureHelper.Initialise(this);
|
SignatureHelper.Initialise(this);
|
||||||
_saveService = saveService;
|
_saveService = saveService;
|
||||||
_items = items;
|
_items = items;
|
||||||
_clientState = clientState;
|
_clientState = clientState;
|
||||||
_framework = framework;
|
_framework = framework;
|
||||||
|
_event = @event;
|
||||||
Unlockable = CreateUnlockData(gameData, items);
|
Unlockable = CreateUnlockData(gameData, items);
|
||||||
Load();
|
Load();
|
||||||
_clientState.Login += OnLogin;
|
_clientState.Login += OnLogin;
|
||||||
|
|
@ -166,7 +124,13 @@ public class ItemUnlockManager : ISavable, IDisposable
|
||||||
var time = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
var time = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||||
|
|
||||||
bool AddItem(uint itemId)
|
bool AddItem(uint itemId)
|
||||||
=> _items.ItemService.AwaitedService.TryGetValue(itemId, out var equip) && _unlocked.TryAdd(equip.Id, time);
|
{
|
||||||
|
if (!_items.ItemService.AwaitedService.TryGetValue(itemId, out var equip) || !_unlocked.TryAdd(equip.Id, time))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
_event.Invoke(ObjectUnlocked.Type.Item, equip.Id, DateTimeOffset.FromUnixTimeMilliseconds(time));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
var mirageManager = MirageManager.Instance();
|
var mirageManager = MirageManager.Instance();
|
||||||
var changes = false;
|
var changes = false;
|
||||||
|
|
@ -200,7 +164,7 @@ public class ItemUnlockManager : ISavable, IDisposable
|
||||||
var inventoryManager = InventoryManager.Instance();
|
var inventoryManager = InventoryManager.Instance();
|
||||||
if (inventoryManager != null)
|
if (inventoryManager != null)
|
||||||
{
|
{
|
||||||
var type = ScannableInventories[_currentInventory];
|
var type = ScannableInventories[_currentInventory];
|
||||||
var container = inventoryManager->GetInventoryContainer(type);
|
var container = inventoryManager->GetInventoryContainer(type);
|
||||||
if (container != null && container->Loaded != 0 && _currentInventoryIndex < container->Size)
|
if (container != null && container->Loaded != 0 && _currentInventoryIndex < container->Size)
|
||||||
{
|
{
|
||||||
|
|
@ -217,6 +181,7 @@ public class ItemUnlockManager : ISavable, IDisposable
|
||||||
_currentInventoryIndex = 0;
|
_currentInventoryIndex = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (changes)
|
if (changes)
|
||||||
Save();
|
Save();
|
||||||
}
|
}
|
||||||
|
|
@ -239,8 +204,12 @@ public class ItemUnlockManager : ISavable, IDisposable
|
||||||
if (IsGameUnlocked(itemId))
|
if (IsGameUnlocked(itemId))
|
||||||
{
|
{
|
||||||
time = DateTimeOffset.UtcNow;
|
time = DateTimeOffset.UtcNow;
|
||||||
_unlocked.TryAdd(itemId, time.ToUnixTimeMilliseconds());
|
if (_unlocked.TryAdd(itemId, time.ToUnixTimeMilliseconds()))
|
||||||
Save();
|
{
|
||||||
|
_event.Invoke(ObjectUnlocked.Type.Item, itemId, time);
|
||||||
|
Save();
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -269,8 +238,11 @@ 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))
|
if (unlock.IsUnlocked(this) && _unlocked.TryAdd(itemId, time))
|
||||||
changes |= _unlocked.TryAdd(itemId, time);
|
{
|
||||||
|
_event.Invoke(ObjectUnlocked.Type.Item, itemId, DateTimeOffset.FromUnixTimeMilliseconds(time));
|
||||||
|
changes = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO inventories
|
// TODO inventories
|
||||||
|
|
@ -286,16 +258,11 @@ 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);
|
||||||
|
|
||||||
private void Load()
|
private void Load()
|
||||||
{
|
=> UnlockDictionaryHelpers.Load(ToFilename(_saveService.FileNames), _unlocked,
|
||||||
var file = ToFilename(_saveService.FileNames);
|
id => _items.ItemService.AwaitedService.TryGetValue(id, out _), "item");
|
||||||
if (!File.Exists(file))
|
|
||||||
return;
|
|
||||||
|
|
||||||
_unlocked.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnLogin(object? _, EventArgs _2)
|
private void OnLogin(object? _, EventArgs _2)
|
||||||
=> Scan();
|
=> Scan();
|
||||||
|
|
|
||||||
113
Glamourer/Unlocks/UnlockDictionaryHelpers.cs
Normal file
113
Glamourer/Unlocks/UnlockDictionaryHelpers.cs
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using Dalamud.Interface.Internal.Notifications;
|
||||||
|
|
||||||
|
namespace Glamourer.Unlocks;
|
||||||
|
|
||||||
|
public static class UnlockDictionaryHelpers
|
||||||
|
{
|
||||||
|
public const int Magic = 0x00C0FFEE;
|
||||||
|
public const int Version = 1;
|
||||||
|
|
||||||
|
public static void Save(StreamWriter writer, IReadOnlyDictionary<uint, long> data)
|
||||||
|
{
|
||||||
|
// Not using by choice, as this would close the stream prematurely.
|
||||||
|
var b = new BinaryWriter(writer.BaseStream);
|
||||||
|
b.Write(Magic);
|
||||||
|
b.Write(Version);
|
||||||
|
b.Write(data.Count);
|
||||||
|
foreach (var (id, timestamp) in data)
|
||||||
|
{
|
||||||
|
b.Write(id);
|
||||||
|
b.Write(timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
b.Flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Load(string filePath, Dictionary<uint, long> data, Func<uint, bool> validate, string type)
|
||||||
|
{
|
||||||
|
data.Clear();
|
||||||
|
if (!File.Exists(filePath))
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var fileStream = File.OpenRead(filePath);
|
||||||
|
using var b = new BinaryReader(fileStream);
|
||||||
|
var magic = b.ReadUInt32();
|
||||||
|
bool revertEndian;
|
||||||
|
switch (magic)
|
||||||
|
{
|
||||||
|
case 0x00C0FFEE:
|
||||||
|
revertEndian = false;
|
||||||
|
break;
|
||||||
|
case 0xEEFFC000:
|
||||||
|
revertEndian = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Glamourer.Chat.NotificationMessage($"Loading unlocked {type}s failed: Invalid magic number.", "Warning",
|
||||||
|
NotificationType.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var version = b.ReadInt32();
|
||||||
|
var skips = 0;
|
||||||
|
var now = DateTimeOffset.UtcNow;
|
||||||
|
switch (version)
|
||||||
|
{
|
||||||
|
case Version:
|
||||||
|
var count = b.ReadInt32();
|
||||||
|
data.EnsureCapacity(count);
|
||||||
|
for (var i = 0; i < count; ++i)
|
||||||
|
{
|
||||||
|
var id = b.ReadUInt32();
|
||||||
|
var timestamp = b.ReadInt64();
|
||||||
|
if (revertEndian)
|
||||||
|
{
|
||||||
|
id = RevertEndianness(id);
|
||||||
|
timestamp = (long)RevertEndianness(timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
var date = DateTimeOffset.FromUnixTimeMilliseconds(timestamp);
|
||||||
|
if (!validate(id)
|
||||||
|
|| date < DateTimeOffset.UnixEpoch
|
||||||
|
|| date > now
|
||||||
|
|| !data.TryAdd(id, timestamp))
|
||||||
|
++skips;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (skips > 0)
|
||||||
|
Glamourer.Chat.NotificationMessage($"Skipped {skips} unlocked {type}s while loading unlocked {type}s.", "Warning",
|
||||||
|
NotificationType.Warning);
|
||||||
|
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Glamourer.Chat.NotificationMessage($"Loading unlocked {type}s failed: Version {version} is unknown.", "Warning",
|
||||||
|
NotificationType.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Glamourer.Log.Debug($"[UnlockManager] Loaded {data.Count} unlocked {type}s.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Glamourer.Chat.NotificationMessage(ex, $"Loading unlocked {type}s failed: Unknown Error.", $"Loading unlocked {type}s failed:\n",
|
||||||
|
"Error", NotificationType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static uint RevertEndianness(uint value)
|
||||||
|
=> ((value & 0x000000FFU) << 24) | ((value & 0x0000FF00U) << 8) | ((value & 0x00FF0000U) >> 8) | ((value & 0xFF000000U) >> 24);
|
||||||
|
|
||||||
|
private static ulong RevertEndianness(long value)
|
||||||
|
=> (((ulong)value & 0x00000000000000FFU) << 56)
|
||||||
|
| (((ulong)value & 0x000000000000FF00U) << 40)
|
||||||
|
| (((ulong)value & 0x0000000000FF0000U) << 24)
|
||||||
|
| (((ulong)value & 0x00000000FF000000U) << 8)
|
||||||
|
| (((ulong)value & 0x000000FF00000000U) >> 8)
|
||||||
|
| (((ulong)value & 0x0000FF0000000000U) >> 24)
|
||||||
|
| (((ulong)value & 0x00FF000000000000U) >> 40)
|
||||||
|
| (((ulong)value & 0xFF00000000000000U) >> 56);
|
||||||
|
}
|
||||||
50
Glamourer/Unlocks/UnlockRequirements.cs
Normal file
50
Glamourer/Unlocks/UnlockRequirements.cs
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
||||||
|
|
||||||
|
namespace Glamourer.Unlocks;
|
||||||
|
|
||||||
|
public readonly record struct UnlockRequirements(uint Quest1, uint Quest2, uint Achievement, ushort State, ItemUnlockManager.UnlockType Type)
|
||||||
|
{
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return Type switch
|
||||||
|
{
|
||||||
|
ItemUnlockManager.UnlockType.Quest1 => $"Quest {Quest1}",
|
||||||
|
ItemUnlockManager.UnlockType.Quest1 | ItemUnlockManager.UnlockType.Quest2 => $"Quests {Quest1} & {Quest2}",
|
||||||
|
ItemUnlockManager.UnlockType.Achievement => $"Achievement {Achievement}",
|
||||||
|
ItemUnlockManager.UnlockType.Quest1 | ItemUnlockManager.UnlockType.Achievement => $"Quest {Quest1} & Achievement {Achievement}",
|
||||||
|
ItemUnlockManager.UnlockType.Quest1 | ItemUnlockManager.UnlockType.Quest2 | ItemUnlockManager.UnlockType.Achievement => $"Quests {Quest1} & {Quest2}, Achievement {Achievement}",
|
||||||
|
ItemUnlockManager.UnlockType.Cabinet => $"Cabinet {Quest1}",
|
||||||
|
_ => string.Empty,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe bool IsUnlocked(ItemUnlockManager manager)
|
||||||
|
{
|
||||||
|
if (Type == 0)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
var uiState = UIState.Instance();
|
||||||
|
if (uiState == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
bool CheckQuest(uint quest)
|
||||||
|
=> uiState->IsUnlockLinkUnlockedOrQuestCompleted(quest);
|
||||||
|
|
||||||
|
// TODO ClientStructs
|
||||||
|
bool CheckAchievement(uint achievement)
|
||||||
|
=> false;
|
||||||
|
|
||||||
|
return Type switch
|
||||||
|
{
|
||||||
|
ItemUnlockManager.UnlockType.Quest1 => CheckQuest(Quest1),
|
||||||
|
ItemUnlockManager.UnlockType.Quest1 | ItemUnlockManager.UnlockType.Quest2 => CheckQuest(Quest1) && CheckQuest(Quest2),
|
||||||
|
ItemUnlockManager.UnlockType.Achievement => CheckAchievement(Achievement),
|
||||||
|
ItemUnlockManager.UnlockType.Quest1 | ItemUnlockManager.UnlockType.Achievement => CheckQuest(Quest1) && CheckAchievement(Achievement),
|
||||||
|
ItemUnlockManager.UnlockType.Quest1 | ItemUnlockManager.UnlockType.Quest2 | ItemUnlockManager.UnlockType.Achievement => CheckQuest(Quest1)
|
||||||
|
&& CheckQuest(Quest2)
|
||||||
|
&& CheckAchievement(Achievement),
|
||||||
|
ItemUnlockManager.UnlockType.Cabinet => uiState->Cabinet.IsCabinetLoaded() && uiState->Cabinet.IsItemInCabinet((int)Quest1),
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue