mirror of
https://github.com/goatcorp/Dalamud.git
synced 2026-02-25 14:11:48 +01:00
commit
2e9ca24803
34 changed files with 1689 additions and 231 deletions
26
Dalamud.sln
26
Dalamud.sln
|
|
@ -75,6 +75,17 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lumina.Excel.Generator", "l
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lumina.Excel", "lib\Lumina.Excel\src\Lumina.Excel\Lumina.Excel.csproj", "{88FB719B-EB41-73C5-8D25-C03E0C69904F}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lumina.Excel", "lib\Lumina.Excel\src\Lumina.Excel\Lumina.Excel.csproj", "{88FB719B-EB41-73C5-8D25-C03E0C69904F}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Source Generators", "Source Generators", "{50BEC23B-FFFD-427B-A95D-27E1D1958FFF}"
|
||||||
|
ProjectSection(SolutionItems) = preProject
|
||||||
|
generators\Directory.Build.props = generators\Directory.Build.props
|
||||||
|
EndProjectSection
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dalamud.EnumGenerator", "generators\Dalamud.EnumGenerator\Dalamud.EnumGenerator\Dalamud.EnumGenerator.csproj", "{27AA9F87-D2AA-41D9-A559-0F1EBA38C5F8}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dalamud.EnumGenerator.Sample", "generators\Dalamud.EnumGenerator\Dalamud.EnumGenerator.Sample\Dalamud.EnumGenerator.Sample.csproj", "{8CDAEB2D-5022-450A-A97F-181C6270185F}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dalamud.EnumGenerator.Tests", "generators\Dalamud.EnumGenerator\Dalamud.EnumGenerator.Tests\Dalamud.EnumGenerator.Tests.csproj", "{F5D92D2D-D36F-4471-B657-8B9AA6C98AD6}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
|
@ -173,6 +184,18 @@ Global
|
||||||
{88FB719B-EB41-73C5-8D25-C03E0C69904F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{88FB719B-EB41-73C5-8D25-C03E0C69904F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{88FB719B-EB41-73C5-8D25-C03E0C69904F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{88FB719B-EB41-73C5-8D25-C03E0C69904F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{88FB719B-EB41-73C5-8D25-C03E0C69904F}.Release|Any CPU.Build.0 = Release|Any CPU
|
{88FB719B-EB41-73C5-8D25-C03E0C69904F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{27AA9F87-D2AA-41D9-A559-0F1EBA38C5F8}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||||
|
{27AA9F87-D2AA-41D9-A559-0F1EBA38C5F8}.Debug|Any CPU.Build.0 = Debug|x64
|
||||||
|
{27AA9F87-D2AA-41D9-A559-0F1EBA38C5F8}.Release|Any CPU.ActiveCfg = Release|x64
|
||||||
|
{27AA9F87-D2AA-41D9-A559-0F1EBA38C5F8}.Release|Any CPU.Build.0 = Release|x64
|
||||||
|
{8CDAEB2D-5022-450A-A97F-181C6270185F}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||||
|
{8CDAEB2D-5022-450A-A97F-181C6270185F}.Debug|Any CPU.Build.0 = Debug|x64
|
||||||
|
{8CDAEB2D-5022-450A-A97F-181C6270185F}.Release|Any CPU.ActiveCfg = Release|x64
|
||||||
|
{8CDAEB2D-5022-450A-A97F-181C6270185F}.Release|Any CPU.Build.0 = Release|x64
|
||||||
|
{F5D92D2D-D36F-4471-B657-8B9AA6C98AD6}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||||
|
{F5D92D2D-D36F-4471-B657-8B9AA6C98AD6}.Debug|Any CPU.Build.0 = Debug|x64
|
||||||
|
{F5D92D2D-D36F-4471-B657-8B9AA6C98AD6}.Release|Any CPU.ActiveCfg = Release|x64
|
||||||
|
{F5D92D2D-D36F-4471-B657-8B9AA6C98AD6}.Release|Any CPU.Build.0 = Release|x64
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|
@ -197,6 +220,9 @@ Global
|
||||||
{02EA681E-C7D8-13C7-8484-4AC65E1B71E8} = {E15BDA6D-E881-4482-94BA-BE5527E917FF}
|
{02EA681E-C7D8-13C7-8484-4AC65E1B71E8} = {E15BDA6D-E881-4482-94BA-BE5527E917FF}
|
||||||
{5A44DF0C-C9DA-940F-4D6B-4A11D13AEA3D} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
|
{5A44DF0C-C9DA-940F-4D6B-4A11D13AEA3D} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
|
||||||
{88FB719B-EB41-73C5-8D25-C03E0C69904F} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
|
{88FB719B-EB41-73C5-8D25-C03E0C69904F} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
|
||||||
|
{27AA9F87-D2AA-41D9-A559-0F1EBA38C5F8} = {50BEC23B-FFFD-427B-A95D-27E1D1958FFF}
|
||||||
|
{8CDAEB2D-5022-450A-A97F-181C6270185F} = {50BEC23B-FFFD-427B-A95D-27E1D1958FFF}
|
||||||
|
{F5D92D2D-D36F-4471-B657-8B9AA6C98AD6} = {50BEC23B-FFFD-427B-A95D-27E1D1958FFF}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {79B65AC9-C940-410E-AB61-7EA7E12C7599}
|
SolutionGuid = {79B65AC9-C940-410E-AB61-7EA7E12C7599}
|
||||||
|
|
|
||||||
|
|
@ -88,6 +88,15 @@
|
||||||
<PackageReference Include="TerraFX.Interop.Windows" />
|
<PackageReference Include="TerraFX.Interop.Windows" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\generators\Dalamud.EnumGenerator\Dalamud.EnumGenerator\Dalamud.EnumGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Remove="EnumCloneMap.txt"/>
|
||||||
|
<AdditionalFiles Include="EnumCloneMap.txt" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<EmbeddedResource Include="Interface\ImGuiBackend\Renderers\imgui-frag.hlsl.bytes">
|
<EmbeddedResource Include="Interface\ImGuiBackend\Renderers\imgui-frag.hlsl.bytes">
|
||||||
<LogicalName>imgui-frag.hlsl.bytes</LogicalName>
|
<LogicalName>imgui-frag.hlsl.bytes</LogicalName>
|
||||||
|
|
|
||||||
3
Dalamud/EnumCloneMap.txt
Normal file
3
Dalamud/EnumCloneMap.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Format: Target.Full.TypeName = Source.Full.EnumTypeName
|
||||||
|
# Example: Generate a local enum MyGeneratedEnum in namespace Sample.Gen mapped to SourceEnums.SampleSourceEnum
|
||||||
|
Dalamud.Game.Agent.AgentId = FFXIVClientStructs.FFXIV.Client.UI.Agent.AgentId
|
||||||
39
Dalamud/Game/Agent/AgentArgTypes/AgentArgs.cs
Normal file
39
Dalamud/Game/Agent/AgentArgTypes/AgentArgs.cs
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
using Dalamud.Game.NativeWrapper;
|
||||||
|
|
||||||
|
namespace Dalamud.Game.Agent.AgentArgTypes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Base class for AgentLifecycle AgentArgTypes.
|
||||||
|
/// </summary>
|
||||||
|
public unsafe class AgentArgs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="AgentArgs"/> class.
|
||||||
|
/// </summary>
|
||||||
|
internal AgentArgs()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the pointer to the Agents AgentInterface*.
|
||||||
|
/// </summary>
|
||||||
|
public AgentInterfacePtr Agent { get; internal set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the agent id.
|
||||||
|
/// </summary>
|
||||||
|
public AgentId AgentId { get; internal set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the type of these args.
|
||||||
|
/// </summary>
|
||||||
|
public virtual AgentArgsType Type => AgentArgsType.Generic;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the typed pointer to the Agents AgentInterface*.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">AgentInterface.</typeparam>
|
||||||
|
/// <returns>Typed pointer to contained Agents AgentInterface.</returns>
|
||||||
|
public T* GetAgentPointer<T>() where T : unmanaged
|
||||||
|
=> (T*)this.Agent.Address;
|
||||||
|
}
|
||||||
22
Dalamud/Game/Agent/AgentArgTypes/AgentClassJobChangeArgs.cs
Normal file
22
Dalamud/Game/Agent/AgentArgTypes/AgentClassJobChangeArgs.cs
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
namespace Dalamud.Game.Agent.AgentArgTypes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Agent argument data for game events.
|
||||||
|
/// </summary>
|
||||||
|
public class AgentClassJobChangeArgs : AgentArgs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="AgentClassJobChangeArgs"/> class.
|
||||||
|
/// </summary>
|
||||||
|
internal AgentClassJobChangeArgs()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override AgentArgsType Type => AgentArgsType.ClassJobChange;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating what the new ClassJob is.
|
||||||
|
/// </summary>
|
||||||
|
public byte ClassJobId { get; set; }
|
||||||
|
}
|
||||||
22
Dalamud/Game/Agent/AgentArgTypes/AgentGameEventArgs.cs
Normal file
22
Dalamud/Game/Agent/AgentArgTypes/AgentGameEventArgs.cs
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
namespace Dalamud.Game.Agent.AgentArgTypes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Agent argument data for game events.
|
||||||
|
/// </summary>
|
||||||
|
public class AgentGameEventArgs : AgentArgs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="AgentGameEventArgs"/> class.
|
||||||
|
/// </summary>
|
||||||
|
internal AgentGameEventArgs()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override AgentArgsType Type => AgentArgsType.GameEvent;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value representing which gameEvent was triggered.
|
||||||
|
/// </summary>
|
||||||
|
public int GameEvent { get; set; }
|
||||||
|
}
|
||||||
27
Dalamud/Game/Agent/AgentArgTypes/AgentLevelChangeArgs.cs
Normal file
27
Dalamud/Game/Agent/AgentArgTypes/AgentLevelChangeArgs.cs
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
namespace Dalamud.Game.Agent.AgentArgTypes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Agent argument data for game events.
|
||||||
|
/// </summary>
|
||||||
|
public class AgentLevelChangeArgs : AgentArgs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="AgentLevelChangeArgs"/> class.
|
||||||
|
/// </summary>
|
||||||
|
internal AgentLevelChangeArgs()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override AgentArgsType Type => AgentArgsType.LevelChange;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating which ClassJob was switched to.
|
||||||
|
/// </summary>
|
||||||
|
public byte ClassJobId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating what the new level is.
|
||||||
|
/// </summary>
|
||||||
|
public ushort Level { get; set; }
|
||||||
|
}
|
||||||
37
Dalamud/Game/Agent/AgentArgTypes/AgentReceiveEventArgs.cs
Normal file
37
Dalamud/Game/Agent/AgentArgTypes/AgentReceiveEventArgs.cs
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
namespace Dalamud.Game.Agent.AgentArgTypes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Agent argument data for ReceiveEvent events.
|
||||||
|
/// </summary>
|
||||||
|
public class AgentReceiveEventArgs : AgentArgs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="AgentReceiveEventArgs"/> class.
|
||||||
|
/// </summary>
|
||||||
|
internal AgentReceiveEventArgs()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override AgentArgsType Type => AgentArgsType.ReceiveEvent;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the AtkValue return value for this event message.
|
||||||
|
/// </summary>
|
||||||
|
public nint ReturnValue { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the AtkValue array for this event message.
|
||||||
|
/// </summary>
|
||||||
|
public nint AtkValues { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the AtkValue count for this event message.
|
||||||
|
/// </summary>
|
||||||
|
public uint ValueCount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the event kind for this event message.
|
||||||
|
/// </summary>
|
||||||
|
public ulong EventKind { get; set; }
|
||||||
|
}
|
||||||
32
Dalamud/Game/Agent/AgentArgsType.cs
Normal file
32
Dalamud/Game/Agent/AgentArgsType.cs
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
namespace Dalamud.Game.Agent;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enumeration for available AgentLifecycle arg data.
|
||||||
|
/// </summary>
|
||||||
|
public enum AgentArgsType
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Generic arg type that contains no meaningful data.
|
||||||
|
/// </summary>
|
||||||
|
Generic,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Contains argument data for ReceiveEvent.
|
||||||
|
/// </summary>
|
||||||
|
ReceiveEvent,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Contains argument data for GameEvent.
|
||||||
|
/// </summary>
|
||||||
|
GameEvent,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Contains argument data for LevelChange.
|
||||||
|
/// </summary>
|
||||||
|
LevelChange,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Contains argument data for ClassJobChange.
|
||||||
|
/// </summary>
|
||||||
|
ClassJobChange,
|
||||||
|
}
|
||||||
87
Dalamud/Game/Agent/AgentEvent.cs
Normal file
87
Dalamud/Game/Agent/AgentEvent.cs
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
namespace Dalamud.Game.Agent;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enumeration for available AgentLifecycle events.
|
||||||
|
/// </summary>
|
||||||
|
public enum AgentEvent
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An event that is fired before the agent processes its Receive Event Function.
|
||||||
|
/// </summary>
|
||||||
|
PreReceiveEvent,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event that is fired after the agent has processed its Receive Event Function.
|
||||||
|
/// </summary>
|
||||||
|
PostReceiveEvent,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event that is fired before the agent processes its Filtered Receive Event Function.
|
||||||
|
/// </summary>
|
||||||
|
PreReceiveFilteredEvent,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event that is fired after the agent has processed its Filtered Receive Event Function.
|
||||||
|
/// </summary>
|
||||||
|
PostReceiveFilteredEvent,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event that is fired before the agent processes its Show Function.
|
||||||
|
/// </summary>
|
||||||
|
PreShow,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event that is fired after the agent has processed its Show Function.
|
||||||
|
/// </summary>
|
||||||
|
PostShow,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event that is fired before the agent processes its Hide Function.
|
||||||
|
/// </summary>
|
||||||
|
PreHide,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event that is fired after the agent has processed its Hide Function.
|
||||||
|
/// </summary>
|
||||||
|
PostHide,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event that is fired before the agent processes its Update Function.
|
||||||
|
/// </summary>
|
||||||
|
PreUpdate,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event that is fired after the agent has processed its Update Function.
|
||||||
|
/// </summary>
|
||||||
|
PostUpdate,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event that is fired before the agent processes its Game Event Function.
|
||||||
|
/// </summary>
|
||||||
|
PreGameEvent,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event that is fired after the agent has processed its Game Event Function.
|
||||||
|
/// </summary>
|
||||||
|
PostGameEvent,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event that is fired before the agent processes its Game Event Function.
|
||||||
|
/// </summary>
|
||||||
|
PreLevelChange,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event that is fired after the agent has processed its Level Change Function.
|
||||||
|
/// </summary>
|
||||||
|
PostLevelChange,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event that is fired before the agent processes its ClassJob Change Function.
|
||||||
|
/// </summary>
|
||||||
|
PreClassJobChange,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event that is fired after the agent has processed its ClassJob Change Function.
|
||||||
|
/// </summary>
|
||||||
|
PostClassJobChange,
|
||||||
|
}
|
||||||
315
Dalamud/Game/Agent/AgentLifecycle.cs
Normal file
315
Dalamud/Game/Agent/AgentLifecycle.cs
Normal file
|
|
@ -0,0 +1,315 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
using Dalamud.Game.Agent.AgentArgTypes;
|
||||||
|
using Dalamud.Hooking;
|
||||||
|
using Dalamud.IoC;
|
||||||
|
using Dalamud.IoC.Internal;
|
||||||
|
using Dalamud.Logging.Internal;
|
||||||
|
using Dalamud.Plugin.Services;
|
||||||
|
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||||
|
using FFXIVClientStructs.Interop;
|
||||||
|
|
||||||
|
namespace Dalamud.Game.Agent;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This class provides events for in-game agent lifecycles.
|
||||||
|
/// </summary>
|
||||||
|
[ServiceManager.EarlyLoadedService]
|
||||||
|
internal unsafe class AgentLifecycle : IInternalDisposableService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a list of all allocated agent virtual tables.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly List<AgentVirtualTable> AllocatedTables = [];
|
||||||
|
|
||||||
|
private static readonly ModuleLog Log = new("AgentLifecycle");
|
||||||
|
|
||||||
|
[ServiceManager.ServiceDependency]
|
||||||
|
private readonly Framework framework = Service<Framework>.Get();
|
||||||
|
|
||||||
|
private Hook<AgentModule.Delegates.Ctor>? onInitializeAgentsHook;
|
||||||
|
private bool isInvokingListeners;
|
||||||
|
|
||||||
|
[ServiceManager.ServiceConstructor]
|
||||||
|
private AgentLifecycle()
|
||||||
|
{
|
||||||
|
var agentModuleInstance = AgentModule.Instance();
|
||||||
|
|
||||||
|
// Hook is only used to determine appropriate timing for replacing Agent Virtual Tables
|
||||||
|
// If the agent module is already initialized, then we can replace the tables safely.
|
||||||
|
if (agentModuleInstance is null)
|
||||||
|
{
|
||||||
|
this.onInitializeAgentsHook = Hook<AgentModule.Delegates.Ctor>.FromAddress((nint)AgentModule.MemberFunctionPointers.Ctor, this.OnAgentModuleInitialize);
|
||||||
|
this.onInitializeAgentsHook.Enable();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// For safety because this might be injected async, we will make sure we are on the main thread first.
|
||||||
|
this.framework.RunOnFrameworkThread(() => this.ReplaceVirtualTables(agentModuleInstance));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a list of all AgentLifecycle Event Listeners.
|
||||||
|
/// </summary> <br/>
|
||||||
|
/// Mapping is: EventType -> ListenerList
|
||||||
|
internal Dictionary<AgentEvent, Dictionary<AgentId, HashSet<AgentLifecycleEventListener>>> EventListeners { get; } = [];
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
void IInternalDisposableService.DisposeService()
|
||||||
|
{
|
||||||
|
this.onInitializeAgentsHook?.Dispose();
|
||||||
|
this.onInitializeAgentsHook = null;
|
||||||
|
|
||||||
|
AllocatedTables.ForEach(entry => entry.Dispose());
|
||||||
|
AllocatedTables.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Register a listener for the target event and agent.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="listener">The listener to register.</param>
|
||||||
|
internal void RegisterListener(AgentLifecycleEventListener listener)
|
||||||
|
{
|
||||||
|
this.framework.RunOnTick(() =>
|
||||||
|
{
|
||||||
|
if (!this.EventListeners.ContainsKey(listener.EventType))
|
||||||
|
{
|
||||||
|
if (!this.EventListeners.TryAdd(listener.EventType, []))
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: uint.MaxValue is a valid agent id, as that will trigger on any agent for this event type
|
||||||
|
if (!this.EventListeners[listener.EventType].ContainsKey(listener.AgentId))
|
||||||
|
{
|
||||||
|
if (!this.EventListeners[listener.EventType].TryAdd(listener.AgentId, []))
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.EventListeners[listener.EventType][listener.AgentId].Add(listener);
|
||||||
|
},
|
||||||
|
delayTicks: this.isInvokingListeners ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unregisters the listener from events.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="listener">The listener to unregister.</param>
|
||||||
|
internal void UnregisterListener(AgentLifecycleEventListener listener)
|
||||||
|
{
|
||||||
|
this.framework.RunOnTick(() =>
|
||||||
|
{
|
||||||
|
if (this.EventListeners.TryGetValue(listener.EventType, out var agentListeners))
|
||||||
|
{
|
||||||
|
if (agentListeners.TryGetValue(listener.AgentId, out var agentListener))
|
||||||
|
{
|
||||||
|
agentListener.Remove(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
delayTicks: this.isInvokingListeners ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoke listeners for the specified event type.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="eventType">Event Type.</param>
|
||||||
|
/// <param name="args">AgentARgs.</param>
|
||||||
|
/// <param name="blame">What to blame on errors.</param>
|
||||||
|
internal void InvokeListenersSafely(AgentEvent eventType, AgentArgs args, [CallerMemberName] string blame = "")
|
||||||
|
{
|
||||||
|
this.isInvokingListeners = true;
|
||||||
|
|
||||||
|
// Early return if we don't have any listeners of this type
|
||||||
|
if (!this.EventListeners.TryGetValue(eventType, out var agentListeners)) return;
|
||||||
|
|
||||||
|
// Handle listeners for this event type that don't care which agent is triggering it
|
||||||
|
if (agentListeners.TryGetValue((AgentId)uint.MaxValue, out var globalListeners))
|
||||||
|
{
|
||||||
|
foreach (var listener in globalListeners)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
listener.FunctionDelegate.Invoke(eventType, args);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, $"Exception in {blame} during {eventType} invoke, for global agent event listener.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle listeners that are listening for this agent and event type specifically
|
||||||
|
if (agentListeners.TryGetValue(args.AgentId, out var agentListener))
|
||||||
|
{
|
||||||
|
foreach (var listener in agentListener)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
listener.FunctionDelegate.Invoke(eventType, args);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, $"Exception in {blame} during {eventType} invoke, for specific agent {args.AgentId}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isInvokingListeners = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resolves a virtual table address to the original virtual table address.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tableAddress">The modified address to resolve.</param>
|
||||||
|
/// <returns>The original address.</returns>
|
||||||
|
internal AgentInterface.AgentInterfaceVirtualTable* GetOriginalVirtualTable(AgentInterface.AgentInterfaceVirtualTable* tableAddress)
|
||||||
|
{
|
||||||
|
var matchedTable = AllocatedTables.FirstOrDefault(table => table.ModifiedVirtualTable == tableAddress);
|
||||||
|
if (matchedTable == null) return null;
|
||||||
|
|
||||||
|
return matchedTable.OriginalVirtualTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAgentModuleInitialize(AgentModule* thisPtr, UIModule* uiModule)
|
||||||
|
{
|
||||||
|
this.onInitializeAgentsHook!.Original(thisPtr, uiModule);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.ReplaceVirtualTables(thisPtr);
|
||||||
|
|
||||||
|
// We don't need this hook anymore, it did its job!
|
||||||
|
this.onInitializeAgentsHook!.Dispose();
|
||||||
|
this.onInitializeAgentsHook = null;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Exception in AgentLifecycle during AgentModule Ctor.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReplaceVirtualTables(AgentModule* agentModule)
|
||||||
|
{
|
||||||
|
foreach (uint index in Enumerable.Range(0, agentModule->Agents.Length))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var agentPointer = agentModule->Agents.GetPointer((int)index);
|
||||||
|
|
||||||
|
if (agentPointer is null)
|
||||||
|
{
|
||||||
|
Log.Warning("Null Agent Found?");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// AgentVirtualTable class handles creating the virtual table, and overriding each of the tracked virtual functions
|
||||||
|
AllocatedTables.Add(new AgentVirtualTable(agentPointer->Value, (AgentId)index, this));
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Exception in AgentLifecycle during ReplaceVirtualTables.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Plugin-scoped version of a AgentLifecycle service.
|
||||||
|
/// </summary>
|
||||||
|
[PluginInterface]
|
||||||
|
[ServiceManager.ScopedService]
|
||||||
|
#pragma warning disable SA1015
|
||||||
|
[ResolveVia<IAgentLifecycle>]
|
||||||
|
#pragma warning restore SA1015
|
||||||
|
internal class AgentLifecyclePluginScoped : IInternalDisposableService, IAgentLifecycle
|
||||||
|
{
|
||||||
|
[ServiceManager.ServiceDependency]
|
||||||
|
private readonly AgentLifecycle agentLifecycleService = Service<AgentLifecycle>.Get();
|
||||||
|
|
||||||
|
private readonly List<AgentLifecycleEventListener> eventListeners = [];
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
void IInternalDisposableService.DisposeService()
|
||||||
|
{
|
||||||
|
foreach (var listener in this.eventListeners)
|
||||||
|
{
|
||||||
|
this.agentLifecycleService.UnregisterListener(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void RegisterListener(AgentEvent eventType, IEnumerable<AgentId> agentIds, IAgentLifecycle.AgentEventDelegate handler)
|
||||||
|
{
|
||||||
|
foreach (var agentId in agentIds)
|
||||||
|
{
|
||||||
|
this.RegisterListener(eventType, agentId, handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void RegisterListener(AgentEvent eventType, AgentId agentId, IAgentLifecycle.AgentEventDelegate handler)
|
||||||
|
{
|
||||||
|
var listener = new AgentLifecycleEventListener(eventType, agentId, handler);
|
||||||
|
this.eventListeners.Add(listener);
|
||||||
|
this.agentLifecycleService.RegisterListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void RegisterListener(AgentEvent eventType, IAgentLifecycle.AgentEventDelegate handler)
|
||||||
|
{
|
||||||
|
this.RegisterListener(eventType, (AgentId)uint.MaxValue, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void UnregisterListener(AgentEvent eventType, IEnumerable<AgentId> agentIds, IAgentLifecycle.AgentEventDelegate? handler = null)
|
||||||
|
{
|
||||||
|
foreach (var agentId in agentIds)
|
||||||
|
{
|
||||||
|
this.UnregisterListener(eventType, agentId, handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void UnregisterListener(AgentEvent eventType, AgentId agentId, IAgentLifecycle.AgentEventDelegate? handler = null)
|
||||||
|
{
|
||||||
|
this.eventListeners.RemoveAll(entry =>
|
||||||
|
{
|
||||||
|
if (entry.EventType != eventType) return false;
|
||||||
|
if (entry.AgentId != agentId) return false;
|
||||||
|
if (handler is not null && entry.FunctionDelegate != handler) return false;
|
||||||
|
|
||||||
|
this.agentLifecycleService.UnregisterListener(entry);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void UnregisterListener(AgentEvent eventType, IAgentLifecycle.AgentEventDelegate? handler = null)
|
||||||
|
{
|
||||||
|
this.UnregisterListener(eventType, (AgentId)uint.MaxValue, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void UnregisterListener(params IAgentLifecycle.AgentEventDelegate[] handlers)
|
||||||
|
{
|
||||||
|
foreach (var handler in handlers)
|
||||||
|
{
|
||||||
|
this.eventListeners.RemoveAll(entry =>
|
||||||
|
{
|
||||||
|
if (entry.FunctionDelegate != handler) return false;
|
||||||
|
|
||||||
|
this.agentLifecycleService.UnregisterListener(entry);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public unsafe nint GetOriginalVirtualTable(nint virtualTableAddress)
|
||||||
|
=> (nint)this.agentLifecycleService.GetOriginalVirtualTable((AgentInterface.AgentInterfaceVirtualTable*)virtualTableAddress);
|
||||||
|
}
|
||||||
38
Dalamud/Game/Agent/AgentLifecycleEventListener.cs
Normal file
38
Dalamud/Game/Agent/AgentLifecycleEventListener.cs
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
using Dalamud.Plugin.Services;
|
||||||
|
|
||||||
|
namespace Dalamud.Game.Agent;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This class is a helper for tracking and invoking listener delegates.
|
||||||
|
/// </summary>
|
||||||
|
public class AgentLifecycleEventListener
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="AgentLifecycleEventListener"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="eventType">Event type to listen for.</param>
|
||||||
|
/// <param name="agentId">Agent id to listen for.</param>
|
||||||
|
/// <param name="functionDelegate">Delegate to invoke.</param>
|
||||||
|
internal AgentLifecycleEventListener(AgentEvent eventType, AgentId agentId, IAgentLifecycle.AgentEventDelegate functionDelegate)
|
||||||
|
{
|
||||||
|
this.EventType = eventType;
|
||||||
|
this.AgentId = agentId;
|
||||||
|
this.FunctionDelegate = functionDelegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the agentId of the agent this listener is looking for.
|
||||||
|
/// uint.MaxValue if it wants to be called for any agent.
|
||||||
|
/// </summary>
|
||||||
|
public AgentId AgentId { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the event type this listener is looking for.
|
||||||
|
/// </summary>
|
||||||
|
public AgentEvent EventType { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the delegate this listener invokes.
|
||||||
|
/// </summary>
|
||||||
|
public IAgentLifecycle.AgentEventDelegate FunctionDelegate { get; init; }
|
||||||
|
}
|
||||||
393
Dalamud/Game/Agent/AgentVirtualTable.cs
Normal file
393
Dalamud/Game/Agent/AgentVirtualTable.cs
Normal file
|
|
@ -0,0 +1,393 @@
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
using Dalamud.Game.Agent.AgentArgTypes;
|
||||||
|
using Dalamud.Logging.Internal;
|
||||||
|
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.System.Memory;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||||
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
|
|
||||||
|
namespace Dalamud.Game.Agent;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a class that holds references to an agents original and modified virtual table entries.
|
||||||
|
/// </summary>
|
||||||
|
internal unsafe class AgentVirtualTable : IDisposable
|
||||||
|
{
|
||||||
|
// This need to be at minimum the largest virtual table size of all agents
|
||||||
|
// Copying extra entries is not problematic, and is considered safe.
|
||||||
|
private const int VirtualTableEntryCount = 60;
|
||||||
|
|
||||||
|
private const bool EnableLogging = true;
|
||||||
|
|
||||||
|
private static readonly ModuleLog Log = new("AgentVT");
|
||||||
|
|
||||||
|
private readonly AgentLifecycle lifecycleService;
|
||||||
|
|
||||||
|
private readonly AgentId agentId;
|
||||||
|
|
||||||
|
// Each agent gets its own set of args that are used to mutate the original call when used in pre-calls
|
||||||
|
private readonly AgentReceiveEventArgs receiveEventArgs = new();
|
||||||
|
private readonly AgentReceiveEventArgs filteredReceiveEventArgs = new();
|
||||||
|
private readonly AgentArgs showArgs = new();
|
||||||
|
private readonly AgentArgs hideArgs = new();
|
||||||
|
private readonly AgentArgs updateArgs = new();
|
||||||
|
private readonly AgentGameEventArgs gameEventArgs = new();
|
||||||
|
private readonly AgentLevelChangeArgs levelChangeArgs = new();
|
||||||
|
private readonly AgentClassJobChangeArgs classJobChangeArgs = new();
|
||||||
|
|
||||||
|
private readonly AgentInterface* agentInterface;
|
||||||
|
|
||||||
|
// Pinned Function Delegates, as these functions get assigned to an unmanaged virtual table,
|
||||||
|
// the CLR needs to know they are in use, or it will invalidate them causing random crashing.
|
||||||
|
private readonly AgentInterface.Delegates.ReceiveEvent receiveEventFunction;
|
||||||
|
private readonly AgentInterface.Delegates.ReceiveEvent2 filteredReceiveEventFunction;
|
||||||
|
private readonly AgentInterface.Delegates.Show showFunction;
|
||||||
|
private readonly AgentInterface.Delegates.Hide hideFunction;
|
||||||
|
private readonly AgentInterface.Delegates.Update updateFunction;
|
||||||
|
private readonly AgentInterface.Delegates.OnGameEvent gameEventFunction;
|
||||||
|
private readonly AgentInterface.Delegates.OnLevelChange levelChangeFunction;
|
||||||
|
private readonly AgentInterface.Delegates.OnClassJobChange classJobChangeFunction;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="AgentVirtualTable"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="agent">AgentInterface* for the agent to replace the table of.</param>
|
||||||
|
/// <param name="agentId">Agent ID.</param>
|
||||||
|
/// <param name="lifecycleService">Reference to AgentLifecycle service to callback and invoke listeners.</param>
|
||||||
|
internal AgentVirtualTable(AgentInterface* agent, AgentId agentId, AgentLifecycle lifecycleService)
|
||||||
|
{
|
||||||
|
Log.Debug($"Initializing AgentVirtualTable for {agentId}, Address: {(nint)agent:X}");
|
||||||
|
|
||||||
|
this.agentInterface = agent;
|
||||||
|
this.agentId = agentId;
|
||||||
|
this.lifecycleService = lifecycleService;
|
||||||
|
|
||||||
|
// Save original virtual table
|
||||||
|
this.OriginalVirtualTable = agent->VirtualTable;
|
||||||
|
|
||||||
|
// Create copy of original table
|
||||||
|
// Note this will copy any derived/overriden functions that this specific agent has.
|
||||||
|
// Note: currently there are 16 virtual functions, but there's no harm in copying more for when they add new virtual functions to the game
|
||||||
|
this.ModifiedVirtualTable = (AgentInterface.AgentInterfaceVirtualTable*)IMemorySpace.GetUISpace()->Malloc(0x8 * VirtualTableEntryCount, 8);
|
||||||
|
NativeMemory.Copy(agent->VirtualTable, this.ModifiedVirtualTable, 0x8 * VirtualTableEntryCount);
|
||||||
|
|
||||||
|
// Overwrite the agents existing virtual table with our own
|
||||||
|
agent->VirtualTable = this.ModifiedVirtualTable;
|
||||||
|
|
||||||
|
// Pin each of our listener functions
|
||||||
|
this.receiveEventFunction = this.OnAgentReceiveEvent;
|
||||||
|
this.filteredReceiveEventFunction = this.OnAgentFilteredReceiveEvent;
|
||||||
|
this.showFunction = this.OnAgentShow;
|
||||||
|
this.hideFunction = this.OnAgentHide;
|
||||||
|
this.updateFunction = this.OnAgentUpdate;
|
||||||
|
this.gameEventFunction = this.OnAgentGameEvent;
|
||||||
|
this.levelChangeFunction = this.OnAgentLevelChange;
|
||||||
|
this.classJobChangeFunction = this.OnClassJobChange;
|
||||||
|
|
||||||
|
// Overwrite specific virtual table entries
|
||||||
|
this.ModifiedVirtualTable->ReceiveEvent = (delegate* unmanaged<AgentInterface*, AtkValue*, AtkValue*, uint, ulong, AtkValue*>)Marshal.GetFunctionPointerForDelegate(this.receiveEventFunction);
|
||||||
|
this.ModifiedVirtualTable->ReceiveEvent2 = (delegate* unmanaged<AgentInterface*, AtkValue*, AtkValue*, uint, ulong, AtkValue*>)Marshal.GetFunctionPointerForDelegate(this.filteredReceiveEventFunction);
|
||||||
|
this.ModifiedVirtualTable->Show = (delegate* unmanaged<AgentInterface*, void>)Marshal.GetFunctionPointerForDelegate(this.showFunction);
|
||||||
|
this.ModifiedVirtualTable->Hide = (delegate* unmanaged<AgentInterface*, void>)Marshal.GetFunctionPointerForDelegate(this.hideFunction);
|
||||||
|
this.ModifiedVirtualTable->Update = (delegate* unmanaged<AgentInterface*, uint, void>)Marshal.GetFunctionPointerForDelegate(this.updateFunction);
|
||||||
|
this.ModifiedVirtualTable->OnGameEvent = (delegate* unmanaged<AgentInterface*, AgentInterface.GameEvent, void>)Marshal.GetFunctionPointerForDelegate(this.gameEventFunction);
|
||||||
|
this.ModifiedVirtualTable->OnLevelChange = (delegate* unmanaged<AgentInterface*, byte, ushort, void>)Marshal.GetFunctionPointerForDelegate(this.levelChangeFunction);
|
||||||
|
this.ModifiedVirtualTable->OnClassJobChange = (delegate* unmanaged<AgentInterface*, byte, void>)Marshal.GetFunctionPointerForDelegate(this.classJobChangeFunction);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the original virtual table address for this agent.
|
||||||
|
/// </summary>
|
||||||
|
internal AgentInterface.AgentInterfaceVirtualTable* OriginalVirtualTable { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the modified virtual address for this agent.
|
||||||
|
/// </summary>
|
||||||
|
internal AgentInterface.AgentInterfaceVirtualTable* ModifiedVirtualTable { get; private set; }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
// Ensure restoration is done atomically.
|
||||||
|
Interlocked.Exchange(ref *(nint*)&this.agentInterface->VirtualTable, (nint)this.OriginalVirtualTable);
|
||||||
|
IMemorySpace.Free(this.ModifiedVirtualTable, 0x8 * VirtualTableEntryCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
private AtkValue* OnAgentReceiveEvent(AgentInterface* thisPtr, AtkValue* returnValue, AtkValue* values, uint valueCount, ulong eventKind)
|
||||||
|
{
|
||||||
|
AtkValue* result = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.LogEvent(EnableLogging);
|
||||||
|
|
||||||
|
this.receiveEventArgs.Agent = thisPtr;
|
||||||
|
this.receiveEventArgs.AgentId = this.agentId;
|
||||||
|
this.receiveEventArgs.ReturnValue = (nint)returnValue;
|
||||||
|
this.receiveEventArgs.AtkValues = (nint)values;
|
||||||
|
this.receiveEventArgs.ValueCount = valueCount;
|
||||||
|
this.receiveEventArgs.EventKind = eventKind;
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AgentEvent.PreReceiveEvent, this.receiveEventArgs);
|
||||||
|
|
||||||
|
returnValue = (AtkValue*)this.receiveEventArgs.ReturnValue;
|
||||||
|
values = (AtkValue*)this.receiveEventArgs.AtkValues;
|
||||||
|
valueCount = this.receiveEventArgs.ValueCount;
|
||||||
|
eventKind = this.receiveEventArgs.EventKind;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
result = this.OriginalVirtualTable->ReceiveEvent(thisPtr, returnValue, values, valueCount, eventKind);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception when calling original Agent ReceiveEvent. This may be a bug in the game or another plugin hooking this method.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AgentEvent.PostReceiveEvent, this.receiveEventArgs);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAgentReceiveEvent.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private AtkValue* OnAgentFilteredReceiveEvent(AgentInterface* thisPtr, AtkValue* returnValue, AtkValue* values, uint valueCount, ulong eventKind)
|
||||||
|
{
|
||||||
|
AtkValue* result = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.LogEvent(EnableLogging);
|
||||||
|
|
||||||
|
this.filteredReceiveEventArgs.Agent = thisPtr;
|
||||||
|
this.filteredReceiveEventArgs.AgentId = this.agentId;
|
||||||
|
this.filteredReceiveEventArgs.ReturnValue = (nint)returnValue;
|
||||||
|
this.filteredReceiveEventArgs.AtkValues = (nint)values;
|
||||||
|
this.filteredReceiveEventArgs.ValueCount = valueCount;
|
||||||
|
this.filteredReceiveEventArgs.EventKind = eventKind;
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AgentEvent.PreReceiveFilteredEvent, this.filteredReceiveEventArgs);
|
||||||
|
|
||||||
|
returnValue = (AtkValue*)this.filteredReceiveEventArgs.ReturnValue;
|
||||||
|
values = (AtkValue*)this.filteredReceiveEventArgs.AtkValues;
|
||||||
|
valueCount = this.filteredReceiveEventArgs.ValueCount;
|
||||||
|
eventKind = this.filteredReceiveEventArgs.EventKind;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
result = this.OriginalVirtualTable->ReceiveEvent2(thisPtr, returnValue, values, valueCount, eventKind);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception when calling original Agent FilteredReceiveEvent. This may be a bug in the game or another plugin hooking this method.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AgentEvent.PostReceiveFilteredEvent, this.filteredReceiveEventArgs);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAgentFilteredReceiveEvent.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAgentShow(AgentInterface* thisPtr)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.LogEvent(EnableLogging);
|
||||||
|
|
||||||
|
this.showArgs.Agent = thisPtr;
|
||||||
|
this.showArgs.AgentId = this.agentId;
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AgentEvent.PreShow, this.showArgs);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.OriginalVirtualTable->Show(thisPtr);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception when calling original Addon Show. This may be a bug in the game or another plugin hooking this method.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AgentEvent.PostShow, this.showArgs);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAgentShow.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAgentHide(AgentInterface* thisPtr)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.LogEvent(EnableLogging);
|
||||||
|
|
||||||
|
this.hideArgs.Agent = thisPtr;
|
||||||
|
this.hideArgs.AgentId = this.agentId;
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AgentEvent.PreHide, this.hideArgs);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.OriginalVirtualTable->Hide(thisPtr);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception when calling original Addon Hide. This may be a bug in the game or another plugin hooking this method.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AgentEvent.PostHide, this.hideArgs);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAgentHide.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAgentUpdate(AgentInterface* thisPtr, uint frameCount)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.LogEvent(EnableLogging);
|
||||||
|
|
||||||
|
this.updateArgs.Agent = thisPtr;
|
||||||
|
this.updateArgs.AgentId = this.agentId;
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AgentEvent.PreUpdate, this.updateArgs);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.OriginalVirtualTable->Update(thisPtr, frameCount);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception when calling original Addon Update. This may be a bug in the game or another plugin hooking this method.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AgentEvent.PostUpdate, this.updateArgs);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAgentUpdate.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAgentGameEvent(AgentInterface* thisPtr, AgentInterface.GameEvent gameEvent)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.LogEvent(EnableLogging);
|
||||||
|
|
||||||
|
this.gameEventArgs.Agent = thisPtr;
|
||||||
|
this.gameEventArgs.AgentId = this.agentId;
|
||||||
|
this.gameEventArgs.GameEvent = (int)gameEvent;
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AgentEvent.PreGameEvent, this.gameEventArgs);
|
||||||
|
|
||||||
|
gameEvent = (AgentInterface.GameEvent)this.gameEventArgs.GameEvent;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.OriginalVirtualTable->OnGameEvent(thisPtr, gameEvent);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception when calling original Addon OnGameEvent. This may be a bug in the game or another plugin hooking this method.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AgentEvent.PostGameEvent, this.gameEventArgs);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAgentGameEvent.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAgentLevelChange(AgentInterface* thisPtr, byte classJobId, ushort level)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.LogEvent(EnableLogging);
|
||||||
|
|
||||||
|
this.levelChangeArgs.Agent = thisPtr;
|
||||||
|
this.levelChangeArgs.AgentId = this.agentId;
|
||||||
|
this.levelChangeArgs.ClassJobId = classJobId;
|
||||||
|
this.levelChangeArgs.Level = level;
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AgentEvent.PreLevelChange, this.levelChangeArgs);
|
||||||
|
|
||||||
|
classJobId = this.levelChangeArgs.ClassJobId;
|
||||||
|
level = this.levelChangeArgs.Level;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.OriginalVirtualTable->OnLevelChange(thisPtr, classJobId, level);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception when calling original Addon OnLevelChange. This may be a bug in the game or another plugin hooking this method.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AgentEvent.PostLevelChange, this.levelChangeArgs);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAgentLevelChange.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnClassJobChange(AgentInterface* thisPtr, byte classJobId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.LogEvent(EnableLogging);
|
||||||
|
|
||||||
|
this.classJobChangeArgs.Agent = thisPtr;
|
||||||
|
this.classJobChangeArgs.AgentId = this.agentId;
|
||||||
|
this.classJobChangeArgs.ClassJobId = classJobId;
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AgentEvent.PreClassJobChange, this.classJobChangeArgs);
|
||||||
|
|
||||||
|
classJobId = this.classJobChangeArgs.ClassJobId;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.OriginalVirtualTable->OnClassJobChange(thisPtr, classJobId);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception when calling original Addon OnClassJobChange. This may be a bug in the game or another plugin hooking this method.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AgentEvent.PostClassJobChange, this.classJobChangeArgs);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception from Dalamud when attempting to process OnClassJobChange.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Conditional("DEBUG")]
|
||||||
|
private void LogEvent(bool loggingEnabled, [CallerMemberName] string caller = "")
|
||||||
|
{
|
||||||
|
if (loggingEnabled)
|
||||||
|
{
|
||||||
|
// Manually disable the really spammy log events, you can comment this out if you need to debug them.
|
||||||
|
if (caller is "OnAgentUpdate" || this.agentId is AgentId.PadMouseMode)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Log.Debug($"[{caller}]: {this.agentId}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -22,8 +22,6 @@ using PublicContentSheet = Lumina.Excel.Sheets.PublicContent;
|
||||||
|
|
||||||
namespace Dalamud.Game.UnlockState;
|
namespace Dalamud.Game.UnlockState;
|
||||||
|
|
||||||
#pragma warning disable Dalamud001
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This class provides unlock state of various content in the game.
|
/// This class provides unlock state of various content in the game.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,11 @@ using System.Threading.Tasks;
|
||||||
|
|
||||||
using Dalamud.Bindings.ImGui;
|
using Dalamud.Bindings.ImGui;
|
||||||
using Dalamud.Interface.Components;
|
using Dalamud.Interface.Components;
|
||||||
|
using Dalamud.Interface.ImGuiSeStringRenderer;
|
||||||
using Dalamud.Interface.Textures.Internal;
|
using Dalamud.Interface.Textures.Internal;
|
||||||
using Dalamud.Interface.Utility;
|
using Dalamud.Interface.Utility;
|
||||||
using Dalamud.Interface.Utility.Internal;
|
using Dalamud.Interface.Utility.Internal;
|
||||||
|
using Dalamud.Interface.Utility.Raii;
|
||||||
|
|
||||||
using Lumina.Text.ReadOnly;
|
using Lumina.Text.ReadOnly;
|
||||||
|
|
||||||
|
|
@ -18,13 +20,15 @@ namespace Dalamud.Interface.Internal.Windows.Data.Widgets;
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class FontAwesomeTestWidget : IDataWindowWidget
|
internal class FontAwesomeTestWidget : IDataWindowWidget
|
||||||
{
|
{
|
||||||
|
private static readonly string[] First = ["(Show All)", "(Undefined)"];
|
||||||
|
|
||||||
private List<FontAwesomeIcon>? icons;
|
private List<FontAwesomeIcon>? icons;
|
||||||
private List<string>? iconNames;
|
private List<string>? iconNames;
|
||||||
private string[]? iconCategories;
|
private string[]? iconCategories;
|
||||||
private int selectedIconCategory;
|
private int selectedIconCategory;
|
||||||
private string iconSearchInput = string.Empty;
|
private string iconSearchInput = string.Empty;
|
||||||
private bool iconSearchChanged = true;
|
private bool iconSearchChanged = true;
|
||||||
private bool useFixedWidth = false;
|
private bool useFixedWidth;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public string[]? CommandShortcuts { get; init; } = ["fa", "fatest", "fontawesome"];
|
public string[]? CommandShortcuts { get; init; } = ["fa", "fatest", "fontawesome"];
|
||||||
|
|
@ -44,11 +48,9 @@ internal class FontAwesomeTestWidget : IDataWindowWidget
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Draw()
|
public void Draw()
|
||||||
{
|
{
|
||||||
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
|
using var pushedStyle = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
|
||||||
|
|
||||||
this.iconCategories ??= new[] { "(Show All)", "(Undefined)" }
|
this.iconCategories ??= First.Concat(FontAwesomeHelpers.GetCategories().Skip(1)).ToArray();
|
||||||
.Concat(FontAwesomeHelpers.GetCategories().Skip(1))
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
if (this.iconSearchChanged)
|
if (this.iconSearchChanged)
|
||||||
{
|
{
|
||||||
|
|
@ -101,7 +103,8 @@ internal class FontAwesomeTestWidget : IDataWindowWidget
|
||||||
ImGuiHelpers.ScaledRelativeSameLine(50f);
|
ImGuiHelpers.ScaledRelativeSameLine(50f);
|
||||||
ImGui.Text($"{this.iconNames?[i]}");
|
ImGui.Text($"{this.iconNames?[i]}");
|
||||||
ImGuiHelpers.ScaledRelativeSameLine(280f);
|
ImGuiHelpers.ScaledRelativeSameLine(280f);
|
||||||
ImGui.PushFont(this.useFixedWidth ? InterfaceManager.IconFontFixedWidth : InterfaceManager.IconFont);
|
|
||||||
|
using var pushedFont = ImRaii.PushFont(this.useFixedWidth ? InterfaceManager.IconFontFixedWidth : InterfaceManager.IconFont);
|
||||||
ImGui.Text(this.icons[i].ToIconString());
|
ImGui.Text(this.icons[i].ToIconString());
|
||||||
ImGuiHelpers.ScaledRelativeSameLine(320f);
|
ImGuiHelpers.ScaledRelativeSameLine(320f);
|
||||||
if (this.useFixedWidth
|
if (this.useFixedWidth
|
||||||
|
|
@ -114,13 +117,10 @@ internal class FontAwesomeTestWidget : IDataWindowWidget
|
||||||
Task.FromResult(
|
Task.FromResult(
|
||||||
Service<TextureManager>.Get().CreateTextureFromSeString(
|
Service<TextureManager>.Get().CreateTextureFromSeString(
|
||||||
ReadOnlySeString.FromText(this.icons[i].ToIconString()),
|
ReadOnlySeString.FromText(this.icons[i].ToIconString()),
|
||||||
new() { Font = ImGui.GetFont(), FontSize = ImGui.GetFontSize(), ScreenOffset = Vector2.Zero })));
|
new SeStringDrawParams { Font = ImGui.GetFont(), FontSize = ImGui.GetFontSize(), ScreenOffset = Vector2.Zero })));
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.PopFont();
|
|
||||||
ImGuiHelpers.ScaledDummy(2f);
|
ImGuiHelpers.ScaledDummy(2f);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.PopStyleVar();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ using Dalamud.Interface.ImGuiSeStringRenderer.Internal;
|
||||||
using Dalamud.Interface.Textures.Internal;
|
using Dalamud.Interface.Textures.Internal;
|
||||||
using Dalamud.Interface.Utility;
|
using Dalamud.Interface.Utility;
|
||||||
using Dalamud.Interface.Utility.Internal;
|
using Dalamud.Interface.Utility.Internal;
|
||||||
|
using Dalamud.Interface.Utility.Raii;
|
||||||
using Dalamud.Storage.Assets;
|
using Dalamud.Storage.Assets;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
|
|
||||||
|
|
@ -29,7 +30,7 @@ namespace Dalamud.Interface.Internal.Windows.Data.Widgets;
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal unsafe class SeStringRendererTestWidget : IDataWindowWidget
|
internal unsafe class SeStringRendererTestWidget : IDataWindowWidget
|
||||||
{
|
{
|
||||||
private static readonly string[] ThemeNames = ["Dark", "Light", "Classic FF", "Clear Blue"];
|
private static readonly string[] ThemeNames = ["Dark", "Light", "Classic FF", "Clear Blue", "Clear White", "Clear Green"];
|
||||||
private ImVectorWrapper<byte> testStringBuffer;
|
private ImVectorWrapper<byte> testStringBuffer;
|
||||||
private string testString = string.Empty;
|
private string testString = string.Empty;
|
||||||
private ReadOnlySeString? logkind;
|
private ReadOnlySeString? logkind;
|
||||||
|
|
@ -119,9 +120,11 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
var t4 = this.style.ThemeIndex ?? AtkStage.Instance()->AtkUIColorHolder->ActiveColorThemeType;
|
var t4 = this.style.ThemeIndex ?? AtkStage.Instance()->AtkUIColorHolder->ActiveColorThemeType;
|
||||||
ImGui.PushItemWidth(ImGui.CalcTextSize("WWWWWWWWWWWWWW"u8).X);
|
using (ImRaii.ItemWidth(ImGui.CalcTextSize("WWWWWWWWWWWWWW"u8).X))
|
||||||
if (ImGui.Combo("##theme", ref t4, ThemeNames))
|
{
|
||||||
this.style.ThemeIndex = t4;
|
if (ImGui.Combo("##theme", ref t4, ThemeNames))
|
||||||
|
this.style.ThemeIndex = t4;
|
||||||
|
}
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
t = this.style.LinkUnderlineThickness > 0f;
|
t = this.style.LinkUnderlineThickness > 0f;
|
||||||
|
|
@ -192,22 +195,19 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget
|
||||||
dl.PushClipRect(clipMin, clipMax);
|
dl.PushClipRect(clipMin, clipMax);
|
||||||
ImGuiHelpers.CompileSeStringWrapped(
|
ImGuiHelpers.CompileSeStringWrapped(
|
||||||
"<icon(1)>Test test<icon(1)>",
|
"<icon(1)>Test test<icon(1)>",
|
||||||
new SeStringDrawParams
|
new SeStringDrawParams { Color = 0xFFFFFFFF, WrapWidth = float.MaxValue, TargetDrawList = dl });
|
||||||
{ Color = 0xFFFFFFFF, WrapWidth = float.MaxValue, TargetDrawList = dl });
|
|
||||||
dl.PopClipRect();
|
dl.PopClipRect();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui.CollapsingHeader("Addon Table"u8))
|
if (ImGui.CollapsingHeader("Addon Table"u8))
|
||||||
{
|
{
|
||||||
if (ImGui.BeginTable("Addon Sheet"u8, 3))
|
using var table = ImRaii.Table("Addon Sheet"u8, 3);
|
||||||
|
if (table.Success)
|
||||||
{
|
{
|
||||||
ImGui.TableSetupScrollFreeze(0, 1);
|
ImGui.TableSetupScrollFreeze(0, 1);
|
||||||
ImGui.TableSetupColumn("Row ID"u8, ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("0000000"u8).X);
|
ImGui.TableSetupColumn("Row ID"u8, ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("0000000"u8).X);
|
||||||
ImGui.TableSetupColumn("Text"u8, ImGuiTableColumnFlags.WidthStretch);
|
ImGui.TableSetupColumn("Text"u8, ImGuiTableColumnFlags.WidthStretch);
|
||||||
ImGui.TableSetupColumn(
|
ImGui.TableSetupColumn("Misc"u8, ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("AAAAAAAAAAAAAAAAA"u8).X);
|
||||||
"Misc"u8,
|
|
||||||
ImGuiTableColumnFlags.WidthFixed,
|
|
||||||
ImGui.CalcTextSize("AAAAAAAAAAAAAAAAA"u8).X);
|
|
||||||
ImGui.TableHeadersRow();
|
ImGui.TableHeadersRow();
|
||||||
|
|
||||||
var addon = Service<DataManager>.GetNullable()?.GetExcelSheet<Addon>() ??
|
var addon = Service<DataManager>.GetNullable()?.GetExcelSheet<Addon>() ??
|
||||||
|
|
@ -222,7 +222,7 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget
|
||||||
var row = addon.GetRowAt(i);
|
var row = addon.GetRowAt(i);
|
||||||
|
|
||||||
ImGui.TableNextRow();
|
ImGui.TableNextRow();
|
||||||
ImGui.PushID(i);
|
using var pushedId = ImRaii.PushId(i);
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.AlignTextToFramePadding();
|
ImGui.AlignTextToFramePadding();
|
||||||
|
|
@ -234,14 +234,11 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
if (ImGui.Button("Print to Chat"u8))
|
if (ImGui.Button("Print to Chat"u8))
|
||||||
Service<ChatGui>.Get().Print(row.Text.ToDalamudString());
|
Service<ChatGui>.Get().Print(row.Text);
|
||||||
|
|
||||||
ImGui.PopID();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
clipper.Destroy();
|
clipper.Destroy();
|
||||||
ImGui.EndTable();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -258,9 +255,7 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget
|
||||||
|
|
||||||
if (ImGui.Button("Print to Chat Log"u8))
|
if (ImGui.Button("Print to Chat Log"u8))
|
||||||
{
|
{
|
||||||
Service<ChatGui>.Get().Print(
|
Service<ChatGui>.Get().Print(Service<SeStringRenderer>.Get().CompileAndCache(this.testString));
|
||||||
Game.Text.SeStringHandling.SeString.Parse(
|
|
||||||
Service<SeStringRenderer>.Get().CompileAndCache(this.testString).Data.Span));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
|
|
@ -315,6 +310,7 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget
|
||||||
var len = this.testStringBuffer.StorageSpan.IndexOf((byte)0);
|
var len = this.testStringBuffer.StorageSpan.IndexOf((byte)0);
|
||||||
if (len + 4 >= this.testStringBuffer.Capacity)
|
if (len + 4 >= this.testStringBuffer.Capacity)
|
||||||
this.testStringBuffer.EnsureCapacityExponential(len + 4);
|
this.testStringBuffer.EnsureCapacityExponential(len + 4);
|
||||||
|
|
||||||
if (len < this.testStringBuffer.Capacity)
|
if (len < this.testStringBuffer.Capacity)
|
||||||
{
|
{
|
||||||
this.testStringBuffer.LengthUnsafe = len;
|
this.testStringBuffer.LengthUnsafe = len;
|
||||||
|
|
|
||||||
|
|
@ -266,8 +266,7 @@ internal class TaskSchedulerWidget : IDataWindowWidget
|
||||||
|
|
||||||
ImGui.Text($"{this.downloadState.Downloaded:##,###}/{this.downloadState.Total:##,###} ({this.downloadState.Percentage:0.00}%)");
|
ImGui.Text($"{this.downloadState.Downloaded:##,###}/{this.downloadState.Total:##,###} ({this.downloadState.Percentage:0.00}%)");
|
||||||
|
|
||||||
using var disabled =
|
using var disabled = ImRaii.Disabled(this.downloadTask?.IsCompleted is false || this.localPath[0] == 0);
|
||||||
ImRaii.Disabled(this.downloadTask?.IsCompleted is false || this.localPath[0] == 0);
|
|
||||||
ImGui.AlignTextToFramePadding();
|
ImGui.AlignTextToFramePadding();
|
||||||
ImGui.Text("Download"u8);
|
ImGui.Text("Download"u8);
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
|
|
@ -388,27 +387,19 @@ internal class TaskSchedulerWidget : IDataWindowWidget
|
||||||
if (task.Task == null)
|
if (task.Task == null)
|
||||||
subTime = task.FinishTime;
|
subTime = task.FinishTime;
|
||||||
|
|
||||||
switch (task.Status)
|
using var pushedColor = task.Status switch
|
||||||
{
|
{
|
||||||
case TaskStatus.Created:
|
TaskStatus.Created or TaskStatus.WaitingForActivation or TaskStatus.WaitingToRun
|
||||||
case TaskStatus.WaitingForActivation:
|
=> ImRaii.PushColor(ImGuiCol.Header, ImGuiColors.DalamudGrey),
|
||||||
case TaskStatus.WaitingToRun:
|
TaskStatus.Running or TaskStatus.WaitingForChildrenToComplete
|
||||||
ImGui.PushStyleColor(ImGuiCol.Header, ImGuiColors.DalamudGrey);
|
=> ImRaii.PushColor(ImGuiCol.Header, ImGuiColors.ParsedBlue),
|
||||||
break;
|
TaskStatus.RanToCompletion
|
||||||
case TaskStatus.Running:
|
=> ImRaii.PushColor(ImGuiCol.Header, ImGuiColors.ParsedGreen),
|
||||||
case TaskStatus.WaitingForChildrenToComplete:
|
TaskStatus.Canceled or TaskStatus.Faulted
|
||||||
ImGui.PushStyleColor(ImGuiCol.Header, ImGuiColors.ParsedBlue);
|
=> ImRaii.PushColor(ImGuiCol.Header, ImGuiColors.DalamudRed),
|
||||||
break;
|
|
||||||
case TaskStatus.RanToCompletion:
|
_ => throw new ArgumentOutOfRangeException(),
|
||||||
ImGui.PushStyleColor(ImGuiCol.Header, ImGuiColors.ParsedGreen);
|
};
|
||||||
break;
|
|
||||||
case TaskStatus.Canceled:
|
|
||||||
case TaskStatus.Faulted:
|
|
||||||
ImGui.PushStyleColor(ImGuiCol.Header, ImGuiColors.DalamudRed);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new ArgumentOutOfRangeException();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui.CollapsingHeader($"#{task.Id} - {task.Status} {(subTime - task.StartTime).TotalMilliseconds}ms###task{i}"))
|
if (ImGui.CollapsingHeader($"#{task.Id} - {task.Status} {(subTime - task.StartTime).TotalMilliseconds}ms###task{i}"))
|
||||||
{
|
{
|
||||||
|
|
@ -418,8 +409,7 @@ internal class TaskSchedulerWidget : IDataWindowWidget
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var cancelFunc =
|
var cancelFunc = typeof(Task).GetMethod("InternalCancel", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||||
typeof(Task).GetMethod("InternalCancel", BindingFlags.NonPublic | BindingFlags.Instance);
|
|
||||||
cancelFunc?.Invoke(task, null);
|
cancelFunc?.Invoke(task, null);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|
@ -430,7 +420,7 @@ internal class TaskSchedulerWidget : IDataWindowWidget
|
||||||
|
|
||||||
ImGuiHelpers.ScaledDummy(10);
|
ImGuiHelpers.ScaledDummy(10);
|
||||||
|
|
||||||
ImGui.Text(task.StackTrace?.ToString());
|
ImGui.Text(task.StackTrace?.ToString() ?? "Null StackTrace");
|
||||||
|
|
||||||
if (task.Exception != null)
|
if (task.Exception != null)
|
||||||
{
|
{
|
||||||
|
|
@ -443,8 +433,6 @@ internal class TaskSchedulerWidget : IDataWindowWidget
|
||||||
{
|
{
|
||||||
task.IsBeingViewed = false;
|
task.IsBeingViewed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.PopStyleColor(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.fileDialogManager.Draw();
|
this.fileDialogManager.Draw();
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ using Dalamud.Interface.Textures.Internal.SharedImmediateTextures;
|
||||||
using Dalamud.Interface.Textures.TextureWraps;
|
using Dalamud.Interface.Textures.TextureWraps;
|
||||||
using Dalamud.Interface.Utility;
|
using Dalamud.Interface.Utility;
|
||||||
using Dalamud.Interface.Utility.Internal;
|
using Dalamud.Interface.Utility.Internal;
|
||||||
|
using Dalamud.Interface.Utility.Raii;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using Dalamud.Storage.Assets;
|
using Dalamud.Storage.Assets;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
|
|
@ -29,6 +30,10 @@ namespace Dalamud.Interface.Internal.Windows.Data.Widgets;
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class TexWidget : IDataWindowWidget
|
internal class TexWidget : IDataWindowWidget
|
||||||
{
|
{
|
||||||
|
private const ImGuiTableFlags TableFlags = ImGuiTableFlags.Sortable | ImGuiTableFlags.SortTristate | ImGuiTableFlags.SortMulti |
|
||||||
|
ImGuiTableFlags.Reorderable | ImGuiTableFlags.Resizable | ImGuiTableFlags.NoBordersInBodyUntilResize |
|
||||||
|
ImGuiTableFlags.NoSavedSettings;
|
||||||
|
|
||||||
// TODO: move tracking implementation to PluginStats where applicable,
|
// TODO: move tracking implementation to PluginStats where applicable,
|
||||||
// and show stats over there instead of TexWidget.
|
// and show stats over there instead of TexWidget.
|
||||||
private static readonly Dictionary<
|
private static readonly Dictionary<
|
||||||
|
|
@ -49,7 +54,7 @@ internal class TexWidget : IDataWindowWidget
|
||||||
private string allLoadedTexturesTableName = "##table";
|
private string allLoadedTexturesTableName = "##table";
|
||||||
private string iconId = "18";
|
private string iconId = "18";
|
||||||
private bool hiRes = true;
|
private bool hiRes = true;
|
||||||
private bool hq = false;
|
private bool hq;
|
||||||
private string inputTexPath = string.Empty;
|
private string inputTexPath = string.Empty;
|
||||||
private string inputFilePath = string.Empty;
|
private string inputFilePath = string.Empty;
|
||||||
private Assembly[]? inputManifestResourceAssemblyCandidates;
|
private Assembly[]? inputManifestResourceAssemblyCandidates;
|
||||||
|
|
@ -140,46 +145,40 @@ internal class TexWidget : IDataWindowWidget
|
||||||
|
|
||||||
lock (this.textureManager.BlameTracker)
|
lock (this.textureManager.BlameTracker)
|
||||||
{
|
{
|
||||||
|
using var pushedId = ImRaii.PushId("blames"u8);
|
||||||
var allBlames = this.textureManager.BlameTracker;
|
var allBlames = this.textureManager.BlameTracker;
|
||||||
ImGui.PushID("blames"u8);
|
|
||||||
var sizeSum = allBlames.Sum(static x => Math.Max(0, x.RawSpecs.EstimatedBytes));
|
var sizeSum = allBlames.Sum(static x => Math.Max(0, x.RawSpecs.EstimatedBytes));
|
||||||
if (ImGui.CollapsingHeader(
|
if (ImGui.CollapsingHeader($"All Loaded Textures: {allBlames.Count:n0} ({Util.FormatBytes(sizeSum)})###header"))
|
||||||
$"All Loaded Textures: {allBlames.Count:n0} ({Util.FormatBytes(sizeSum)})###header"))
|
|
||||||
this.DrawBlame(allBlames);
|
this.DrawBlame(allBlames);
|
||||||
ImGui.PopID();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.PushID("loadedGameTextures"u8);
|
using (ImRaii.PushId("loadedGameTextures"u8))
|
||||||
if (ImGui.CollapsingHeader(
|
{
|
||||||
$"Loaded Game Textures: {this.textureManager.Shared.ForDebugGamePathTextures.Count:n0}###header"))
|
if (ImGui.CollapsingHeader($"Loaded Game Textures: {this.textureManager.Shared.ForDebugGamePathTextures.Count:n0}###header"))
|
||||||
this.DrawLoadedTextures(this.textureManager.Shared.ForDebugGamePathTextures);
|
this.DrawLoadedTextures(this.textureManager.Shared.ForDebugGamePathTextures);
|
||||||
ImGui.PopID();
|
}
|
||||||
|
|
||||||
ImGui.PushID("loadedFileTextures"u8);
|
using (ImRaii.PushId("loadedFileTextures"u8))
|
||||||
if (ImGui.CollapsingHeader(
|
{
|
||||||
$"Loaded File Textures: {this.textureManager.Shared.ForDebugFileSystemTextures.Count:n0}###header"))
|
if (ImGui.CollapsingHeader($"Loaded File Textures: {this.textureManager.Shared.ForDebugFileSystemTextures.Count:n0}###header"))
|
||||||
this.DrawLoadedTextures(this.textureManager.Shared.ForDebugFileSystemTextures);
|
this.DrawLoadedTextures(this.textureManager.Shared.ForDebugFileSystemTextures);
|
||||||
ImGui.PopID();
|
}
|
||||||
|
|
||||||
ImGui.PushID("loadedManifestResourceTextures"u8);
|
using (ImRaii.PushId("loadedManifestResourceTextures"u8))
|
||||||
if (ImGui.CollapsingHeader(
|
{
|
||||||
$"Loaded Manifest Resource Textures: {this.textureManager.Shared.ForDebugManifestResourceTextures.Count:n0}###header"))
|
if (ImGui.CollapsingHeader($"Loaded Manifest Resource Textures: {this.textureManager.Shared.ForDebugManifestResourceTextures.Count:n0}###header"))
|
||||||
this.DrawLoadedTextures(this.textureManager.Shared.ForDebugManifestResourceTextures);
|
this.DrawLoadedTextures(this.textureManager.Shared.ForDebugManifestResourceTextures);
|
||||||
ImGui.PopID();
|
}
|
||||||
|
|
||||||
lock (this.textureManager.Shared.ForDebugInvalidatedTextures)
|
lock (this.textureManager.Shared.ForDebugInvalidatedTextures)
|
||||||
{
|
{
|
||||||
ImGui.PushID("invalidatedTextures"u8);
|
using var pushedId = ImRaii.PushId("invalidatedTextures"u8);
|
||||||
if (ImGui.CollapsingHeader(
|
if (ImGui.CollapsingHeader($"Invalidated: {this.textureManager.Shared.ForDebugInvalidatedTextures.Count:n0}###header"))
|
||||||
$"Invalidated: {this.textureManager.Shared.ForDebugInvalidatedTextures.Count:n0}###header"))
|
|
||||||
{
|
|
||||||
this.DrawLoadedTextures(this.textureManager.Shared.ForDebugInvalidatedTextures);
|
this.DrawLoadedTextures(this.textureManager.Shared.ForDebugInvalidatedTextures);
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.PopID();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.Dummy(new(ImGui.GetTextLineHeightWithSpacing()));
|
var textHeightSpacing = new Vector2(ImGui.GetTextLineHeightWithSpacing());
|
||||||
|
ImGui.Dummy(textHeightSpacing);
|
||||||
|
|
||||||
if (!this.textureManager.HasClipboardImage())
|
if (!this.textureManager.HasClipboardImage())
|
||||||
{
|
{
|
||||||
|
|
@ -192,59 +191,53 @@ internal class TexWidget : IDataWindowWidget
|
||||||
|
|
||||||
if (ImGui.CollapsingHeader(nameof(ITextureProvider.GetFromGameIcon)))
|
if (ImGui.CollapsingHeader(nameof(ITextureProvider.GetFromGameIcon)))
|
||||||
{
|
{
|
||||||
ImGui.PushID(nameof(this.DrawGetFromGameIcon));
|
using var pushedId = ImRaii.PushId(nameof(this.DrawGetFromGameIcon));
|
||||||
this.DrawGetFromGameIcon();
|
this.DrawGetFromGameIcon();
|
||||||
ImGui.PopID();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui.CollapsingHeader(nameof(ITextureProvider.GetFromGame)))
|
if (ImGui.CollapsingHeader(nameof(ITextureProvider.GetFromGame)))
|
||||||
{
|
{
|
||||||
ImGui.PushID(nameof(this.DrawGetFromGame));
|
using var pushedId = ImRaii.PushId(nameof(this.DrawGetFromGame));
|
||||||
this.DrawGetFromGame();
|
this.DrawGetFromGame();
|
||||||
ImGui.PopID();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui.CollapsingHeader(nameof(ITextureProvider.GetFromFile)))
|
if (ImGui.CollapsingHeader(nameof(ITextureProvider.GetFromFile)))
|
||||||
{
|
{
|
||||||
ImGui.PushID(nameof(this.DrawGetFromFile));
|
using var pushedId = ImRaii.PushId(nameof(this.DrawGetFromFile));
|
||||||
this.DrawGetFromFile();
|
this.DrawGetFromFile();
|
||||||
ImGui.PopID();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui.CollapsingHeader(nameof(ITextureProvider.GetFromManifestResource)))
|
if (ImGui.CollapsingHeader(nameof(ITextureProvider.GetFromManifestResource)))
|
||||||
{
|
{
|
||||||
ImGui.PushID(nameof(this.DrawGetFromManifestResource));
|
using var pushedId = ImRaii.PushId(nameof(this.DrawGetFromManifestResource));
|
||||||
this.DrawGetFromManifestResource();
|
this.DrawGetFromManifestResource();
|
||||||
ImGui.PopID();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui.CollapsingHeader(nameof(ITextureProvider.CreateFromImGuiViewportAsync)))
|
if (ImGui.CollapsingHeader(nameof(ITextureProvider.CreateFromImGuiViewportAsync)))
|
||||||
{
|
{
|
||||||
ImGui.PushID(nameof(this.DrawCreateFromImGuiViewportAsync));
|
using var pushedId = ImRaii.PushId(nameof(this.DrawCreateFromImGuiViewportAsync));
|
||||||
this.DrawCreateFromImGuiViewportAsync();
|
this.DrawCreateFromImGuiViewportAsync();
|
||||||
ImGui.PopID();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui.CollapsingHeader("UV"u8))
|
if (ImGui.CollapsingHeader("UV"u8))
|
||||||
{
|
{
|
||||||
ImGui.PushID(nameof(this.DrawUvInput));
|
using var pushedId = ImRaii.PushId(nameof(this.DrawUvInput));
|
||||||
this.DrawUvInput();
|
this.DrawUvInput();
|
||||||
ImGui.PopID();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui.CollapsingHeader($"CropCopy##{nameof(this.DrawExistingTextureModificationArgs)}"))
|
if (ImGui.CollapsingHeader($"CropCopy##{nameof(this.DrawExistingTextureModificationArgs)}"))
|
||||||
{
|
{
|
||||||
ImGui.PushID(nameof(this.DrawExistingTextureModificationArgs));
|
using var pushedId = ImRaii.PushId(nameof(this.DrawExistingTextureModificationArgs));
|
||||||
this.DrawExistingTextureModificationArgs();
|
this.DrawExistingTextureModificationArgs();
|
||||||
ImGui.PopID();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.Dummy(new(ImGui.GetTextLineHeightWithSpacing()));
|
ImGui.Dummy(textHeightSpacing);
|
||||||
|
|
||||||
Action? runLater = null;
|
Action? runLater = null;
|
||||||
foreach (var t in this.addedTextures)
|
foreach (var t in this.addedTextures)
|
||||||
{
|
{
|
||||||
ImGui.PushID(t.Id);
|
using var pushedId = ImRaii.PushId(t.Id);
|
||||||
|
|
||||||
if (ImGui.CollapsingHeader($"Tex #{t.Id} {t}###header", ImGuiTreeNodeFlags.DefaultOpen))
|
if (ImGui.CollapsingHeader($"Tex #{t.Id} {t}###header", ImGuiTreeNodeFlags.DefaultOpen))
|
||||||
{
|
{
|
||||||
if (ImGui.Button("X"u8))
|
if (ImGui.Button("X"u8))
|
||||||
|
|
@ -336,8 +329,6 @@ internal class TexWidget : IDataWindowWidget
|
||||||
ImGui.Text(e.ToString());
|
ImGui.Text(e.ToString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.PopID();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
runLater?.Invoke();
|
runLater?.Invoke();
|
||||||
|
|
@ -357,18 +348,16 @@ internal class TexWidget : IDataWindowWidget
|
||||||
if (ImGui.Button("Reset Columns"u8))
|
if (ImGui.Button("Reset Columns"u8))
|
||||||
this.allLoadedTexturesTableName = "##table" + Environment.TickCount64;
|
this.allLoadedTexturesTableName = "##table" + Environment.TickCount64;
|
||||||
|
|
||||||
if (!ImGui.BeginTable(
|
using var table = ImRaii.Table(this.allLoadedTexturesTableName, (int)DrawBlameTableColumnUserId.ColumnCount, TableFlags);
|
||||||
this.allLoadedTexturesTableName,
|
if (!table.Success)
|
||||||
(int)DrawBlameTableColumnUserId.ColumnCount,
|
|
||||||
ImGuiTableFlags.Sortable | ImGuiTableFlags.SortTristate | ImGuiTableFlags.SortMulti |
|
|
||||||
ImGuiTableFlags.Reorderable | ImGuiTableFlags.Resizable | ImGuiTableFlags.NoBordersInBodyUntilResize |
|
|
||||||
ImGuiTableFlags.NoSavedSettings))
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const int numIcons = 1;
|
const int numIcons = 1;
|
||||||
float iconWidths;
|
float iconWidths;
|
||||||
using (im.IconFontHandle?.Push())
|
using (im.IconFontHandle?.Push())
|
||||||
|
{
|
||||||
iconWidths = ImGui.CalcTextSize(FontAwesomeIcon.Save.ToIconString()).X;
|
iconWidths = ImGui.CalcTextSize(FontAwesomeIcon.Save.ToIconString()).X;
|
||||||
|
}
|
||||||
|
|
||||||
ImGui.TableSetupScrollFreeze(0, 1);
|
ImGui.TableSetupScrollFreeze(0, 1);
|
||||||
ImGui.TableSetupColumn(
|
ImGui.TableSetupColumn(
|
||||||
|
|
@ -463,7 +452,8 @@ internal class TexWidget : IDataWindowWidget
|
||||||
{
|
{
|
||||||
var wrap = allBlames[i];
|
var wrap = allBlames[i];
|
||||||
ImGui.TableNextRow();
|
ImGui.TableNextRow();
|
||||||
ImGui.PushID(i);
|
|
||||||
|
using var pushedId = ImRaii.PushId(i);
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.AlignTextToFramePadding();
|
ImGui.AlignTextToFramePadding();
|
||||||
|
|
@ -480,9 +470,8 @@ internal class TexWidget : IDataWindowWidget
|
||||||
|
|
||||||
if (ImGui.IsItemHovered())
|
if (ImGui.IsItemHovered())
|
||||||
{
|
{
|
||||||
ImGui.BeginTooltip();
|
using var tooltip = ImRaii.Tooltip();
|
||||||
ImGui.Image(wrap.Handle, wrap.Size);
|
ImGui.Image(wrap.Handle, wrap.Size);
|
||||||
ImGui.EndTooltip();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
|
|
@ -504,21 +493,19 @@ internal class TexWidget : IDataWindowWidget
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
lock (wrap.OwnerPlugins)
|
lock (wrap.OwnerPlugins)
|
||||||
this.TextColumnCopiable(string.Join(", ", wrap.OwnerPlugins.Select(static x => x.Name)), false, true);
|
this.TextColumnCopiable(string.Join(", ", wrap.OwnerPlugins.Select(static x => x.Name)), false, true);
|
||||||
|
|
||||||
ImGui.PopID();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
clipper.Destroy();
|
clipper.Destroy();
|
||||||
ImGui.EndTable();
|
|
||||||
|
|
||||||
ImGuiHelpers.ScaledDummy(10);
|
ImGuiHelpers.ScaledDummy(10);
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe void DrawLoadedTextures(ICollection<SharedImmediateTexture> textures)
|
private void DrawLoadedTextures(ICollection<SharedImmediateTexture> textures)
|
||||||
{
|
{
|
||||||
var im = Service<InterfaceManager>.Get();
|
var im = Service<InterfaceManager>.Get();
|
||||||
if (!ImGui.BeginTable("##table"u8, 6))
|
using var table = ImRaii.Table("##table"u8, 6);
|
||||||
|
if (!table.Success)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const int numIcons = 4;
|
const int numIcons = 4;
|
||||||
|
|
@ -576,7 +563,7 @@ internal class TexWidget : IDataWindowWidget
|
||||||
}
|
}
|
||||||
|
|
||||||
var remain = texture.SelfReferenceExpiresInForDebug;
|
var remain = texture.SelfReferenceExpiresInForDebug;
|
||||||
ImGui.PushID(row);
|
using var pushedId = ImRaii.PushId(row);
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.AlignTextToFramePadding();
|
ImGui.AlignTextToFramePadding();
|
||||||
|
|
@ -603,28 +590,26 @@ internal class TexWidget : IDataWindowWidget
|
||||||
|
|
||||||
if (ImGui.IsItemHovered() && texture.GetWrapOrDefault(null) is { } immediate)
|
if (ImGui.IsItemHovered() && texture.GetWrapOrDefault(null) is { } immediate)
|
||||||
{
|
{
|
||||||
ImGui.BeginTooltip();
|
using var tooltip = ImRaii.Tooltip();
|
||||||
ImGui.Image(immediate.Handle, immediate.Size);
|
ImGui.Image(immediate.Handle, immediate.Size);
|
||||||
ImGui.EndTooltip();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
if (ImGuiComponents.IconButton(FontAwesomeIcon.Sync))
|
if (ImGuiComponents.IconButton(FontAwesomeIcon.Sync))
|
||||||
this.textureManager.InvalidatePaths([texture.SourcePathForDebug]);
|
this.textureManager.InvalidatePaths([texture.SourcePathForDebug]);
|
||||||
|
|
||||||
if (ImGui.IsItemHovered())
|
if (ImGui.IsItemHovered())
|
||||||
ImGui.SetTooltip($"Call {nameof(ITextureSubstitutionProvider.InvalidatePaths)}.");
|
ImGui.SetTooltip($"Call {nameof(ITextureSubstitutionProvider.InvalidatePaths)}.");
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
if (remain <= 0)
|
using (ImRaii.Disabled(remain <= 0))
|
||||||
ImGui.BeginDisabled();
|
{
|
||||||
if (ImGuiComponents.IconButton(FontAwesomeIcon.Trash))
|
if (ImGuiComponents.IconButton(FontAwesomeIcon.Trash))
|
||||||
texture.ReleaseSelfReference(true);
|
texture.ReleaseSelfReference(true);
|
||||||
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
|
|
||||||
ImGui.SetTooltip("Release self-reference immediately."u8);
|
|
||||||
if (remain <= 0)
|
|
||||||
ImGui.EndDisabled();
|
|
||||||
|
|
||||||
ImGui.PopID();
|
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
|
||||||
|
ImGui.SetTooltip("Release self-reference immediately."u8);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!valid)
|
if (!valid)
|
||||||
|
|
@ -633,7 +618,6 @@ internal class TexWidget : IDataWindowWidget
|
||||||
}
|
}
|
||||||
|
|
||||||
clipper.Destroy();
|
clipper.Destroy();
|
||||||
ImGui.EndTable();
|
|
||||||
|
|
||||||
ImGuiHelpers.ScaledDummy(10);
|
ImGuiHelpers.ScaledDummy(10);
|
||||||
}
|
}
|
||||||
|
|
@ -752,10 +736,7 @@ internal class TexWidget : IDataWindowWidget
|
||||||
{
|
{
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
if (ImGui.Button("Load File (Async)"u8))
|
if (ImGui.Button("Load File (Async)"u8))
|
||||||
{
|
this.addedTextures.Add(new(Api10: this.textureManager.Shared.GetFromManifestResource(assembly, name).RentAsync()));
|
||||||
this.addedTextures.Add(
|
|
||||||
new(Api10: this.textureManager.Shared.GetFromManifestResource(assembly, name).RentAsync()));
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
if (ImGui.Button("Load File (Immediate)"u8))
|
if (ImGui.Button("Load File (Immediate)"u8))
|
||||||
|
|
@ -768,21 +749,20 @@ internal class TexWidget : IDataWindowWidget
|
||||||
private void DrawCreateFromImGuiViewportAsync()
|
private void DrawCreateFromImGuiViewportAsync()
|
||||||
{
|
{
|
||||||
var viewports = ImGui.GetPlatformIO().Viewports;
|
var viewports = ImGui.GetPlatformIO().Viewports;
|
||||||
if (ImGui.BeginCombo(
|
using (var combo = ImRaii.Combo(nameof(this.viewportTextureArgs.ViewportId), $"{this.viewportIndexInt}. {viewports[this.viewportIndexInt].ID:X08}"))
|
||||||
nameof(this.viewportTextureArgs.ViewportId),
|
|
||||||
$"{this.viewportIndexInt}. {viewports[this.viewportIndexInt].ID:X08}"))
|
|
||||||
{
|
{
|
||||||
for (var i = 0; i < viewports.Size; i++)
|
if (combo.Success)
|
||||||
{
|
{
|
||||||
var sel = this.viewportIndexInt == i;
|
for (var i = 0; i < viewports.Size; i++)
|
||||||
if (ImGui.Selectable($"#{i}: {viewports[i].ID:X08}", ref sel))
|
|
||||||
{
|
{
|
||||||
this.viewportIndexInt = i;
|
var sel = this.viewportIndexInt == i;
|
||||||
ImGui.SetItemDefaultFocus();
|
if (ImGui.Selectable($"#{i}: {viewports[i].ID:X08}", ref sel))
|
||||||
|
{
|
||||||
|
this.viewportIndexInt = i;
|
||||||
|
ImGui.SetItemDefaultFocus();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.EndCombo();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var b = this.viewportTextureArgs.KeepTransparency;
|
var b = this.viewportTextureArgs.KeepTransparency;
|
||||||
|
|
@ -844,17 +824,12 @@ internal class TexWidget : IDataWindowWidget
|
||||||
}
|
}
|
||||||
|
|
||||||
this.supportedRenderTargetFormatNames ??= this.supportedRenderTargetFormats.Select(Enum.GetName).ToArray();
|
this.supportedRenderTargetFormatNames ??= this.supportedRenderTargetFormats.Select(Enum.GetName).ToArray();
|
||||||
ImGui.Combo(
|
ImGui.Combo(nameof(this.textureModificationArgs.DxgiFormat), ref this.renderTargetChoiceInt, this.supportedRenderTargetFormatNames);
|
||||||
nameof(this.textureModificationArgs.DxgiFormat),
|
|
||||||
ref this.renderTargetChoiceInt,
|
|
||||||
this.supportedRenderTargetFormatNames);
|
|
||||||
|
|
||||||
Span<int> wh = stackalloc int[2];
|
Span<int> wh = stackalloc int[2];
|
||||||
wh[0] = this.textureModificationArgs.NewWidth;
|
wh[0] = this.textureModificationArgs.NewWidth;
|
||||||
wh[1] = this.textureModificationArgs.NewHeight;
|
wh[1] = this.textureModificationArgs.NewHeight;
|
||||||
if (ImGui.InputInt(
|
if (ImGui.InputInt($"{nameof(this.textureModificationArgs.NewWidth)}/{nameof(this.textureModificationArgs.NewHeight)}", wh))
|
||||||
$"{nameof(this.textureModificationArgs.NewWidth)}/{nameof(this.textureModificationArgs.NewHeight)}",
|
|
||||||
wh))
|
|
||||||
{
|
{
|
||||||
this.textureModificationArgs.NewWidth = wh[0];
|
this.textureModificationArgs.NewWidth = wh[0];
|
||||||
this.textureModificationArgs.NewHeight = wh[1];
|
this.textureModificationArgs.NewHeight = wh[1];
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ using System.Numerics;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
using Dalamud.Bindings.ImGui;
|
using Dalamud.Bindings.ImGui;
|
||||||
|
using Dalamud.Interface.Utility.Raii;
|
||||||
using Dalamud.Data;
|
using Dalamud.Data;
|
||||||
using Dalamud.Interface.ImGuiNotification;
|
using Dalamud.Interface.ImGuiNotification;
|
||||||
using Dalamud.Interface.ImGuiNotification.Internal;
|
using Dalamud.Interface.ImGuiNotification.Internal;
|
||||||
|
|
@ -33,7 +34,7 @@ internal class UiColorWidget : IDataWindowWidget
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public unsafe void Draw()
|
public void Draw()
|
||||||
{
|
{
|
||||||
var colors = Service<DataManager>.GetNullable()?.GetExcelSheet<UIColor>()
|
var colors = Service<DataManager>.GetNullable()?.GetExcelSheet<UIColor>()
|
||||||
?? throw new InvalidOperationException("UIColor sheet not loaded.");
|
?? throw new InvalidOperationException("UIColor sheet not loaded.");
|
||||||
|
|
@ -45,7 +46,9 @@ internal class UiColorWidget : IDataWindowWidget
|
||||||
"<edgecolor(0xEEEEFF)><color(0x0000FF)>BB<color(stackcolor)><edgecolor(stackcolor)>.<br>" +
|
"<edgecolor(0xEEEEFF)><color(0x0000FF)>BB<color(stackcolor)><edgecolor(stackcolor)>.<br>" +
|
||||||
"· Click on a color to copy the color code.<br>" +
|
"· Click on a color to copy the color code.<br>" +
|
||||||
"· Hover on a color to preview the text with edge, when the next color has been used together.");
|
"· Hover on a color to preview the text with edge, when the next color has been used together.");
|
||||||
if (!ImGui.BeginTable("UIColor"u8, 7))
|
|
||||||
|
using var table = ImRaii.Table("UIColor"u8, 7);
|
||||||
|
if (!table.Success)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ImGui.TableSetupScrollFreeze(0, 1);
|
ImGui.TableSetupScrollFreeze(0, 1);
|
||||||
|
|
@ -94,61 +97,61 @@ internal class UiColorWidget : IDataWindowWidget
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.AlignTextToFramePadding();
|
ImGui.AlignTextToFramePadding();
|
||||||
ImGui.PushID($"row{id}_dark");
|
using (ImRaii.PushId($"row{id}_dark"))
|
||||||
if (this.DrawColorColumn(row.Dark) &&
|
{
|
||||||
adjacentRow.HasValue)
|
if (this.DrawColorColumn(row.Dark) && adjacentRow.HasValue)
|
||||||
DrawEdgePreview(id, row.Dark, adjacentRow.Value.Dark);
|
DrawEdgePreview(id, row.Dark, adjacentRow.Value.Dark);
|
||||||
ImGui.PopID();
|
}
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.AlignTextToFramePadding();
|
ImGui.AlignTextToFramePadding();
|
||||||
ImGui.PushID($"row{id}_light");
|
using (ImRaii.PushId($"row{id}_light"))
|
||||||
if (this.DrawColorColumn(row.Light) &&
|
{
|
||||||
adjacentRow.HasValue)
|
if (this.DrawColorColumn(row.Light) && adjacentRow.HasValue)
|
||||||
DrawEdgePreview(id, row.Light, adjacentRow.Value.Light);
|
DrawEdgePreview(id, row.Light, adjacentRow.Value.Light);
|
||||||
ImGui.PopID();
|
}
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.AlignTextToFramePadding();
|
ImGui.AlignTextToFramePadding();
|
||||||
ImGui.PushID($"row{id}_classic");
|
using (ImRaii.PushId($"row{id}_classic"))
|
||||||
if (this.DrawColorColumn(row.ClassicFF) &&
|
{
|
||||||
adjacentRow.HasValue)
|
if (this.DrawColorColumn(row.ClassicFF) && adjacentRow.HasValue)
|
||||||
DrawEdgePreview(id, row.ClassicFF, adjacentRow.Value.ClassicFF);
|
DrawEdgePreview(id, row.ClassicFF, adjacentRow.Value.ClassicFF);
|
||||||
ImGui.PopID();
|
}
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.AlignTextToFramePadding();
|
ImGui.AlignTextToFramePadding();
|
||||||
ImGui.PushID($"row{id}_blue");
|
using (ImRaii.PushId($"row{id}_blue"))
|
||||||
if (this.DrawColorColumn(row.ClearBlue) &&
|
{
|
||||||
adjacentRow.HasValue)
|
if (this.DrawColorColumn(row.ClearBlue) && adjacentRow.HasValue)
|
||||||
DrawEdgePreview(id, row.ClearBlue, adjacentRow.Value.ClearBlue);
|
DrawEdgePreview(id, row.ClearBlue, adjacentRow.Value.ClearBlue);
|
||||||
ImGui.PopID();
|
}
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.AlignTextToFramePadding();
|
ImGui.AlignTextToFramePadding();
|
||||||
ImGui.PushID($"row{id}_white");
|
using (ImRaii.PushId($"row{id}_white"))
|
||||||
if (this.DrawColorColumn(row.ClearWhite) &&
|
{
|
||||||
adjacentRow.HasValue)
|
if (this.DrawColorColumn(row.ClearWhite) && adjacentRow.HasValue)
|
||||||
DrawEdgePreview(id, row.ClearWhite, adjacentRow.Value.ClearWhite);
|
DrawEdgePreview(id, row.ClearWhite, adjacentRow.Value.ClearWhite);
|
||||||
ImGui.PopID();
|
}
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.AlignTextToFramePadding();
|
ImGui.AlignTextToFramePadding();
|
||||||
ImGui.PushID($"row{id}_green");
|
using (ImRaii.PushId($"row{id}_green"))
|
||||||
if (this.DrawColorColumn(row.ClearGreen) &&
|
{
|
||||||
adjacentRow.HasValue)
|
if (this.DrawColorColumn(row.ClearGreen) && adjacentRow.HasValue)
|
||||||
DrawEdgePreview(id, row.ClearGreen, adjacentRow.Value.ClearGreen);
|
DrawEdgePreview(id, row.ClearGreen, adjacentRow.Value.ClearGreen);
|
||||||
ImGui.PopID();
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
clipper.Destroy();
|
clipper.Destroy();
|
||||||
ImGui.EndTable();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void DrawEdgePreview(uint id, uint sheetColor, uint sheetColor2)
|
private static void DrawEdgePreview(uint id, uint sheetColor, uint sheetColor2)
|
||||||
{
|
{
|
||||||
ImGui.BeginTooltip();
|
using var tooltip = ImRaii.Tooltip();
|
||||||
|
|
||||||
Span<byte> buf = stackalloc byte[256];
|
Span<byte> buf = stackalloc byte[256];
|
||||||
var ptr = 0;
|
var ptr = 0;
|
||||||
ptr += Encoding.UTF8.GetBytes("<colortype(", buf[ptr..]);
|
ptr += Encoding.UTF8.GetBytes("<colortype(", buf[ptr..]);
|
||||||
|
|
@ -185,7 +188,6 @@ internal class UiColorWidget : IDataWindowWidget
|
||||||
EdgeColor = BinaryPrimitives.ReverseEndianness(sheetColor) | 0xFF000000u,
|
EdgeColor = BinaryPrimitives.ReverseEndianness(sheetColor) | 0xFF000000u,
|
||||||
WrapWidth = float.PositiveInfinity,
|
WrapWidth = float.PositiveInfinity,
|
||||||
});
|
});
|
||||||
ImGui.EndTooltip();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool DrawColorColumn(uint sheetColor)
|
private bool DrawColorColumn(uint sheetColor)
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ using Dalamud.Interface.Colors;
|
||||||
using Dalamud.Interface.Components;
|
using Dalamud.Interface.Components;
|
||||||
using Dalamud.Interface.Textures.Internal;
|
using Dalamud.Interface.Textures.Internal;
|
||||||
using Dalamud.Interface.Utility;
|
using Dalamud.Interface.Utility;
|
||||||
|
using Dalamud.Interface.Utility.Raii;
|
||||||
using Dalamud.Memory;
|
using Dalamud.Memory;
|
||||||
|
|
||||||
using Lumina.Data.Files;
|
using Lumina.Data.Files;
|
||||||
|
|
@ -163,17 +164,19 @@ internal class UldWidget : IDataWindowWidget
|
||||||
ImGuiColors.DalamudRed,
|
ImGuiColors.DalamudRed,
|
||||||
$"Error: {nameof(UldFile.AssetData)} is not populated.");
|
$"Error: {nameof(UldFile.AssetData)} is not populated.");
|
||||||
}
|
}
|
||||||
else if (ImGui.BeginTable("##uldTextureEntries"u8, 3, ImGuiTableFlags.RowBg | ImGuiTableFlags.Borders))
|
else
|
||||||
{
|
{
|
||||||
ImGui.TableSetupColumn("Id"u8, ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("000000"u8).X);
|
using var table = ImRaii.Table("##uldTextureEntries"u8, 3, ImGuiTableFlags.RowBg | ImGuiTableFlags.Borders);
|
||||||
ImGui.TableSetupColumn("Path"u8, ImGuiTableColumnFlags.WidthStretch);
|
if (table.Success)
|
||||||
ImGui.TableSetupColumn("Actions"u8, ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("Preview___"u8).X);
|
{
|
||||||
ImGui.TableHeadersRow();
|
ImGui.TableSetupColumn("Id"u8, ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("000000"u8).X);
|
||||||
|
ImGui.TableSetupColumn("Path"u8, ImGuiTableColumnFlags.WidthStretch);
|
||||||
|
ImGui.TableSetupColumn("Actions"u8, ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("Preview___"u8).X);
|
||||||
|
ImGui.TableHeadersRow();
|
||||||
|
|
||||||
foreach (var textureEntry in uld.AssetData)
|
foreach (var textureEntry in uld.AssetData)
|
||||||
this.DrawTextureEntry(textureEntry, textureManager);
|
this.DrawTextureEntry(textureEntry, textureManager);
|
||||||
|
}
|
||||||
ImGui.EndTable();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -287,7 +290,7 @@ internal class UldWidget : IDataWindowWidget
|
||||||
|
|
||||||
if (ImGui.IsItemHovered())
|
if (ImGui.IsItemHovered())
|
||||||
{
|
{
|
||||||
ImGui.BeginTooltip();
|
using var tooltip = ImRaii.Tooltip();
|
||||||
|
|
||||||
var texturePath = GetStringNullTerminated(textureEntry.Path);
|
var texturePath = GetStringNullTerminated(textureEntry.Path);
|
||||||
ImGui.Text($"Base path at {texturePath}:");
|
ImGui.Text($"Base path at {texturePath}:");
|
||||||
|
|
@ -308,8 +311,6 @@ internal class UldWidget : IDataWindowWidget
|
||||||
ImGui.Text(e.ToString());
|
ImGui.Text(e.ToString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.EndTooltip();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -318,15 +319,14 @@ internal class UldWidget : IDataWindowWidget
|
||||||
ImGui.SliderInt("FrameData"u8, ref this.selectedFrameData, 0, timeline.FrameData.Length - 1);
|
ImGui.SliderInt("FrameData"u8, ref this.selectedFrameData, 0, timeline.FrameData.Length - 1);
|
||||||
var frameData = timeline.FrameData[this.selectedFrameData];
|
var frameData = timeline.FrameData[this.selectedFrameData];
|
||||||
ImGui.Text($"FrameInfo: {frameData.StartFrame} -> {frameData.EndFrame}");
|
ImGui.Text($"FrameInfo: {frameData.StartFrame} -> {frameData.EndFrame}");
|
||||||
ImGui.Indent();
|
|
||||||
|
using var indent = ImRaii.PushIndent();
|
||||||
foreach (var frameDataKeyGroup in frameData.KeyGroups)
|
foreach (var frameDataKeyGroup in frameData.KeyGroups)
|
||||||
{
|
{
|
||||||
ImGui.Text($"{frameDataKeyGroup.Usage:G} {frameDataKeyGroup.Type:G}");
|
ImGui.Text($"{frameDataKeyGroup.Usage:G} {frameDataKeyGroup.Type:G}");
|
||||||
foreach (var keyframe in frameDataKeyGroup.Frames)
|
foreach (var keyframe in frameDataKeyGroup.Frames)
|
||||||
this.DrawTimelineKeyGroupFrame(keyframe);
|
this.DrawTimelineKeyGroupFrame(keyframe);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.Unindent();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawTimelineKeyGroupFrame(IKeyframe frame)
|
private void DrawTimelineKeyGroupFrame(IKeyframe frame)
|
||||||
|
|
@ -334,8 +334,7 @@ internal class UldWidget : IDataWindowWidget
|
||||||
switch (frame)
|
switch (frame)
|
||||||
{
|
{
|
||||||
case BaseKeyframeData baseKeyframeData:
|
case BaseKeyframeData baseKeyframeData:
|
||||||
ImGui.Text(
|
ImGui.Text($"Time: {baseKeyframeData.Time} | Interpolation: {baseKeyframeData.Interpolation} | Acceleration: {baseKeyframeData.Acceleration} | Deceleration: {baseKeyframeData.Deceleration}");
|
||||||
$"Time: {baseKeyframeData.Time} | Interpolation: {baseKeyframeData.Interpolation} | Acceleration: {baseKeyframeData.Acceleration} | Deceleration: {baseKeyframeData.Deceleration}");
|
|
||||||
break;
|
break;
|
||||||
case Float1Keyframe float1Keyframe:
|
case Float1Keyframe float1Keyframe:
|
||||||
this.DrawTimelineKeyGroupFrame(float1Keyframe.Keyframe);
|
this.DrawTimelineKeyGroupFrame(float1Keyframe.Keyframe);
|
||||||
|
|
@ -350,8 +349,7 @@ internal class UldWidget : IDataWindowWidget
|
||||||
case Float3Keyframe float3Keyframe:
|
case Float3Keyframe float3Keyframe:
|
||||||
this.DrawTimelineKeyGroupFrame(float3Keyframe.Keyframe);
|
this.DrawTimelineKeyGroupFrame(float3Keyframe.Keyframe);
|
||||||
ImGui.SameLine(0, 0);
|
ImGui.SameLine(0, 0);
|
||||||
ImGui.Text(
|
ImGui.Text($" | Value1: {float3Keyframe.Value[0]} | Value2: {float3Keyframe.Value[1]} | Value3: {float3Keyframe.Value[2]}");
|
||||||
$" | Value1: {float3Keyframe.Value[0]} | Value2: {float3Keyframe.Value[1]} | Value3: {float3Keyframe.Value[2]}");
|
|
||||||
break;
|
break;
|
||||||
case SByte1Keyframe sbyte1Keyframe:
|
case SByte1Keyframe sbyte1Keyframe:
|
||||||
this.DrawTimelineKeyGroupFrame(sbyte1Keyframe.Keyframe);
|
this.DrawTimelineKeyGroupFrame(sbyte1Keyframe.Keyframe);
|
||||||
|
|
@ -366,8 +364,7 @@ internal class UldWidget : IDataWindowWidget
|
||||||
case SByte3Keyframe sbyte3Keyframe:
|
case SByte3Keyframe sbyte3Keyframe:
|
||||||
this.DrawTimelineKeyGroupFrame(sbyte3Keyframe.Keyframe);
|
this.DrawTimelineKeyGroupFrame(sbyte3Keyframe.Keyframe);
|
||||||
ImGui.SameLine(0, 0);
|
ImGui.SameLine(0, 0);
|
||||||
ImGui.Text(
|
ImGui.Text($" | Value1: {sbyte3Keyframe.Value[0]} | Value2: {sbyte3Keyframe.Value[1]} | Value3: {sbyte3Keyframe.Value[2]}");
|
||||||
$" | Value1: {sbyte3Keyframe.Value[0]} | Value2: {sbyte3Keyframe.Value[1]} | Value3: {sbyte3Keyframe.Value[2]}");
|
|
||||||
break;
|
break;
|
||||||
case Byte1Keyframe byte1Keyframe:
|
case Byte1Keyframe byte1Keyframe:
|
||||||
this.DrawTimelineKeyGroupFrame(byte1Keyframe.Keyframe);
|
this.DrawTimelineKeyGroupFrame(byte1Keyframe.Keyframe);
|
||||||
|
|
@ -382,8 +379,7 @@ internal class UldWidget : IDataWindowWidget
|
||||||
case Byte3Keyframe byte3Keyframe:
|
case Byte3Keyframe byte3Keyframe:
|
||||||
this.DrawTimelineKeyGroupFrame(byte3Keyframe.Keyframe);
|
this.DrawTimelineKeyGroupFrame(byte3Keyframe.Keyframe);
|
||||||
ImGui.SameLine(0, 0);
|
ImGui.SameLine(0, 0);
|
||||||
ImGui.Text(
|
ImGui.Text($" | Value1: {byte3Keyframe.Value[0]} | Value2: {byte3Keyframe.Value[1]} | Value3: {byte3Keyframe.Value[2]}");
|
||||||
$" | Value1: {byte3Keyframe.Value[0]} | Value2: {byte3Keyframe.Value[1]} | Value3: {byte3Keyframe.Value[2]}");
|
|
||||||
break;
|
break;
|
||||||
case Short1Keyframe short1Keyframe:
|
case Short1Keyframe short1Keyframe:
|
||||||
this.DrawTimelineKeyGroupFrame(short1Keyframe.Keyframe);
|
this.DrawTimelineKeyGroupFrame(short1Keyframe.Keyframe);
|
||||||
|
|
@ -398,8 +394,7 @@ internal class UldWidget : IDataWindowWidget
|
||||||
case Short3Keyframe short3Keyframe:
|
case Short3Keyframe short3Keyframe:
|
||||||
this.DrawTimelineKeyGroupFrame(short3Keyframe.Keyframe);
|
this.DrawTimelineKeyGroupFrame(short3Keyframe.Keyframe);
|
||||||
ImGui.SameLine(0, 0);
|
ImGui.SameLine(0, 0);
|
||||||
ImGui.Text(
|
ImGui.Text($" | Value1: {short3Keyframe.Value[0]} | Value2: {short3Keyframe.Value[1]} | Value3: {short3Keyframe.Value[2]}");
|
||||||
$" | Value1: {short3Keyframe.Value[0]} | Value2: {short3Keyframe.Value[1]} | Value3: {short3Keyframe.Value[2]}");
|
|
||||||
break;
|
break;
|
||||||
case UShort1Keyframe ushort1Keyframe:
|
case UShort1Keyframe ushort1Keyframe:
|
||||||
this.DrawTimelineKeyGroupFrame(ushort1Keyframe.Keyframe);
|
this.DrawTimelineKeyGroupFrame(ushort1Keyframe.Keyframe);
|
||||||
|
|
@ -414,8 +409,7 @@ internal class UldWidget : IDataWindowWidget
|
||||||
case UShort3Keyframe ushort3Keyframe:
|
case UShort3Keyframe ushort3Keyframe:
|
||||||
this.DrawTimelineKeyGroupFrame(ushort3Keyframe.Keyframe);
|
this.DrawTimelineKeyGroupFrame(ushort3Keyframe.Keyframe);
|
||||||
ImGui.SameLine(0, 0);
|
ImGui.SameLine(0, 0);
|
||||||
ImGui.Text(
|
ImGui.Text($" | Value1: {ushort3Keyframe.Value[0]} | Value2: {ushort3Keyframe.Value[1]} | Value3: {ushort3Keyframe.Value[2]}");
|
||||||
$" | Value1: {ushort3Keyframe.Value[0]} | Value2: {ushort3Keyframe.Value[1]} | Value3: {ushort3Keyframe.Value[2]}");
|
|
||||||
break;
|
break;
|
||||||
case Int1Keyframe int1Keyframe:
|
case Int1Keyframe int1Keyframe:
|
||||||
this.DrawTimelineKeyGroupFrame(int1Keyframe.Keyframe);
|
this.DrawTimelineKeyGroupFrame(int1Keyframe.Keyframe);
|
||||||
|
|
@ -430,8 +424,7 @@ internal class UldWidget : IDataWindowWidget
|
||||||
case Int3Keyframe int3Keyframe:
|
case Int3Keyframe int3Keyframe:
|
||||||
this.DrawTimelineKeyGroupFrame(int3Keyframe.Keyframe);
|
this.DrawTimelineKeyGroupFrame(int3Keyframe.Keyframe);
|
||||||
ImGui.SameLine(0, 0);
|
ImGui.SameLine(0, 0);
|
||||||
ImGui.Text(
|
ImGui.Text($" | Value1: {int3Keyframe.Value[0]} | Value2: {int3Keyframe.Value[1]} | Value3: {int3Keyframe.Value[2]}");
|
||||||
$" | Value1: {int3Keyframe.Value[0]} | Value2: {int3Keyframe.Value[1]} | Value3: {int3Keyframe.Value[2]}");
|
|
||||||
break;
|
break;
|
||||||
case UInt1Keyframe uint1Keyframe:
|
case UInt1Keyframe uint1Keyframe:
|
||||||
this.DrawTimelineKeyGroupFrame(uint1Keyframe.Keyframe);
|
this.DrawTimelineKeyGroupFrame(uint1Keyframe.Keyframe);
|
||||||
|
|
@ -446,8 +439,7 @@ internal class UldWidget : IDataWindowWidget
|
||||||
case UInt3Keyframe uint3Keyframe:
|
case UInt3Keyframe uint3Keyframe:
|
||||||
this.DrawTimelineKeyGroupFrame(uint3Keyframe.Keyframe);
|
this.DrawTimelineKeyGroupFrame(uint3Keyframe.Keyframe);
|
||||||
ImGui.SameLine(0, 0);
|
ImGui.SameLine(0, 0);
|
||||||
ImGui.Text(
|
ImGui.Text($" | Value1: {uint3Keyframe.Value[0]} | Value2: {uint3Keyframe.Value[1]} | Value3: {uint3Keyframe.Value[2]}");
|
||||||
$" | Value1: {uint3Keyframe.Value[0]} | Value2: {uint3Keyframe.Value[1]} | Value3: {uint3Keyframe.Value[2]}");
|
|
||||||
break;
|
break;
|
||||||
case Bool1Keyframe bool1Keyframe:
|
case Bool1Keyframe bool1Keyframe:
|
||||||
this.DrawTimelineKeyGroupFrame(bool1Keyframe.Keyframe);
|
this.DrawTimelineKeyGroupFrame(bool1Keyframe.Keyframe);
|
||||||
|
|
@ -462,28 +454,22 @@ internal class UldWidget : IDataWindowWidget
|
||||||
case Bool3Keyframe bool3Keyframe:
|
case Bool3Keyframe bool3Keyframe:
|
||||||
this.DrawTimelineKeyGroupFrame(bool3Keyframe.Keyframe);
|
this.DrawTimelineKeyGroupFrame(bool3Keyframe.Keyframe);
|
||||||
ImGui.SameLine(0, 0);
|
ImGui.SameLine(0, 0);
|
||||||
ImGui.Text(
|
ImGui.Text($" | Value1: {bool3Keyframe.Value[0]} | Value2: {bool3Keyframe.Value[1]} | Value3: {bool3Keyframe.Value[2]}");
|
||||||
$" | Value1: {bool3Keyframe.Value[0]} | Value2: {bool3Keyframe.Value[1]} | Value3: {bool3Keyframe.Value[2]}");
|
|
||||||
break;
|
break;
|
||||||
case ColorKeyframe colorKeyframe:
|
case ColorKeyframe colorKeyframe:
|
||||||
this.DrawTimelineKeyGroupFrame(colorKeyframe.Keyframe);
|
this.DrawTimelineKeyGroupFrame(colorKeyframe.Keyframe);
|
||||||
ImGui.SameLine(0, 0);
|
ImGui.SameLine(0, 0);
|
||||||
ImGui.Text(
|
ImGui.Text($" | Add: {colorKeyframe.AddRed} {colorKeyframe.AddGreen} {colorKeyframe.AddBlue} | Multiply: {colorKeyframe.MultiplyRed} {colorKeyframe.MultiplyGreen} {colorKeyframe.MultiplyBlue}");
|
||||||
$" | Add: {colorKeyframe.AddRed} {colorKeyframe.AddGreen} {colorKeyframe.AddBlue} | Multiply: {colorKeyframe.MultiplyRed} {colorKeyframe.MultiplyGreen} {colorKeyframe.MultiplyBlue}");
|
|
||||||
break;
|
break;
|
||||||
case LabelKeyframe labelKeyframe:
|
case LabelKeyframe labelKeyframe:
|
||||||
this.DrawTimelineKeyGroupFrame(labelKeyframe.Keyframe);
|
this.DrawTimelineKeyGroupFrame(labelKeyframe.Keyframe);
|
||||||
ImGui.SameLine(0, 0);
|
ImGui.SameLine(0, 0);
|
||||||
ImGui.Text(
|
ImGui.Text($" | LabelCommand: {labelKeyframe.LabelCommand} | JumpId: {labelKeyframe.JumpId} | LabelId: {labelKeyframe.LabelId}");
|
||||||
$" | LabelCommand: {labelKeyframe.LabelCommand} | JumpId: {labelKeyframe.JumpId} | LabelId: {labelKeyframe.LabelId}");
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawParts(
|
private void DrawParts(UldRoot.PartsData partsData, UldRoot.TextureEntry[] textureEntries, TextureManager textureManager)
|
||||||
UldRoot.PartsData partsData,
|
|
||||||
UldRoot.TextureEntry[] textureEntries,
|
|
||||||
TextureManager textureManager)
|
|
||||||
{
|
{
|
||||||
for (var index = 0; index < partsData.Parts.Length; index++)
|
for (var index = 0; index < partsData.Parts.Length; index++)
|
||||||
{
|
{
|
||||||
|
|
@ -549,10 +535,9 @@ internal class UldWidget : IDataWindowWidget
|
||||||
|
|
||||||
if (ImGui.IsItemHovered())
|
if (ImGui.IsItemHovered())
|
||||||
{
|
{
|
||||||
ImGui.BeginTooltip();
|
using var tooltip = ImRaii.Tooltip();
|
||||||
ImGui.Text("Click to copy:"u8);
|
ImGui.Text("Click to copy:"u8);
|
||||||
ImGui.Text(texturePath);
|
ImGui.Text(texturePath);
|
||||||
ImGui.EndTooltip();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
88
Dalamud/Plugin/Services/IAgentLifecycle.cs
Normal file
88
Dalamud/Plugin/Services/IAgentLifecycle.cs
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
using Dalamud.Game.Agent;
|
||||||
|
using Dalamud.Game.Agent.AgentArgTypes;
|
||||||
|
|
||||||
|
namespace Dalamud.Plugin.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This class provides events for in-game agent lifecycles.
|
||||||
|
/// </summary>
|
||||||
|
public interface IAgentLifecycle : IDalamudService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Delegate for receiving agent lifecycle event messages.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">The event type that triggered the message.</param>
|
||||||
|
/// <param name="args">Information about what agent triggered the message.</param>
|
||||||
|
public delegate void AgentEventDelegate(AgentEvent type, AgentArgs args);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Register a listener that will trigger on the specified event and any of the specified agent.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="eventType">Event type to trigger on.</param>
|
||||||
|
/// <param name="agentIds">Agent IDs that will trigger the handler to be invoked.</param>
|
||||||
|
/// <param name="handler">The handler to invoke.</param>
|
||||||
|
void RegisterListener(AgentEvent eventType, IEnumerable<AgentId> agentIds, AgentEventDelegate handler);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Register a listener that will trigger on the specified event only for the specified agent.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="eventType">Event type to trigger on.</param>
|
||||||
|
/// <param name="agentId">The agent ID that will trigger the handler to be invoked.</param>
|
||||||
|
/// <param name="handler">The handler to invoke.</param>
|
||||||
|
void RegisterListener(AgentEvent eventType, AgentId agentId, AgentEventDelegate handler);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Register a listener that will trigger on the specified event for any agent.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="eventType">Event type to trigger on.</param>
|
||||||
|
/// <param name="handler">The handler to invoke.</param>
|
||||||
|
void RegisterListener(AgentEvent eventType, AgentEventDelegate handler);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unregister listener from specified event type and specified agent IDs.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// If a specific handler is not provided, all handlers for the event type and agent IDs will be unregistered.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="eventType">Event type to deregister.</param>
|
||||||
|
/// <param name="agentIds">Agent IDs to deregister.</param>
|
||||||
|
/// <param name="handler">Optional specific handler to remove.</param>
|
||||||
|
void UnregisterListener(AgentEvent eventType, IEnumerable<AgentId> agentIds, [Optional] AgentEventDelegate handler);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unregister all listeners for the specified event type and agent ID.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// If a specific handler is not provided, all handlers for the event type and agents will be unregistered.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="eventType">Event type to deregister.</param>
|
||||||
|
/// <param name="agentId">Agent id to deregister.</param>
|
||||||
|
/// <param name="handler">Optional specific handler to remove.</param>
|
||||||
|
void UnregisterListener(AgentEvent eventType, AgentId agentId, [Optional] AgentEventDelegate handler);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unregister an event type handler.<br/>This will only remove a handler that is added via <see cref="RegisterListener(AgentEvent, AgentEventDelegate)"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// If a specific handler is not provided, all handlers for the event type and agents will be unregistered.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="eventType">Event type to deregister.</param>
|
||||||
|
/// <param name="handler">Optional specific handler to remove.</param>
|
||||||
|
void UnregisterListener(AgentEvent eventType, [Optional] AgentEventDelegate handler);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unregister all events that use the specified handlers.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="handlers">Handlers to remove.</param>
|
||||||
|
void UnregisterListener(params AgentEventDelegate[] handlers);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resolves an agents virtual table address back to the original unmodified table address.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="virtualTableAddress">The address of a modified agents virtual table.</param>
|
||||||
|
/// <returns>The address of the agents original virtual table.</returns>
|
||||||
|
nint GetOriginalVirtualTable(nint virtualTableAddress);
|
||||||
|
}
|
||||||
|
|
@ -10,7 +10,6 @@ namespace Dalamud.Plugin.Services;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Interface for determining unlock state of various content in the game.
|
/// Interface for determining unlock state of various content in the game.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Experimental("Dalamud001")]
|
|
||||||
public interface IUnlockState : IDalamudService
|
public interface IUnlockState : IDalamudService
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<RootNamespace>Dalamud.EnumGenerator.Sample</RootNamespace>
|
||||||
|
|
||||||
|
<ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Dalamud.EnumGenerator\Dalamud.EnumGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Remove="EnumCloneMap.txt"/>
|
||||||
|
<AdditionalFiles Include="EnumCloneMap.txt" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
# Format: Target.Full.TypeName = Source.Full.EnumTypeName
|
||||||
|
# Example: Generate a local enum MyGeneratedEnum in namespace Sample.Gen mapped to SourceEnums.SampleSourceEnum
|
||||||
|
Dalamud.EnumGenerator.Sample.Gen.MyGeneratedEnum = Dalamud.EnumGenerator.Sample.SourceEnums.SampleSourceEnum
|
||||||
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
namespace Dalamud.EnumGenerator.Sample.SourceEnums
|
||||||
|
{
|
||||||
|
public enum SampleSourceEnum : long
|
||||||
|
{
|
||||||
|
First = 1,
|
||||||
|
Second = 2,
|
||||||
|
Third = 10000000000L
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
|
||||||
|
<RootNamespace>Dalamud.EnumGenerator.Tests</RootNamespace>
|
||||||
|
|
||||||
|
<ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.XUnit" Version="1.1.1"/>
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.2"/>
|
||||||
|
<PackageReference Include="xunit" Version="2.4.2"/>
|
||||||
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
</PackageReference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Dalamud.EnumGenerator\Dalamud.EnumGenerator.csproj"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
||||||
|
</Project>
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Dalamud.EnumGenerator.Tests;
|
||||||
|
|
||||||
|
public class EnumCloneMapTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void ParseMappings_SimpleLines_ParsesCorrectly()
|
||||||
|
{
|
||||||
|
var text = @"# Comment line
|
||||||
|
My.Namespace.Target = Other.Namespace.Source
|
||||||
|
|
||||||
|
Another.Target = Some.Source";
|
||||||
|
|
||||||
|
var results = Dalamud.EnumGenerator.EnumCloneGenerator.ParseMappings(text);
|
||||||
|
|
||||||
|
Assert.Equal(2, results.Length);
|
||||||
|
Assert.Equal("My.Namespace.Target", results[0].TargetFullName);
|
||||||
|
Assert.Equal("Other.Namespace.Source", results[0].SourceFullName);
|
||||||
|
Assert.Equal("Another.Target", results[1].TargetFullName);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Generator_ProducesFile_WhenSourceResolved()
|
||||||
|
{
|
||||||
|
// We'll create a compilation that contains a source enum type and add an AdditionalText mapping
|
||||||
|
var sourceEnum = @"namespace Foo.Bar { public enum SourceEnum { A = 1, B = 2 } }";
|
||||||
|
|
||||||
|
var mapText = "GeneratedNs.TargetEnum = Foo.Bar.SourceEnum";
|
||||||
|
|
||||||
|
var generator = new EnumCloneGenerator();
|
||||||
|
var driver = CSharpGeneratorDriver.Create(generator)
|
||||||
|
.AddAdditionalTexts(ImmutableArray.Create<AdditionalText>(new Utils.TestAdditionalFile("EnumCloneMap.txt", mapText)));
|
||||||
|
|
||||||
|
var compilation = CSharpCompilation.Create("TestGen", [CSharpSyntaxTree.ParseText(sourceEnum)],
|
||||||
|
[MetadataReference.CreateFromFile(typeof(object).Assembly.Location)]);
|
||||||
|
|
||||||
|
driver.RunGeneratorsAndUpdateCompilation(compilation, out var newCompilation, out var diagnostics);
|
||||||
|
|
||||||
|
var generated = newCompilation.SyntaxTrees.Select(t => t.FilePath).Where(p => p.EndsWith("TargetEnum.CloneEnum.g.cs")).ToArray();
|
||||||
|
Assert.Single(generated);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
using Microsoft.CodeAnalysis.Text;
|
||||||
|
|
||||||
|
namespace Dalamud.EnumGenerator.Tests.Utils;
|
||||||
|
|
||||||
|
public class TestAdditionalFile : AdditionalText
|
||||||
|
{
|
||||||
|
private readonly SourceText text;
|
||||||
|
|
||||||
|
public TestAdditionalFile(string path, string text)
|
||||||
|
{
|
||||||
|
Path = path;
|
||||||
|
this.text = SourceText.From(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override SourceText GetText(CancellationToken cancellationToken = new()) => this.text;
|
||||||
|
|
||||||
|
public override string Path { get; }
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
; Shipped analyzer releases
|
||||||
|
; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md
|
||||||
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
; Unshipped analyzer release
|
||||||
|
; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md
|
||||||
|
|
||||||
|
### New Rules
|
||||||
|
|
||||||
|
Rule ID | Category | Severity | Notes
|
||||||
|
--------|----------|----------|-------
|
||||||
|
ENUMGEN001 | EnumGenerator | Warning | SourceGeneratorWithAttributes
|
||||||
|
ENUMGEN002 | EnumGenerator | Warning | SourceGeneratorWithAttributes
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<LangVersion>latest</LangVersion>
|
||||||
|
|
||||||
|
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
|
||||||
|
<IsRoslynComponent>true</IsRoslynComponent>
|
||||||
|
|
||||||
|
<RootNamespace>Dalamud.EnumGenerator</RootNamespace>
|
||||||
|
<PackageId>Dalamud.EnumGenerator</PackageId>
|
||||||
|
|
||||||
|
<ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.3.0"/>
|
||||||
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.3.0"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<AdditionalFiles Include="AnalyzerReleases.Shipped.md" />
|
||||||
|
<AdditionalFiles Include="AnalyzerReleases.Unshipped.md" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
||||||
|
</Project>
|
||||||
|
|
@ -0,0 +1,193 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
using Microsoft.CodeAnalysis.Text;
|
||||||
|
|
||||||
|
namespace Dalamud.EnumGenerator;
|
||||||
|
|
||||||
|
[Generator]
|
||||||
|
public class EnumCloneGenerator : IIncrementalGenerator
|
||||||
|
{
|
||||||
|
private const string NewLine = "\r\n";
|
||||||
|
|
||||||
|
private const string MappingFileName = "EnumCloneMap.txt";
|
||||||
|
|
||||||
|
private static readonly DiagnosticDescriptor MissingSourceDescriptor = new(
|
||||||
|
id: "ENUMGEN001",
|
||||||
|
title: "Source enum not found",
|
||||||
|
messageFormat: "Source enum '{0}' could not be resolved by the compilation",
|
||||||
|
category: "EnumGenerator",
|
||||||
|
defaultSeverity: DiagnosticSeverity.Warning,
|
||||||
|
isEnabledByDefault: true);
|
||||||
|
|
||||||
|
private static readonly DiagnosticDescriptor DuplicateTargetDescriptor = new(
|
||||||
|
id: "ENUMGEN002",
|
||||||
|
title: "Duplicate target mapping",
|
||||||
|
messageFormat: "Target enum '{0}' is mapped multiple times; generation skipped for this target",
|
||||||
|
category: "EnumGenerator",
|
||||||
|
defaultSeverity: DiagnosticSeverity.Warning,
|
||||||
|
isEnabledByDefault: true);
|
||||||
|
|
||||||
|
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||||
|
{
|
||||||
|
// Read mappings from additional files named EnumCloneMap.txt
|
||||||
|
var mappingEntries = context.AdditionalTextsProvider
|
||||||
|
.Where(at => Path.GetFileName(at.Path).Equals(MappingFileName, StringComparison.OrdinalIgnoreCase))
|
||||||
|
.SelectMany((at, _) => ParseMappings(at.GetText()?.ToString() ?? string.Empty));
|
||||||
|
|
||||||
|
// Combine with compilation so we can resolve types
|
||||||
|
var compilationAndMaps = context.CompilationProvider.Combine(mappingEntries.Collect());
|
||||||
|
|
||||||
|
context.RegisterSourceOutput(compilationAndMaps, (spc, pair) =>
|
||||||
|
{
|
||||||
|
var compilation = pair.Left;
|
||||||
|
var maps = pair.Right;
|
||||||
|
|
||||||
|
// Detect duplicate targets first and report diagnostics
|
||||||
|
var duplicateTargets = maps.GroupBy(m => m.TargetFullName, StringComparer.OrdinalIgnoreCase)
|
||||||
|
.Where(g => g.Count() > 1)
|
||||||
|
.Select(g => g.Key)
|
||||||
|
.ToImmutableArray();
|
||||||
|
foreach (var dup in duplicateTargets)
|
||||||
|
{
|
||||||
|
var diag = Diagnostic.Create(DuplicateTargetDescriptor, Location.None, dup);
|
||||||
|
spc.ReportDiagnostic(diag);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var (targetFullName, sourceFullName) in maps)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(targetFullName) || string.IsNullOrWhiteSpace(sourceFullName))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (duplicateTargets.Contains(targetFullName, StringComparer.OrdinalIgnoreCase))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Resolve the source enum type by metadata name (namespace.type)
|
||||||
|
var sourceSymbol = compilation.GetTypeByMetadataName(sourceFullName);
|
||||||
|
if (sourceSymbol is null)
|
||||||
|
{
|
||||||
|
// Report diagnostic for missing source type
|
||||||
|
var diag = Diagnostic.Create(MissingSourceDescriptor, Location.None, sourceFullName);
|
||||||
|
spc.ReportDiagnostic(diag);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sourceSymbol.TypeKind != TypeKind.Enum)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var sourceNamed = sourceSymbol; // GetTypeByMetadataName already returns INamedTypeSymbol
|
||||||
|
|
||||||
|
// Split target into namespace and type name
|
||||||
|
string? targetNamespace = null;
|
||||||
|
var targetName = targetFullName;
|
||||||
|
var lastDot = targetFullName.LastIndexOf('.');
|
||||||
|
if (lastDot >= 0)
|
||||||
|
{
|
||||||
|
targetNamespace = targetFullName.Substring(0, lastDot);
|
||||||
|
targetName = targetFullName.Substring(lastDot + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
var underlyingType = sourceNamed.EnumUnderlyingType;
|
||||||
|
var underlyingDisplay = underlyingType?.ToDisplayString() ?? "int";
|
||||||
|
|
||||||
|
var fields = sourceNamed.GetMembers()
|
||||||
|
.OfType<IFieldSymbol>()
|
||||||
|
.Where(f => f.IsStatic && f.HasConstantValue)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
var memberLines = fields.Select(f =>
|
||||||
|
{
|
||||||
|
var name = f.Name;
|
||||||
|
var constValue = f.ConstantValue;
|
||||||
|
string literal;
|
||||||
|
|
||||||
|
var st = underlyingType?.SpecialType ?? SpecialType.System_Int32;
|
||||||
|
|
||||||
|
if (constValue is null)
|
||||||
|
{
|
||||||
|
literal = "0";
|
||||||
|
}
|
||||||
|
else if (st == SpecialType.System_UInt64)
|
||||||
|
{
|
||||||
|
literal = Convert.ToString(constValue, CultureInfo.InvariantCulture) + "UL";
|
||||||
|
}
|
||||||
|
else if (st == SpecialType.System_UInt32)
|
||||||
|
{
|
||||||
|
literal = Convert.ToString(constValue, CultureInfo.InvariantCulture) + "U";
|
||||||
|
}
|
||||||
|
else if (st == SpecialType.System_Int64)
|
||||||
|
{
|
||||||
|
literal = Convert.ToString(constValue, CultureInfo.InvariantCulture) + "L";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
literal = Convert.ToString(constValue, CultureInfo.InvariantCulture) ?? throw new InvalidOperationException("Unable to convert enum constant value to string.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return $" {name} = {literal},";
|
||||||
|
});
|
||||||
|
|
||||||
|
var membersText = string.Join(NewLine, memberLines);
|
||||||
|
|
||||||
|
var nsPrefix = targetNamespace is null ? string.Empty : $"namespace {targetNamespace};" + NewLine + NewLine;
|
||||||
|
|
||||||
|
var sourceFullyQualified = sourceNamed.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
|
||||||
|
|
||||||
|
var code = "// <auto-generated/>" + NewLine + NewLine
|
||||||
|
+ nsPrefix
|
||||||
|
+ $"public enum {targetName} : {underlyingDisplay}" + NewLine
|
||||||
|
+ "{" + NewLine
|
||||||
|
+ membersText + NewLine
|
||||||
|
+ "}" + NewLine + NewLine;
|
||||||
|
|
||||||
|
var extClassName = targetName + "Conversions";
|
||||||
|
var extMethodName = "ToDalamud" + targetName;
|
||||||
|
|
||||||
|
var extClass = $"public static class {extClassName}" + NewLine
|
||||||
|
+ "{" + NewLine
|
||||||
|
+ $" public static {targetName} {extMethodName}(this {sourceFullyQualified} value) => ({targetName})(({underlyingDisplay})value);" + NewLine
|
||||||
|
+ "}" + NewLine;
|
||||||
|
|
||||||
|
code += extClass;
|
||||||
|
|
||||||
|
var hintName = $"{targetName}.CloneEnum.g.cs";
|
||||||
|
spc.AddSource(hintName, SourceText.From(code, Encoding.UTF8));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static ImmutableArray<(string TargetFullName, string SourceFullName)> ParseMappings(string text)
|
||||||
|
{
|
||||||
|
var builder = ImmutableArray.CreateBuilder<(string, string)>();
|
||||||
|
using var reader = new StringReader(text);
|
||||||
|
string? line;
|
||||||
|
while ((line = reader.ReadLine()) != null)
|
||||||
|
{
|
||||||
|
// Remove comments starting with #
|
||||||
|
var commentIndex = line.IndexOf('#');
|
||||||
|
var content = commentIndex >= 0 ? line.Substring(0, commentIndex) : line;
|
||||||
|
content = content.Trim();
|
||||||
|
if (string.IsNullOrEmpty(content))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Expected format: Target.Full.Name = Source.Full.Name
|
||||||
|
var idx = content.IndexOf('=');
|
||||||
|
if (idx <= 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var left = content.Substring(0, idx).Trim();
|
||||||
|
var right = content.Substring(idx + 1).Trim();
|
||||||
|
if (string.IsNullOrEmpty(left) || string.IsNullOrEmpty(right))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
builder.Add((left, right));
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.ToImmutable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
[assembly: InternalsVisibleTo("Dalamud.EnumGenerator.Tests")]
|
||||||
|
|
||||||
5
generators/Directory.Build.props
Normal file
5
generators/Directory.Build.props
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<Project>
|
||||||
|
<ItemGroup Label="Code Analysis">
|
||||||
|
<PackageReference Remove="Microsoft.CodeAnalysis.BannedApiAnalyzers" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue