mirror of
https://github.com/Ottermandias/Glamourer.git
synced 2025-12-12 18:27:24 +01:00
.
This commit is contained in:
parent
5e410d8786
commit
8fbca07f3c
10 changed files with 453 additions and 24 deletions
|
|
@ -1,11 +1,12 @@
|
|||
using System.Runtime.InteropServices;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Glamourer.Customization;
|
||||
|
||||
// Any customization value can be represented in 8 bytes by its ID,
|
||||
// a byte value, an optional value-id and an optional icon or color.
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public readonly struct CustomizeData
|
||||
public readonly struct CustomizeData : IEquatable<CustomizeData>
|
||||
{
|
||||
[FieldOffset(0)]
|
||||
public readonly CustomizeIndex Index;
|
||||
|
|
@ -30,4 +31,15 @@ public readonly struct CustomizeData
|
|||
Color = data;
|
||||
CustomizeId = customizeId;
|
||||
}
|
||||
|
||||
public bool Equals(CustomizeData other)
|
||||
=> Index == other.Index
|
||||
&& Value.Value == other.Value.Value
|
||||
&& CustomizeId == other.CustomizeId;
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
=> obj is CustomizeData other && Equals(other);
|
||||
|
||||
public override int GetHashCode()
|
||||
=> HashCode.Combine((int)Index, Value.Value, CustomizeId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ using Glamourer.Events;
|
|||
using Glamourer.Interop;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.Structs;
|
||||
using Glamourer.Unlocks;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OtterGui.Filesystem;
|
||||
|
|
@ -28,6 +29,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>
|
|||
private readonly DesignManager _designs;
|
||||
private readonly ActorService _actors;
|
||||
private readonly AutomationChanged _event;
|
||||
private readonly ItemUnlockManager _unlockManager;
|
||||
|
||||
private readonly List<AutoDesignSet> _data = new();
|
||||
private readonly Dictionary<ActorIdentifier, AutoDesignSet> _enabled = new();
|
||||
|
|
@ -36,13 +38,14 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>
|
|||
=> _enabled;
|
||||
|
||||
public AutoDesignManager(JobService jobs, ActorService actors, SaveService saveService, DesignManager designs, AutomationChanged @event,
|
||||
FixedDesignMigrator migrator, DesignFileSystem fileSystem)
|
||||
FixedDesignMigrator migrator, DesignFileSystem fileSystem, ItemUnlockManager unlockManager)
|
||||
{
|
||||
_jobs = jobs;
|
||||
_actors = actors;
|
||||
_saveService = saveService;
|
||||
_designs = designs;
|
||||
_event = @event;
|
||||
_unlockManager = unlockManager;
|
||||
Load();
|
||||
migrator.ConsumeMigratedData(_actors, fileSystem, this);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ using Glamourer.Interop.Penumbra;
|
|||
using Glamourer.Interop.Structs;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.State;
|
||||
using Glamourer.Unlocks;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
|
|
@ -47,6 +48,8 @@ public unsafe class DebugTab : ITab
|
|||
private readonly ActorService _actors;
|
||||
private readonly CustomizationService _customization;
|
||||
private readonly JobService _jobs;
|
||||
private readonly CustomizeUnlockManager _customizeUnlocks;
|
||||
private readonly CustomizeUnlockManager _itemUnlocks;
|
||||
|
||||
private readonly DesignManager _designManager;
|
||||
private readonly DesignFileSystem _designFileSystem;
|
||||
|
|
@ -66,7 +69,7 @@ public unsafe class DebugTab : ITab
|
|||
ActorService actors, ItemManager items, CustomizationService customization, ObjectManager objectManager,
|
||||
DesignFileSystem designFileSystem, DesignManager designManager, StateManager state, Configuration config,
|
||||
PenumbraChangedItemTooltip penumbraTooltip, MetaService metaService, GlamourerIpc ipc, DalamudPluginInterface pluginInterface,
|
||||
AutoDesignManager autoDesignManager, JobService jobs, PhrasingService phrasing)
|
||||
AutoDesignManager autoDesignManager, JobService jobs, PhrasingService phrasing, CustomizeUnlockManager customizeUnlocks)
|
||||
{
|
||||
_changeCustomizeService = changeCustomizeService;
|
||||
_visorService = visorService;
|
||||
|
|
@ -89,6 +92,7 @@ public unsafe class DebugTab : ITab
|
|||
_autoDesignManager = autoDesignManager;
|
||||
_jobs = jobs;
|
||||
_phrasing = phrasing;
|
||||
_customizeUnlocks = customizeUnlocks;
|
||||
}
|
||||
|
||||
public ReadOnlySpan<byte> Label
|
||||
|
|
@ -106,6 +110,7 @@ public unsafe class DebugTab : ITab
|
|||
DrawDesigns();
|
||||
DrawState();
|
||||
DrawAutoDesigns();
|
||||
DrawUnlocks();
|
||||
DrawIpc();
|
||||
}
|
||||
|
||||
|
|
@ -1236,6 +1241,61 @@ public unsafe class DebugTab : ITab
|
|||
|
||||
#endregion
|
||||
|
||||
#region Unlocks
|
||||
|
||||
private void DrawUnlocks()
|
||||
{
|
||||
if (!ImGui.CollapsingHeader("Unlocks"))
|
||||
return;
|
||||
|
||||
DrawCustomizationUnlocks();
|
||||
DrawItemUnlocks();
|
||||
}
|
||||
|
||||
private void DrawCustomizationUnlocks()
|
||||
{
|
||||
using var tree = ImRaii.TreeNode("Customization");
|
||||
if (!tree)
|
||||
return;
|
||||
|
||||
|
||||
using var table = ImRaii.Table("customizationUnlocks", 6,
|
||||
ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY,
|
||||
new Vector2(ImGui.GetContentRegionAvail().X, 12 * ImGui.GetTextLineHeight()));
|
||||
if (!table)
|
||||
return;
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
var skips = ImGuiClip.GetNecessarySkips(ImGui.GetTextLineHeightWithSpacing());
|
||||
ImGui.TableNextRow();
|
||||
var remainder = ImGuiClip.ClippedDraw(_customizeUnlocks.Unlockable, skips, t =>
|
||||
{
|
||||
ImGuiUtil.DrawTableColumn(t.Key.Index.ToDefaultName());
|
||||
ImGuiUtil.DrawTableColumn(t.Key.CustomizeId.ToString());
|
||||
ImGuiUtil.DrawTableColumn(t.Key.Value.Value.ToString());
|
||||
ImGuiUtil.DrawTableColumn(t.Value.Data.ToString());
|
||||
ImGuiUtil.DrawTableColumn(t.Value.Name);
|
||||
ImGuiUtil.DrawTableColumn(_customizeUnlocks.IsUnlocked(t.Key, out var time)
|
||||
? time == DateTimeOffset.MaxValue ? "Always" : time.LocalDateTime.ToShortDateString()
|
||||
: "Never");
|
||||
}, _customizeUnlocks.Unlockable.Count);
|
||||
ImGuiClip.DrawEndDummy(remainder, ImGui.GetTextLineHeight());
|
||||
}
|
||||
|
||||
private void DrawItemUnlocks()
|
||||
{
|
||||
using var tree = ImRaii.TreeNode("Item");
|
||||
if (!tree)
|
||||
return;
|
||||
|
||||
|
||||
using var table = ImRaii.Table("itemUnlocks", 6, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg);
|
||||
if (!table)
|
||||
return;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IPC
|
||||
|
||||
private string _gameObjectName = string.Empty;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ using Dalamud.Hooking;
|
|||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||
using Glamourer.Events;
|
||||
using Glamourer.Interop.Structs;
|
||||
using static OtterGui.Raii.ImRaii;
|
||||
|
||||
namespace Glamourer.Interop;
|
||||
|
||||
|
|
@ -56,7 +55,7 @@ public unsafe class MetaService : IDisposable
|
|||
var v = value == 0;
|
||||
_headGearEvent.Invoke(actor, ref v);
|
||||
value = (byte)(v ? 0 : 1);
|
||||
Glamourer.Log.Information($"[MetaService] Hide Hat triggered with 0x{(nint)drawData:X} {id} {value} for {actor.Utf8Name}.");
|
||||
Glamourer.Log.Verbose($"[MetaService] Hide Hat triggered with 0x{(nint)drawData:X} {id} {value} for {actor.Utf8Name}.");
|
||||
_hideHatGearHook.Original(drawData, id, value);
|
||||
}
|
||||
|
||||
|
|
@ -66,7 +65,7 @@ public unsafe class MetaService : IDisposable
|
|||
value = !value;
|
||||
_weaponEvent.Invoke(actor, ref value);
|
||||
value = !value;
|
||||
Glamourer.Log.Information($"[MetaService] Hide Weapon triggered with 0x{(nint)drawData:X} {value} for {actor.Utf8Name}.");
|
||||
Glamourer.Log.Verbose($"[MetaService] Hide Weapon triggered with 0x{(nint)drawData:X} {value} for {actor.Utf8Name}.");
|
||||
_hideWeaponsHook.Original(drawData, value);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,9 @@ public class BackupService
|
|||
new(fileNames.ConfigFile),
|
||||
new(fileNames.DesignFileSystem),
|
||||
new(fileNames.MigrationDesignFile),
|
||||
new(fileNames.AutomationFile),
|
||||
new(fileNames.UnlockFileCustomize),
|
||||
new(fileNames.UnlockFileItems),
|
||||
};
|
||||
|
||||
list.AddRange(fileNames.Designs());
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ public class FilenameService
|
|||
public readonly string MigrationDesignFile;
|
||||
public readonly string DesignDirectory;
|
||||
public readonly string AutomationFile;
|
||||
public readonly string UnlockFileCustomize;
|
||||
public readonly string UnlockFileItems;
|
||||
|
||||
public FilenameService(DalamudPluginInterface pi)
|
||||
{
|
||||
|
|
@ -21,6 +23,8 @@ public class FilenameService
|
|||
AutomationFile = Path.Combine(ConfigDirectory, "automation.json");
|
||||
DesignFileSystem = Path.Combine(ConfigDirectory, "sort_order.json");
|
||||
MigrationDesignFile = Path.Combine(ConfigDirectory, "Designs.json");
|
||||
UnlockFileCustomize = Path.Combine(ConfigDirectory, "unlocks_customize.json");
|
||||
UnlockFileItems = Path.Combine(ConfigDirectory, "unlocks_items.json");
|
||||
DesignDirectory = Path.Combine(ConfigDirectory, "designs");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ using Glamourer.Gui.Tabs.DesignTab;
|
|||
using Glamourer.Interop;
|
||||
using Glamourer.Interop.Penumbra;
|
||||
using Glamourer.State;
|
||||
using Glamourer.Unlocks;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using OtterGui.Classes;
|
||||
using OtterGui.Log;
|
||||
|
|
@ -80,7 +81,9 @@ public static class ServiceManager
|
|||
.AddSingleton<PenumbraService>()
|
||||
.AddSingleton<ObjectManager>()
|
||||
.AddSingleton<PenumbraAutoRedraw>()
|
||||
.AddSingleton<JobService>();
|
||||
.AddSingleton<JobService>()
|
||||
.AddSingleton<CustomizeUnlockManager>()
|
||||
.AddSingleton<ItemUnlockManager>();
|
||||
|
||||
private static IServiceCollection AddDesigns(this IServiceCollection services)
|
||||
=> services.AddSingleton<DesignManager>()
|
||||
|
|
|
|||
|
|
@ -20,11 +20,12 @@ public class StateEditor
|
|||
private readonly UpdateSlotService _updateSlot;
|
||||
private readonly VisorService _visor;
|
||||
private readonly WeaponService _weapon;
|
||||
private readonly MetaService _metaService;
|
||||
private readonly ChangeCustomizeService _changeCustomize;
|
||||
private readonly ItemManager _items;
|
||||
|
||||
public StateEditor(UpdateSlotService updateSlot, VisorService visor, WeaponService weapon, ChangeCustomizeService changeCustomize,
|
||||
ItemManager items, PenumbraService penumbra)
|
||||
ItemManager items, PenumbraService penumbra, MetaService metaService)
|
||||
{
|
||||
_updateSlot = updateSlot;
|
||||
_visor = visor;
|
||||
|
|
@ -32,6 +33,7 @@ public class StateEditor
|
|||
_changeCustomize = changeCustomize;
|
||||
_items = items;
|
||||
_penumbra = penumbra;
|
||||
_metaService = metaService;
|
||||
}
|
||||
|
||||
/// <summary> Changing the model ID simply requires guaranteed redrawing. </summary>
|
||||
|
|
@ -152,13 +154,13 @@ public class StateEditor
|
|||
public unsafe void ChangeHatState(ActorData data, bool value)
|
||||
{
|
||||
foreach (var actor in data.Objects.Where(a => a.IsCharacter))
|
||||
actor.AsCharacter->DrawData.HideHeadgear(0, !value);
|
||||
_metaService.SetHatState(actor, value);
|
||||
}
|
||||
|
||||
/// <summary> Change the weapon-visibility state on actors. </summary>
|
||||
public unsafe void ChangeWeaponState(ActorData data, bool value)
|
||||
{
|
||||
foreach (var actor in data.Objects.Where(a => a.IsCharacter))
|
||||
actor.AsCharacter->DrawData.HideWeapons(!value);
|
||||
_metaService.SetWeaponState(actor, value);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
207
Glamourer/Unlocks/CustomizeUnlockManager.cs
Normal file
207
Glamourer/Unlocks/CustomizeUnlockManager.cs
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Dalamud;
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Game.ClientState;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Utility;
|
||||
using Dalamud.Utility.Signatures;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Services;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
|
||||
namespace Glamourer.Unlocks;
|
||||
|
||||
public class CustomizeUnlockManager : IDisposable, ISavable
|
||||
{
|
||||
private readonly SaveService _saveService;
|
||||
private readonly ClientState _clientState;
|
||||
private readonly Dictionary<uint, long> _unlocked = new();
|
||||
|
||||
public readonly IReadOnlyDictionary<CustomizeData, (uint Data, string Name)> Unlockable;
|
||||
|
||||
public IReadOnlyDictionary<uint, long> Unlocked
|
||||
=> _unlocked;
|
||||
|
||||
public unsafe CustomizeUnlockManager(SaveService saveService, CustomizationService customizations, DataManager gameData,
|
||||
ClientState clientState)
|
||||
{
|
||||
SignatureHelper.Initialise(this);
|
||||
_saveService = saveService;
|
||||
_clientState = clientState;
|
||||
Unlockable = CreateUnlockableCustomizations(customizations, gameData);
|
||||
Load();
|
||||
_setUnlockLinkValueHook.Enable();
|
||||
_clientState.Login += OnLogin;
|
||||
Scan();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_setUnlockLinkValueHook.Dispose();
|
||||
_clientState.Login -= OnLogin;
|
||||
}
|
||||
|
||||
/// <summary> Check if a customization is unlocked for Glamourer. </summary>
|
||||
public bool IsUnlocked(CustomizeData data, out DateTimeOffset time)
|
||||
{
|
||||
if (!Unlockable.TryGetValue(data, out var pair))
|
||||
{
|
||||
time = DateTime.MaxValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_unlocked.TryGetValue(pair.Data, out var t))
|
||||
{
|
||||
time = DateTimeOffset.FromUnixTimeMilliseconds(t);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!IsUnlockedGame(pair.Data))
|
||||
{
|
||||
time = DateTimeOffset.MinValue;
|
||||
return false;
|
||||
}
|
||||
|
||||
_unlocked.TryAdd(pair.Data, DateTimeOffset.UtcNow.ToUnixTimeMilliseconds());
|
||||
Save();
|
||||
time = DateTimeOffset.UtcNow;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary> Check if a customization is currently unlocked for the game state. </summary>
|
||||
public unsafe bool IsUnlockedGame(uint dataId)
|
||||
{
|
||||
var instance = UIState.Instance();
|
||||
if (instance == null)
|
||||
return false;
|
||||
|
||||
return UIState.Instance()->IsUnlockLinkUnlocked(dataId);
|
||||
}
|
||||
|
||||
/// <summary> Scan and update all unlockable customizations for their current game state. </summary>
|
||||
public unsafe void Scan()
|
||||
{
|
||||
if (_clientState.LocalPlayer == null)
|
||||
return;
|
||||
|
||||
Glamourer.Log.Debug("[UnlockManager] Scanning for new unlocked customizations.");
|
||||
var instance = UIState.Instance();
|
||||
if (instance == null)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
var count = 0;
|
||||
var time = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||
foreach (var (_, (id, _)) in Unlockable)
|
||||
{
|
||||
if (instance->IsUnlockLinkUnlocked(id) && _unlocked.TryAdd(id, time))
|
||||
++count;
|
||||
}
|
||||
|
||||
if (count <= 0)
|
||||
return;
|
||||
|
||||
Save();
|
||||
Glamourer.Log.Debug($"[UnlockManager] Found {count} new unlocked customizations..");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Glamourer.Log.Error($"[UnlockManager] Error scanning for newly unlocked customizations:\n{ex}");
|
||||
}
|
||||
}
|
||||
|
||||
private delegate void SetUnlockLinkValueDelegate(nint uiState, uint data, byte value);
|
||||
|
||||
[Signature("48 83 EC ?? 8B C2 44 8B D2", DetourName = nameof(SetUnlockLinkValueDetour))]
|
||||
private readonly Hook<SetUnlockLinkValueDelegate> _setUnlockLinkValueHook = null!;
|
||||
|
||||
private void SetUnlockLinkValueDetour(nint uiState, uint data, byte value)
|
||||
{
|
||||
_setUnlockLinkValueHook.Original(uiState, data, value);
|
||||
try
|
||||
{
|
||||
if (value == 0)
|
||||
return;
|
||||
|
||||
var time = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||
foreach (var (_, (id, _)) in Unlockable)
|
||||
{
|
||||
if (id != data || !_unlocked.TryAdd(id, time))
|
||||
continue;
|
||||
|
||||
Save();
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Glamourer.Log.Error($"[UnlockManager] Error in SetUnlockLinkValue Hook:\n{ex}");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnLogin(object? _, EventArgs _2)
|
||||
=> Scan();
|
||||
|
||||
public string ToFilename(FilenameService fileNames)
|
||||
=> fileNames.UnlockFileCustomize;
|
||||
|
||||
public void Save()
|
||||
=> _saveService.QueueSave(this);
|
||||
|
||||
public void Save(StreamWriter writer)
|
||||
{ }
|
||||
|
||||
private void Load()
|
||||
{
|
||||
var file = ToFilename(_saveService.FileNames);
|
||||
if (!File.Exists(file))
|
||||
return;
|
||||
|
||||
_unlocked.Clear();
|
||||
}
|
||||
|
||||
/// <summary> Create a list of all unlockable hairstyles and facepaints. </summary>
|
||||
private static Dictionary<CustomizeData, (uint Data, string Name)> CreateUnlockableCustomizations(CustomizationService customizations,
|
||||
DataManager gameData)
|
||||
{
|
||||
var ret = new Dictionary<CustomizeData, (uint Data, string Name)>();
|
||||
var sheet = gameData.GetExcelSheet<CharaMakeCustomize>(ClientLanguage.English)!;
|
||||
foreach (var clan in customizations.AwaitedService.Clans)
|
||||
{
|
||||
foreach (var gender in customizations.AwaitedService.Genders)
|
||||
{
|
||||
var list = customizations.AwaitedService.GetList(clan, gender);
|
||||
foreach (var hair in list.HairStyles)
|
||||
{
|
||||
var x = sheet.FirstOrDefault(f => f.FeatureID == hair.Value.Value);
|
||||
if (x?.IsPurchasable == true)
|
||||
{
|
||||
var name = x.FeatureID == 61
|
||||
? "Eternal Bond"
|
||||
: x.HintItem.Value?.Name.ToDalamudString().ToString().Replace("Modern Aesthetics - ", string.Empty)
|
||||
?? string.Empty;
|
||||
ret.TryAdd(hair, (x.Data, name));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var paint in list.FacePaints)
|
||||
{
|
||||
var x = sheet.FirstOrDefault(f => f.FeatureID == paint.Value.Value);
|
||||
if (x?.IsPurchasable == true)
|
||||
{
|
||||
var name = x.HintItem.Value?.Name.ToDalamudString().ToString().Replace("Modern Cosmetics - ", string.Empty)
|
||||
?? string.Empty;
|
||||
ret.TryAdd(paint, (x.Data, name));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
136
Glamourer/Unlocks/ItemUnlockManager.cs
Normal file
136
Glamourer/Unlocks/ItemUnlockManager.cs
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Game;
|
||||
using Dalamud.Game.ClientState;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Utility.Signatures;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
||||
using Glamourer.Services;
|
||||
using Lumina.Excel;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Achievement = FFXIVClientStructs.FFXIV.Client.Game.UI.Achievement;
|
||||
using Cabinet = Lumina.Excel.GeneratedSheets.Cabinet;
|
||||
|
||||
namespace Glamourer.Unlocks;
|
||||
|
||||
public class ItemUnlockManager : ISavable, IDisposable
|
||||
{
|
||||
private readonly SaveService _saveService;
|
||||
private readonly ItemManager _items;
|
||||
private readonly ClientState _clientState;
|
||||
private readonly Framework _framework;
|
||||
private readonly Dictionary<uint, long> _unlocked = new();
|
||||
|
||||
|
||||
public enum UnlockType : byte
|
||||
{
|
||||
Cabinet,
|
||||
Quest,
|
||||
Achievement,
|
||||
}
|
||||
|
||||
public readonly IReadOnlyDictionary<uint, (uint, UnlockType)> _unlockable;
|
||||
|
||||
public IReadOnlyDictionary<uint, long> Unlocked
|
||||
=> _unlocked;
|
||||
|
||||
public ItemUnlockManager(SaveService saveService, ItemManager items, ClientState clientState, DataManager gameData, Framework framework)
|
||||
{
|
||||
SignatureHelper.Initialise(this);
|
||||
_saveService = saveService;
|
||||
_items = items;
|
||||
_clientState = clientState;
|
||||
_framework = framework;
|
||||
_unlockable = CreateUnlockData(gameData, items);
|
||||
Load();
|
||||
_clientState.Login += OnLogin;
|
||||
_framework.Update += OnFramework;
|
||||
Scan();
|
||||
}
|
||||
|
||||
//private Achievement.AchievementState _achievementState = Achievement.AchievementState.Invalid;
|
||||
|
||||
private unsafe void OnFramework(Framework _)
|
||||
{
|
||||
//var achievement = Achievement.Instance();
|
||||
var uiState = UIState.Instance();
|
||||
}
|
||||
|
||||
public bool IsUnlocked(uint itemId)
|
||||
{
|
||||
// Pseudo items are always unlocked.
|
||||
if (itemId >= _items.ItemSheet.RowCount)
|
||||
return true;
|
||||
|
||||
if (_unlocked.ContainsKey(itemId))
|
||||
return true;
|
||||
|
||||
// TODO
|
||||
return false;
|
||||
}
|
||||
|
||||
public unsafe bool IsGameUnlocked(uint id, UnlockType type)
|
||||
{
|
||||
var uiState = UIState.Instance();
|
||||
if (uiState == null)
|
||||
return false;
|
||||
|
||||
return type switch
|
||||
{
|
||||
UnlockType.Cabinet => uiState->Cabinet.IsCabinetLoaded() && uiState->Cabinet.IsItemInCabinet((int)id),
|
||||
UnlockType.Quest => uiState->IsUnlockLinkUnlockedOrQuestCompleted(id),
|
||||
UnlockType.Achievement => false,
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_clientState.Login -= OnLogin;
|
||||
_framework.Update -= OnFramework;
|
||||
}
|
||||
|
||||
public void Scan()
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
public string ToFilename(FilenameService fileNames)
|
||||
=> fileNames.UnlockFileItems;
|
||||
|
||||
public void Save()
|
||||
=> _saveService.QueueSave(this);
|
||||
|
||||
public void Save(StreamWriter writer)
|
||||
{ }
|
||||
|
||||
private void Load()
|
||||
{
|
||||
var file = ToFilename(_saveService.FileNames);
|
||||
if (!File.Exists(file))
|
||||
return;
|
||||
|
||||
_unlocked.Clear();
|
||||
}
|
||||
|
||||
private void OnLogin(object? _, EventArgs _2)
|
||||
=> Scan();
|
||||
|
||||
private static Dictionary<uint, (uint, UnlockType)> CreateUnlockData(DataManager gameData, ItemManager items)
|
||||
{
|
||||
var ret = new Dictionary<uint, (uint, UnlockType)>();
|
||||
var cabinet = gameData.GetExcelSheet<Cabinet>()!;
|
||||
foreach (var row in cabinet)
|
||||
{
|
||||
if (items.ItemService.AwaitedService.TryGetValue(row.Item.Row, out var item))
|
||||
ret.TryAdd(item.Id, (row.RowId, UnlockType.Cabinet));
|
||||
}
|
||||
|
||||
var gilShop = gameData.GetExcelSheet<GilShopItem>()!;
|
||||
// TODO
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue