mirror of
https://github.com/goatcorp/Dalamud.git
synced 2026-01-02 05:43:40 +01:00
Add events for ClassJob and Level change (#1985)
* Add events for ClassJob and Level change * Correctly handle uiModuleHandlePacketHook * Update Dalamud/Plugin/Services/IClientState.cs Co-authored-by: KazWolfe <KazWolfe@users.noreply.github.com>
This commit is contained in:
parent
7369878789
commit
9bb9b3acf4
2 changed files with 124 additions and 26 deletions
|
|
@ -13,7 +13,10 @@ using Dalamud.IoC.Internal;
|
||||||
using Dalamud.Logging.Internal;
|
using Dalamud.Logging.Internal;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Game.Event;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||||
|
|
||||||
using Lumina.Excel.GeneratedSheets;
|
using Lumina.Excel.GeneratedSheets;
|
||||||
|
|
@ -29,10 +32,11 @@ namespace Dalamud.Game.ClientState;
|
||||||
internal sealed class ClientState : IInternalDisposableService, IClientState
|
internal sealed class ClientState : IInternalDisposableService, IClientState
|
||||||
{
|
{
|
||||||
private static readonly ModuleLog Log = new("ClientState");
|
private static readonly ModuleLog Log = new("ClientState");
|
||||||
|
|
||||||
private readonly GameLifecycle lifecycle;
|
private readonly GameLifecycle lifecycle;
|
||||||
private readonly ClientStateAddressResolver address;
|
private readonly ClientStateAddressResolver address;
|
||||||
private readonly Hook<SetupTerritoryTypeDelegate> setupTerritoryTypeHook;
|
private readonly Hook<SetupTerritoryTypeDelegate> setupTerritoryTypeHook;
|
||||||
|
private readonly Hook<UIModule.Delegates.HandlePacket> uiModuleHandlePacketHook;
|
||||||
|
|
||||||
[ServiceManager.ServiceDependency]
|
[ServiceManager.ServiceDependency]
|
||||||
private readonly Framework framework = Service<Framework>.Get();
|
private readonly Framework framework = Service<Framework>.Get();
|
||||||
|
|
@ -44,7 +48,7 @@ internal sealed class ClientState : IInternalDisposableService, IClientState
|
||||||
private bool lastFramePvP;
|
private bool lastFramePvP;
|
||||||
|
|
||||||
[ServiceManager.ServiceConstructor]
|
[ServiceManager.ServiceConstructor]
|
||||||
private ClientState(TargetSigScanner sigScanner, Dalamud dalamud, GameLifecycle lifecycle)
|
private unsafe ClientState(TargetSigScanner sigScanner, Dalamud dalamud, GameLifecycle lifecycle)
|
||||||
{
|
{
|
||||||
this.lifecycle = lifecycle;
|
this.lifecycle = lifecycle;
|
||||||
this.address = new ClientStateAddressResolver();
|
this.address = new ClientStateAddressResolver();
|
||||||
|
|
@ -57,20 +61,28 @@ internal sealed class ClientState : IInternalDisposableService, IClientState
|
||||||
Log.Verbose($"SetupTerritoryType address {Util.DescribeAddress(this.address.SetupTerritoryType)}");
|
Log.Verbose($"SetupTerritoryType address {Util.DescribeAddress(this.address.SetupTerritoryType)}");
|
||||||
|
|
||||||
this.setupTerritoryTypeHook = Hook<SetupTerritoryTypeDelegate>.FromAddress(this.address.SetupTerritoryType, this.SetupTerritoryTypeDetour);
|
this.setupTerritoryTypeHook = Hook<SetupTerritoryTypeDelegate>.FromAddress(this.address.SetupTerritoryType, this.SetupTerritoryTypeDetour);
|
||||||
|
this.uiModuleHandlePacketHook = Hook<UIModule.Delegates.HandlePacket>.FromAddress((nint)UIModule.StaticVirtualTablePointer->HandlePacket, this.UIModuleHandlePacketDetour);
|
||||||
|
|
||||||
this.framework.Update += this.FrameworkOnOnUpdateEvent;
|
this.framework.Update += this.FrameworkOnOnUpdateEvent;
|
||||||
|
|
||||||
this.networkHandlers.CfPop += this.NetworkHandlersOnCfPop;
|
this.networkHandlers.CfPop += this.NetworkHandlersOnCfPop;
|
||||||
|
|
||||||
this.setupTerritoryTypeHook.Enable();
|
this.setupTerritoryTypeHook.Enable();
|
||||||
|
this.uiModuleHandlePacketHook.Enable();
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||||
private delegate IntPtr SetupTerritoryTypeDelegate(IntPtr manager, ushort terriType);
|
private unsafe delegate void SetupTerritoryTypeDelegate(EventFramework* eventFramework, ushort terriType);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public event Action<ushort>? TerritoryChanged;
|
public event Action<ushort>? TerritoryChanged;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public event IClientState.ClassJobChangeDelegate? ClassJobChanged;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public event IClientState.LevelChangeDelegate? LevelChanged;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public event Action? Login;
|
public event Action? Login;
|
||||||
|
|
||||||
|
|
@ -124,25 +136,25 @@ internal sealed class ClientState : IInternalDisposableService, IClientState
|
||||||
/// Gets client state address resolver.
|
/// Gets client state address resolver.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal ClientStateAddressResolver AddressResolver => this.address;
|
internal ClientStateAddressResolver AddressResolver => this.address;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public bool IsClientIdle(out ConditionFlag blockingFlag)
|
public bool IsClientIdle(out ConditionFlag blockingFlag)
|
||||||
{
|
{
|
||||||
blockingFlag = 0;
|
blockingFlag = 0;
|
||||||
if (this.LocalPlayer is null) return true;
|
if (this.LocalPlayer is null) return true;
|
||||||
|
|
||||||
var condition = Service<Conditions.Condition>.GetNullable();
|
var condition = Service<Conditions.Condition>.GetNullable();
|
||||||
|
|
||||||
var blockingConditions = condition.AsReadOnlySet().Except([
|
var blockingConditions = condition.AsReadOnlySet().Except([
|
||||||
ConditionFlag.NormalConditions,
|
ConditionFlag.NormalConditions,
|
||||||
ConditionFlag.Jumping,
|
ConditionFlag.Jumping,
|
||||||
ConditionFlag.Mounted,
|
ConditionFlag.Mounted,
|
||||||
ConditionFlag.UsingParasol]);
|
ConditionFlag.UsingParasol]);
|
||||||
|
|
||||||
blockingFlag = blockingConditions.FirstOrDefault();
|
blockingFlag = blockingConditions.FirstOrDefault();
|
||||||
return blockingFlag == 0;
|
return blockingFlag == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public bool IsClientIdle() => this.IsClientIdle(out _);
|
public bool IsClientIdle() => this.IsClientIdle(out _);
|
||||||
|
|
||||||
|
|
@ -152,18 +164,66 @@ internal sealed class ClientState : IInternalDisposableService, IClientState
|
||||||
void IInternalDisposableService.DisposeService()
|
void IInternalDisposableService.DisposeService()
|
||||||
{
|
{
|
||||||
this.setupTerritoryTypeHook.Dispose();
|
this.setupTerritoryTypeHook.Dispose();
|
||||||
|
this.uiModuleHandlePacketHook.Dispose();
|
||||||
this.framework.Update -= this.FrameworkOnOnUpdateEvent;
|
this.framework.Update -= this.FrameworkOnOnUpdateEvent;
|
||||||
this.networkHandlers.CfPop -= this.NetworkHandlersOnCfPop;
|
this.networkHandlers.CfPop -= this.NetworkHandlersOnCfPop;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IntPtr SetupTerritoryTypeDetour(IntPtr manager, ushort terriType)
|
private unsafe void SetupTerritoryTypeDetour(EventFramework* eventFramework, ushort territoryType)
|
||||||
{
|
{
|
||||||
this.TerritoryType = terriType;
|
this.TerritoryType = territoryType;
|
||||||
this.TerritoryChanged?.InvokeSafely(terriType);
|
this.TerritoryChanged?.InvokeSafely(territoryType);
|
||||||
|
|
||||||
Log.Debug("TerritoryType changed: {0}", terriType);
|
Log.Debug("TerritoryType changed: {0}", territoryType);
|
||||||
|
|
||||||
return this.setupTerritoryTypeHook.Original(manager, terriType);
|
this.setupTerritoryTypeHook.Original(eventFramework, territoryType);
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe void UIModuleHandlePacketDetour(UIModule* thisPtr, UIModulePacketType type, uint uintParam, void* packet)
|
||||||
|
{
|
||||||
|
this.uiModuleHandlePacketHook!.Original(thisPtr, type, uintParam, packet);
|
||||||
|
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case UIModulePacketType.ClassJobChange:
|
||||||
|
{
|
||||||
|
var classJobId = uintParam;
|
||||||
|
|
||||||
|
foreach (var action in this.ClassJobChanged.GetInvocationList().Cast<IClientState.ClassJobChangeDelegate>())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
action(classJobId);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, "Exception during raise of {handler}", action.Method);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case UIModulePacketType.LevelChange:
|
||||||
|
{
|
||||||
|
var classJobId = *(uint*)packet;
|
||||||
|
var level = *(ushort*)((nint)packet + 4);
|
||||||
|
|
||||||
|
foreach (var action in this.LevelChanged.GetInvocationList().Cast<IClientState.LevelChangeDelegate>())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
action(classJobId, level);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, "Exception during raise of {handler}", action.Method);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void NetworkHandlersOnCfPop(ContentFinderCondition e)
|
private void NetworkHandlersOnCfPop(ContentFinderCondition e)
|
||||||
|
|
@ -240,28 +300,36 @@ internal class ClientStatePluginScoped : IInternalDisposableService, IClientStat
|
||||||
internal ClientStatePluginScoped()
|
internal ClientStatePluginScoped()
|
||||||
{
|
{
|
||||||
this.clientStateService.TerritoryChanged += this.TerritoryChangedForward;
|
this.clientStateService.TerritoryChanged += this.TerritoryChangedForward;
|
||||||
|
this.clientStateService.ClassJobChanged += this.ClassJobChangedForward;
|
||||||
|
this.clientStateService.LevelChanged += this.LevelChangedForward;
|
||||||
this.clientStateService.Login += this.LoginForward;
|
this.clientStateService.Login += this.LoginForward;
|
||||||
this.clientStateService.Logout += this.LogoutForward;
|
this.clientStateService.Logout += this.LogoutForward;
|
||||||
this.clientStateService.EnterPvP += this.EnterPvPForward;
|
this.clientStateService.EnterPvP += this.EnterPvPForward;
|
||||||
this.clientStateService.LeavePvP += this.ExitPvPForward;
|
this.clientStateService.LeavePvP += this.ExitPvPForward;
|
||||||
this.clientStateService.CfPop += this.ContentFinderPopForward;
|
this.clientStateService.CfPop += this.ContentFinderPopForward;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public event Action<ushort>? TerritoryChanged;
|
public event Action<ushort>? TerritoryChanged;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public event IClientState.ClassJobChangeDelegate? ClassJobChanged;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public event IClientState.LevelChangeDelegate? LevelChanged;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public event Action? Login;
|
public event Action? Login;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public event Action? Logout;
|
public event Action? Logout;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public event Action? EnterPvP;
|
public event Action? EnterPvP;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public event Action? LeavePvP;
|
public event Action? LeavePvP;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public event Action<ContentFinderCondition>? CfPop;
|
public event Action<ContentFinderCondition>? CfPop;
|
||||||
|
|
||||||
|
|
@ -270,7 +338,7 @@ internal class ClientStatePluginScoped : IInternalDisposableService, IClientStat
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public ushort TerritoryType => this.clientStateService.TerritoryType;
|
public ushort TerritoryType => this.clientStateService.TerritoryType;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public uint MapId => this.clientStateService.MapId;
|
public uint MapId => this.clientStateService.MapId;
|
||||||
|
|
||||||
|
|
@ -302,6 +370,8 @@ internal class ClientStatePluginScoped : IInternalDisposableService, IClientStat
|
||||||
void IInternalDisposableService.DisposeService()
|
void IInternalDisposableService.DisposeService()
|
||||||
{
|
{
|
||||||
this.clientStateService.TerritoryChanged -= this.TerritoryChangedForward;
|
this.clientStateService.TerritoryChanged -= this.TerritoryChangedForward;
|
||||||
|
this.clientStateService.ClassJobChanged -= this.ClassJobChangedForward;
|
||||||
|
this.clientStateService.LevelChanged -= this.LevelChangedForward;
|
||||||
this.clientStateService.Login -= this.LoginForward;
|
this.clientStateService.Login -= this.LoginForward;
|
||||||
this.clientStateService.Logout -= this.LogoutForward;
|
this.clientStateService.Logout -= this.LogoutForward;
|
||||||
this.clientStateService.EnterPvP -= this.EnterPvPForward;
|
this.clientStateService.EnterPvP -= this.EnterPvPForward;
|
||||||
|
|
@ -317,13 +387,17 @@ internal class ClientStatePluginScoped : IInternalDisposableService, IClientStat
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TerritoryChangedForward(ushort territoryId) => this.TerritoryChanged?.Invoke(territoryId);
|
private void TerritoryChangedForward(ushort territoryId) => this.TerritoryChanged?.Invoke(territoryId);
|
||||||
|
|
||||||
|
private void ClassJobChangedForward(uint classJobId) => this.ClassJobChanged?.Invoke(classJobId);
|
||||||
|
|
||||||
|
private void LevelChangedForward(uint classJobId, uint level) => this.LevelChanged?.Invoke(classJobId, level);
|
||||||
|
|
||||||
private void LoginForward() => this.Login?.Invoke();
|
private void LoginForward() => this.Login?.Invoke();
|
||||||
|
|
||||||
private void LogoutForward() => this.Logout?.Invoke();
|
private void LogoutForward() => this.Logout?.Invoke();
|
||||||
|
|
||||||
private void EnterPvPForward() => this.EnterPvP?.Invoke();
|
private void EnterPvPForward() => this.EnterPvP?.Invoke();
|
||||||
|
|
||||||
private void ExitPvPForward() => this.LeavePvP?.Invoke();
|
private void ExitPvPForward() => this.LeavePvP?.Invoke();
|
||||||
|
|
||||||
private void ContentFinderPopForward(ContentFinderCondition cfc) => this.CfPop?.Invoke(cfc);
|
private void ContentFinderPopForward(ContentFinderCondition cfc) => this.CfPop?.Invoke(cfc);
|
||||||
|
|
|
||||||
|
|
@ -9,11 +9,35 @@ namespace Dalamud.Plugin.Services;
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IClientState
|
public interface IClientState
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A delegate type used for the <see cref="ClassJobChanged"/> event.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="classJobId">The new ClassJob id.</param>
|
||||||
|
public delegate void ClassJobChangeDelegate(uint classJobId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A delegate type used for the <see cref="LevelChanged"/> event.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="classJobId">The ClassJob id.</param>
|
||||||
|
/// <param name="level">The level of the corresponding ClassJob.</param>
|
||||||
|
public delegate void LevelChangeDelegate(uint classJobId, uint level);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event that gets fired when the current Territory changes.
|
/// Event that gets fired when the current Territory changes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event Action<ushort> TerritoryChanged;
|
public event Action<ushort> TerritoryChanged;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event that fires when a characters ClassJob changed.
|
||||||
|
/// </summary>
|
||||||
|
public event ClassJobChangeDelegate? ClassJobChanged;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event that fires when <em>any</em> character level changes, including levels
|
||||||
|
/// for a not-currently-active ClassJob (e.g. PvP matches, DoH/DoL).
|
||||||
|
/// </summary>
|
||||||
|
public event LevelChangeDelegate? LevelChanged;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event that fires when a character is logging in, and the local character object is available.
|
/// Event that fires when a character is logging in, and the local character object is available.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue