mirror of
https://github.com/goatcorp/Dalamud.git
synced 2026-02-21 07:17:45 +01:00
Add support for Achievements to IUnlockState
This commit is contained in:
parent
ec450da054
commit
907b585b75
2 changed files with 82 additions and 13 deletions
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||||
|
|
||||||
using Dalamud.Data;
|
using Dalamud.Data;
|
||||||
using Dalamud.Game.Gui;
|
using Dalamud.Game.Gui;
|
||||||
|
using Dalamud.Hooking;
|
||||||
using Dalamud.IoC;
|
using Dalamud.IoC;
|
||||||
using Dalamud.IoC.Internal;
|
using Dalamud.IoC.Internal;
|
||||||
using Dalamud.Logging.Internal;
|
using Dalamud.Logging.Internal;
|
||||||
|
|
@ -16,7 +17,9 @@ using FFXIVClientStructs.FFXIV.Component.Exd;
|
||||||
using Lumina.Excel;
|
using Lumina.Excel;
|
||||||
using Lumina.Excel.Sheets;
|
using Lumina.Excel.Sheets;
|
||||||
|
|
||||||
|
using AchievementSheet = Lumina.Excel.Sheets.Achievement;
|
||||||
using ActionSheet = Lumina.Excel.Sheets.Action;
|
using ActionSheet = Lumina.Excel.Sheets.Action;
|
||||||
|
using CSAchievement = FFXIVClientStructs.FFXIV.Client.Game.UI.Achievement;
|
||||||
using InstanceContentSheet = Lumina.Excel.Sheets.InstanceContent;
|
using InstanceContentSheet = Lumina.Excel.Sheets.InstanceContent;
|
||||||
using PublicContentSheet = Lumina.Excel.Sheets.PublicContent;
|
using PublicContentSheet = Lumina.Excel.Sheets.PublicContent;
|
||||||
|
|
||||||
|
|
@ -30,7 +33,8 @@ internal unsafe class UnlockState : IInternalDisposableService, IUnlockState
|
||||||
{
|
{
|
||||||
private static readonly ModuleLog Log = new(nameof(UnlockState));
|
private static readonly ModuleLog Log = new(nameof(UnlockState));
|
||||||
|
|
||||||
private readonly ConcurrentDictionary<Type, HashSet<uint>> cachedUnlockedRowIds = [];
|
[ServiceManager.ServiceDependency]
|
||||||
|
private readonly TargetSigScanner sigScanner = Service<TargetSigScanner>.Get();
|
||||||
|
|
||||||
[ServiceManager.ServiceDependency]
|
[ServiceManager.ServiceDependency]
|
||||||
private readonly DataManager dataManager = Service<DataManager>.Get();
|
private readonly DataManager dataManager = Service<DataManager>.Get();
|
||||||
|
|
@ -44,17 +48,31 @@ internal unsafe class UnlockState : IInternalDisposableService, IUnlockState
|
||||||
[ServiceManager.ServiceDependency]
|
[ServiceManager.ServiceDependency]
|
||||||
private readonly RecipeData recipeData = Service<RecipeData>.Get();
|
private readonly RecipeData recipeData = Service<RecipeData>.Get();
|
||||||
|
|
||||||
|
private readonly ConcurrentDictionary<Type, HashSet<uint>> cachedUnlockedRowIds = [];
|
||||||
|
private readonly Hook<SetAchievementCompletedDelegate> setAchievementCompletedHook;
|
||||||
|
|
||||||
[ServiceManager.ServiceConstructor]
|
[ServiceManager.ServiceConstructor]
|
||||||
private UnlockState()
|
private UnlockState()
|
||||||
{
|
{
|
||||||
this.clientState.Login += this.OnLogin;
|
this.clientState.Login += this.OnLogin;
|
||||||
this.clientState.Logout += this.OnLogout;
|
this.clientState.Logout += this.OnLogout;
|
||||||
this.gameGui.AgentUpdate += this.OnAgentUpdate;
|
this.gameGui.AgentUpdate += this.OnAgentUpdate;
|
||||||
|
|
||||||
|
this.setAchievementCompletedHook = Hook<SetAchievementCompletedDelegate>.FromAddress(
|
||||||
|
this.sigScanner.ScanText("81 FA ?? ?? ?? ?? 0F 87 ?? ?? ?? ?? 53"),
|
||||||
|
this.SetAchievementCompletedDetour);
|
||||||
|
|
||||||
|
this.setAchievementCompletedHook.Enable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private delegate void SetAchievementCompletedDelegate(CSAchievement* thisPtr, uint id);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public event IUnlockState.UnlockDelegate Unlock;
|
public event IUnlockState.UnlockDelegate Unlock;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool IsAchievementListLoaded => CSAchievement.Instance()->IsLoaded();
|
||||||
|
|
||||||
private bool IsLoaded => PlayerState.Instance()->IsLoaded;
|
private bool IsLoaded => PlayerState.Instance()->IsLoaded;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|
@ -63,6 +81,21 @@ internal unsafe class UnlockState : IInternalDisposableService, IUnlockState
|
||||||
this.clientState.Login -= this.OnLogin;
|
this.clientState.Login -= this.OnLogin;
|
||||||
this.clientState.Logout -= this.OnLogout;
|
this.clientState.Logout -= this.OnLogout;
|
||||||
this.gameGui.AgentUpdate -= this.OnAgentUpdate;
|
this.gameGui.AgentUpdate -= this.OnAgentUpdate;
|
||||||
|
|
||||||
|
this.setAchievementCompletedHook.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool IsAchievementComplete(AchievementSheet row)
|
||||||
|
{
|
||||||
|
// Only check for login state here as individual Achievements
|
||||||
|
// may be flagged as complete when you unlock them, regardless
|
||||||
|
// of whether the full Achievements list was loaded or not.
|
||||||
|
|
||||||
|
if (!this.IsLoaded)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return CSAchievement.Instance()->IsComplete((int)row.RowId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|
@ -464,6 +497,9 @@ internal unsafe class UnlockState : IInternalDisposableService, IUnlockState
|
||||||
if (!this.IsLoaded || rowRef.IsUntyped)
|
if (!this.IsLoaded || rowRef.IsUntyped)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
if (rowRef.TryGetValue<AchievementSheet>(out var achievementRow))
|
||||||
|
return this.IsAchievementComplete(achievementRow);
|
||||||
|
|
||||||
if (rowRef.TryGetValue<ActionSheet>(out var actionRow))
|
if (rowRef.TryGetValue<ActionSheet>(out var actionRow))
|
||||||
return this.IsActionUnlocked(actionRow);
|
return this.IsActionUnlocked(actionRow);
|
||||||
|
|
||||||
|
|
@ -621,6 +657,16 @@ internal unsafe class UnlockState : IInternalDisposableService, IUnlockState
|
||||||
this.Update();
|
this.Update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void SetAchievementCompletedDetour(CSAchievement* thisPtr, uint id)
|
||||||
|
{
|
||||||
|
this.setAchievementCompletedHook.Original(thisPtr, id);
|
||||||
|
|
||||||
|
if (!this.IsLoaded)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.RaiseUnlockSafely((RowRef)LuminaUtils.CreateRef<AchievementSheet>(id));
|
||||||
|
}
|
||||||
|
|
||||||
private void Update()
|
private void Update()
|
||||||
{
|
{
|
||||||
if (!this.IsLoaded)
|
if (!this.IsLoaded)
|
||||||
|
|
@ -628,6 +674,8 @@ internal unsafe class UnlockState : IInternalDisposableService, IUnlockState
|
||||||
|
|
||||||
Log.Verbose("Checking for new unlocks...");
|
Log.Verbose("Checking for new unlocks...");
|
||||||
|
|
||||||
|
// Do not check for Achievements here!
|
||||||
|
|
||||||
this.UpdateUnlocksForSheet<ActionSheet>();
|
this.UpdateUnlocksForSheet<ActionSheet>();
|
||||||
this.UpdateUnlocksForSheet<AetherCurrent>();
|
this.UpdateUnlocksForSheet<AetherCurrent>();
|
||||||
this.UpdateUnlocksForSheet<AetherCurrentCompFlgSet>();
|
this.UpdateUnlocksForSheet<AetherCurrentCompFlgSet>();
|
||||||
|
|
@ -688,7 +736,6 @@ internal unsafe class UnlockState : IInternalDisposableService, IUnlockState
|
||||||
// - EmjCostume
|
// - EmjCostume
|
||||||
|
|
||||||
// Probably not happening, because it requires fetching data from server:
|
// Probably not happening, because it requires fetching data from server:
|
||||||
// - Achievements
|
|
||||||
// - Titles
|
// - Titles
|
||||||
// - Bozjan Field Notes
|
// - Bozjan Field Notes
|
||||||
// - Support/Phantom Jobs, which require to be in Occult Crescent, because it checks the jobs level for != 0
|
// - Support/Phantom Jobs, which require to be in Occult Crescent, because it checks the jobs level for != 0
|
||||||
|
|
@ -712,16 +759,21 @@ internal unsafe class UnlockState : IInternalDisposableService, IUnlockState
|
||||||
|
|
||||||
// Log.Verbose($"Unlock detected: {typeof(T).Name}#{row.RowId}");
|
// Log.Verbose($"Unlock detected: {typeof(T).Name}#{row.RowId}");
|
||||||
|
|
||||||
foreach (var action in Delegate.EnumerateInvocationList(this.Unlock))
|
this.RaiseUnlockSafely((RowRef)rowRef);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RaiseUnlockSafely(RowRef rowRef)
|
||||||
|
{
|
||||||
|
foreach (var action in Delegate.EnumerateInvocationList(this.Unlock))
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
try
|
action(rowRef);
|
||||||
{
|
}
|
||||||
action((RowRef)rowRef);
|
catch (Exception ex)
|
||||||
}
|
{
|
||||||
catch (Exception ex)
|
Log.Error(ex, "Exception during raise of {handler}", action.Method);
|
||||||
{
|
|
||||||
Log.Error(ex, "Exception during raise of {handler}", action.Method);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -751,6 +803,12 @@ internal class UnlockStatePluginScoped : IInternalDisposableService, IUnlockStat
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public event IUnlockState.UnlockDelegate? Unlock;
|
public event IUnlockState.UnlockDelegate? Unlock;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool IsAchievementListLoaded => this.unlockStateService.IsAchievementListLoaded;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool IsAchievementComplete(AchievementSheet row) => this.unlockStateService.IsAchievementComplete(row);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public bool IsActionUnlocked(ActionSheet row) => this.unlockStateService.IsActionUnlocked(row);
|
public bool IsActionUnlocked(ActionSheet row) => this.unlockStateService.IsActionUnlocked(row);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
|
|
||||||
using Lumina.Excel;
|
using Lumina.Excel;
|
||||||
using Lumina.Excel.Sheets;
|
using Lumina.Excel.Sheets;
|
||||||
|
|
||||||
|
|
@ -23,6 +21,19 @@ public interface IUnlockState : IDalamudService
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event UnlockDelegate? Unlock;
|
event UnlockDelegate? Unlock;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether the full Achievements list was received.
|
||||||
|
/// </summary>
|
||||||
|
bool IsAchievementListLoaded { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether the specified Achievement is completed.<br/>
|
||||||
|
/// Requires that the player requested the Achievements list (can be chcked with <see cref="IsAchievementListLoaded"/>).
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="row">The Achievement row to check.</param>
|
||||||
|
/// <returns><see langword="true"/> if completed; otherwise, <see langword="false"/>.</returns>
|
||||||
|
bool IsAchievementComplete(Achievement row);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Determines whether the specified Action is unlocked.
|
/// Determines whether the specified Action is unlocked.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue