mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-15 05:04:15 +01:00
Merge branch 'v9' into ihookprovider
This commit is contained in:
commit
a59875bb77
164 changed files with 6033 additions and 1795 deletions
|
|
@ -57,12 +57,12 @@ dotnet_naming_symbols.private_static_fields_symbols.required_modifiers = static
|
|||
dotnet_naming_symbols.private_static_readonly_symbols.applicable_accessibilities = private
|
||||
dotnet_naming_symbols.private_static_readonly_symbols.applicable_kinds = field
|
||||
dotnet_naming_symbols.private_static_readonly_symbols.required_modifiers = static,readonly
|
||||
dotnet_style_parentheses_in_arithmetic_binary_operators =always_for_clarity:suggestion
|
||||
dotnet_style_parentheses_in_other_binary_operators =always_for_clarity:suggestion
|
||||
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:suggestion
|
||||
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:suggestion
|
||||
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:suggestion
|
||||
dotnet_style_predefined_type_for_member_access = true:suggestion
|
||||
dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion
|
||||
dotnet_style_parentheses_in_other_operators=always_for_clarity:silent
|
||||
dotnet_style_parentheses_in_other_operators = always_for_clarity:silent
|
||||
dotnet_style_object_initializer = false
|
||||
dotnet_style_qualification_for_event = true:suggestion
|
||||
dotnet_style_qualification_for_field = true:suggestion
|
||||
|
|
@ -78,7 +78,7 @@ csharp_space_before_comma = false
|
|||
csharp_space_after_keywords_in_control_flow_statements = true
|
||||
csharp_space_after_comma = true
|
||||
csharp_space_after_cast = false
|
||||
csharp_space_around_binary_operators = before_and_after
|
||||
csharp_space_around_binary_operators = before_and_after
|
||||
csharp_space_between_method_declaration_name_and_open_parenthesis = false
|
||||
csharp_space_between_method_declaration_parameter_list_parentheses = false
|
||||
csharp_space_between_parentheses = none
|
||||
|
|
@ -101,7 +101,7 @@ resharper_braces_for_ifelse = required_for_multiline
|
|||
resharper_can_use_global_alias = false
|
||||
resharper_csharp_align_multiline_parameter = true
|
||||
resharper_csharp_align_multiple_declaration = true
|
||||
resharper_csharp_empty_block_style = together_same_line
|
||||
resharper_csharp_empty_block_style = multiline
|
||||
resharper_csharp_int_align_comments = true
|
||||
resharper_csharp_new_line_before_while = true
|
||||
resharper_csharp_wrap_after_declaration_lpar = true
|
||||
|
|
@ -133,13 +133,13 @@ resharper_suggest_var_or_type_built_in_types_highlighting = hint
|
|||
resharper_suggest_var_or_type_elsewhere_highlighting = hint
|
||||
resharper_suggest_var_or_type_simple_types_highlighting = hint
|
||||
resharper_unused_auto_property_accessor_global_highlighting = none
|
||||
csharp_style_deconstructed_variable_declaration=true:silent
|
||||
csharp_style_deconstructed_variable_declaration = true:silent
|
||||
|
||||
[*.{appxmanifest,asax,ascx,aspx,axaml,axml,build,c,c++,cc,cginc,compute,config,cp,cpp,cs,cshtml,csproj,css,cu,cuh,cxx,dbml,discomap,dtd,h,hh,hlsl,hlsli,hlslinc,hpp,htm,html,hxx,inc,inl,ino,ipp,js,json,jsproj,jsx,lsproj,master,mpp,mq4,mq5,mqh,njsproj,nuspec,paml,proj,props,proto,razor,resjson,resw,resx,skin,StyleCop,targets,tasks,tpp,ts,tsx,usf,ush,vb,vbproj,xaml,xamlx,xml,xoml,xsd}]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
tab_width = 4
|
||||
dotnet_style_parentheses_in_other_operators=always_for_clarity:silent
|
||||
dotnet_style_parentheses_in_other_operators = always_for_clarity:silent
|
||||
|
||||
[*.{yaml,yml}]
|
||||
indent_style = space
|
||||
|
|
|
|||
|
|
@ -133,8 +133,8 @@ DWORD WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) {
|
|||
// ============================== VEH ======================================== //
|
||||
|
||||
logging::I("Initializing VEH...");
|
||||
if (utils::is_running_on_linux()) {
|
||||
logging::I("=> VEH was disabled, running on linux");
|
||||
if (utils::is_running_on_wine()) {
|
||||
logging::I("=> VEH was disabled, running on wine");
|
||||
} else if (g_startInfo.BootVehEnabled) {
|
||||
if (veh::add_handler(g_startInfo.BootVehFull, g_startInfo.WorkingDirectory))
|
||||
logging::I("=> Done!");
|
||||
|
|
|
|||
|
|
@ -578,7 +578,7 @@ std::vector<std::string> utils::get_env_list(const wchar_t* pcszName) {
|
|||
return res;
|
||||
}
|
||||
|
||||
bool utils::is_running_on_linux() {
|
||||
bool utils::is_running_on_wine() {
|
||||
if (get_env<bool>(L"XL_WINEONLINUX"))
|
||||
return true;
|
||||
HMODULE hntdll = GetModuleHandleW(L"ntdll.dll");
|
||||
|
|
@ -588,6 +588,10 @@ bool utils::is_running_on_linux() {
|
|||
return true;
|
||||
if (GetProcAddress(hntdll, "wine_get_host_version"))
|
||||
return true;
|
||||
if (GetProcAddress(hntdll, "wine_server_call"))
|
||||
return true;
|
||||
if (GetProcAddress(hntdll, "wine_unix_to_nt_file_name"))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -264,7 +264,7 @@ namespace utils {
|
|||
return get_env_list<T>(unicode::convert<std::wstring>(pcszName).c_str());
|
||||
}
|
||||
|
||||
bool is_running_on_linux();
|
||||
bool is_running_on_wine();
|
||||
|
||||
std::filesystem::path get_module_path(HMODULE hModule);
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Lumina" Version="3.10.2" />
|
||||
<PackageReference Include="Lumina" Version="3.11.0" />
|
||||
<PackageReference Include="Lumina.Excel" Version="6.4.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.333">
|
||||
|
|
|
|||
|
|
@ -6,7 +6,9 @@ using Dalamud.Game.Command;
|
|||
using Dalamud.Interface.Windowing;
|
||||
using Dalamud.Logging;
|
||||
using Dalamud.Plugin;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Dalamud.Utility;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.CorePlugin
|
||||
{
|
||||
|
|
@ -55,7 +57,7 @@ namespace Dalamud.CorePlugin
|
|||
/// </summary>
|
||||
/// <param name="pluginInterface">Dalamud plugin interface.</param>
|
||||
/// <param name="log">Logging service.</param>
|
||||
public PluginImpl(DalamudPluginInterface pluginInterface, PluginLog log)
|
||||
public PluginImpl(DalamudPluginInterface pluginInterface, IPluginLog log)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
|
@ -66,6 +68,7 @@ namespace Dalamud.CorePlugin
|
|||
|
||||
this.Interface.UiBuilder.Draw += this.OnDraw;
|
||||
this.Interface.UiBuilder.OpenConfigUi += this.OnOpenConfigUi;
|
||||
this.Interface.UiBuilder.OpenMainUi += this.OnOpenMainUi;
|
||||
|
||||
Service<CommandManager>.Get().AddHandler("/coreplug", new(this.OnCommand) { HelpMessage = $"Access the {this.Name} plugin." });
|
||||
|
||||
|
|
@ -143,6 +146,11 @@ namespace Dalamud.CorePlugin
|
|||
// this.window.IsOpen = true;
|
||||
}
|
||||
|
||||
private void OnOpenMainUi()
|
||||
{
|
||||
Log.Verbose("Opened main UI");
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,6 +69,12 @@ internal sealed class DalamudConfiguration : IServiceType
|
|||
/// </summary>
|
||||
public string LastVersion { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating the last seen FTUE version.
|
||||
/// Unused for now, added to prevent existing users from seeing level 0 FTUE.
|
||||
/// </summary>
|
||||
public int SeenFtueLevel { get; set; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the last loaded Dalamud version.
|
||||
/// </summary>
|
||||
|
|
@ -94,6 +100,11 @@ internal sealed class DalamudConfiguration : IServiceType
|
|||
/// </summary>
|
||||
public List<ThirdPartyRepoSettings> ThirdRepoList { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not a disclaimer regarding third-party repos has been dismissed.
|
||||
/// </summary>
|
||||
public bool? ThirdRepoSpeedbumpDismissed { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a list of hidden plugins.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
using System;
|
||||
|
||||
namespace Dalamud.Configuration.Internal;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -14,4 +16,9 @@ internal sealed class DevPluginSettings
|
|||
/// Gets or sets a value indicating whether this plugin should automatically reload on file change.
|
||||
/// </summary>
|
||||
public bool AutomaticReloading { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets an ID uniquely identifying this specific instance of a devPlugin.
|
||||
/// </summary>
|
||||
public Guid WorkingPluginId { get; set; } = Guid.Empty;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
<AssemblyVersion>$(DalamudVersion)</AssemblyVersion>
|
||||
<Version>$(DalamudVersion)</Version>
|
||||
<FileVersion>$(DalamudVersion)</FileVersion>
|
||||
<PackageLicenseExpression>AGPL-3.0-or-later</PackageLicenseExpression>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Label="Output">
|
||||
|
|
@ -67,7 +68,7 @@
|
|||
<PackageReference Include="goaaats.Reloaded.Hooks" Version="4.2.0-goat.4" />
|
||||
<PackageReference Include="goaaats.Reloaded.Assembler" Version="1.0.14-goat.2" />
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2021.2.0" />
|
||||
<PackageReference Include="Lumina" Version="3.10.2" />
|
||||
<PackageReference Include="Lumina" Version="3.11.0" />
|
||||
<PackageReference Include="Lumina.Excel" Version="6.4.0" />
|
||||
<PackageReference Include="MinSharp" Version="1.0.4" />
|
||||
<PackageReference Include="MonoModReorg.RuntimeDetour" Version="23.1.2-prerelease.1" />
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ namespace Dalamud.Data;
|
|||
#pragma warning disable SA1015
|
||||
[ResolveVia<IDataManager>]
|
||||
#pragma warning restore SA1015
|
||||
public sealed class DataManager : IDisposable, IServiceType, IDataManager
|
||||
internal sealed class DataManager : IDisposable, IServiceType, IDataManager
|
||||
{
|
||||
private readonly Thread luminaResourceThread;
|
||||
private readonly CancellationTokenSource luminaCancellationTokenSource;
|
||||
|
|
@ -126,10 +126,14 @@ public sealed class DataManager : IDisposable, IServiceType, IDataManager
|
|||
/// <inheritdoc/>
|
||||
public ClientLanguage Language { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <summary>
|
||||
/// Gets a list of server opcodes.
|
||||
/// </summary>
|
||||
public ReadOnlyDictionary<string, ushort> ServerOpCodes { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of client opcodes.
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public ReadOnlyDictionary<string, ushort> ClientOpCodes { get; private set; }
|
||||
|
||||
|
|
|
|||
|
|
@ -166,7 +166,7 @@ public sealed class EntryPoint
|
|||
// This is due to GitHub not supporting TLS 1.0, so we enable all TLS versions globally
|
||||
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12 | SecurityProtocolType.Tls;
|
||||
|
||||
if (!Util.IsLinux())
|
||||
if (!Util.IsWine())
|
||||
InitSymbolHandler(info);
|
||||
|
||||
var dalamud = new Dalamud(info, configuration, mainThreadContinueEvent);
|
||||
|
|
|
|||
97
Dalamud/Game/AddonEventManager/AddonCursorType.cs
Normal file
97
Dalamud/Game/AddonEventManager/AddonCursorType.cs
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
namespace Dalamud.Game.AddonEventManager;
|
||||
|
||||
/// <summary>
|
||||
/// Reimplementation of CursorType.
|
||||
/// </summary>
|
||||
public enum AddonCursorType
|
||||
{
|
||||
/// <summary>
|
||||
/// Arrow.
|
||||
/// </summary>
|
||||
Arrow,
|
||||
|
||||
/// <summary>
|
||||
/// Boot.
|
||||
/// </summary>
|
||||
Boot,
|
||||
|
||||
/// <summary>
|
||||
/// Search.
|
||||
/// </summary>
|
||||
Search,
|
||||
|
||||
/// <summary>
|
||||
/// Chat Pointer.
|
||||
/// </summary>
|
||||
ChatPointer,
|
||||
|
||||
/// <summary>
|
||||
/// Interact.
|
||||
/// </summary>
|
||||
Interact,
|
||||
|
||||
/// <summary>
|
||||
/// Attack.
|
||||
/// </summary>
|
||||
Attack,
|
||||
|
||||
/// <summary>
|
||||
/// Hand.
|
||||
/// </summary>
|
||||
Hand,
|
||||
|
||||
/// <summary>
|
||||
/// Resizeable Left-Right.
|
||||
/// </summary>
|
||||
ResizeWE,
|
||||
|
||||
/// <summary>
|
||||
/// Resizeable Up-Down.
|
||||
/// </summary>
|
||||
ResizeNS,
|
||||
|
||||
/// <summary>
|
||||
/// Resizeable.
|
||||
/// </summary>
|
||||
ResizeNWSR,
|
||||
|
||||
/// <summary>
|
||||
/// Resizeable 4-way.
|
||||
/// </summary>
|
||||
ResizeNESW,
|
||||
|
||||
/// <summary>
|
||||
/// Clickable.
|
||||
/// </summary>
|
||||
Clickable,
|
||||
|
||||
/// <summary>
|
||||
/// Text Input.
|
||||
/// </summary>
|
||||
TextInput,
|
||||
|
||||
/// <summary>
|
||||
/// Text Click.
|
||||
/// </summary>
|
||||
TextClick,
|
||||
|
||||
/// <summary>
|
||||
/// Grab.
|
||||
/// </summary>
|
||||
Grab,
|
||||
|
||||
/// <summary>
|
||||
/// Chat Bubble.
|
||||
/// </summary>
|
||||
ChatBubble,
|
||||
|
||||
/// <summary>
|
||||
/// No Access.
|
||||
/// </summary>
|
||||
NoAccess,
|
||||
|
||||
/// <summary>
|
||||
/// Hidden.
|
||||
/// </summary>
|
||||
Hidden,
|
||||
}
|
||||
87
Dalamud/Game/AddonEventManager/AddonEventListener.cs
Normal file
87
Dalamud/Game/AddonEventManager/AddonEventListener.cs
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
|
||||
namespace Dalamud.Game.AddonEventManager;
|
||||
|
||||
/// <summary>
|
||||
/// Event listener class for managing custom events.
|
||||
/// </summary>
|
||||
// Custom event handler tech provided by Pohky, implemented by MidoriKami
|
||||
internal unsafe class AddonEventListener : IDisposable
|
||||
{
|
||||
private ReceiveEventDelegate? receiveEventDelegate;
|
||||
|
||||
private AtkEventListener* eventListener;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AddonEventListener"/> class.
|
||||
/// </summary>
|
||||
/// <param name="eventHandler">The managed handler to send events to.</param>
|
||||
public AddonEventListener(ReceiveEventDelegate eventHandler)
|
||||
{
|
||||
this.receiveEventDelegate = eventHandler;
|
||||
|
||||
this.eventListener = (AtkEventListener*)Marshal.AllocHGlobal(sizeof(AtkEventListener));
|
||||
this.eventListener->vtbl = (void*)Marshal.AllocHGlobal(sizeof(void*) * 3);
|
||||
this.eventListener->vfunc[0] = (delegate* unmanaged<void>)&NullSub;
|
||||
this.eventListener->vfunc[1] = (delegate* unmanaged<void>)&NullSub;
|
||||
this.eventListener->vfunc[2] = (void*)Marshal.GetFunctionPointerForDelegate(this.receiveEventDelegate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delegate for receiving custom events.
|
||||
/// </summary>
|
||||
/// <param name="self">Pointer to the event listener.</param>
|
||||
/// <param name="eventType">Event type.</param>
|
||||
/// <param name="eventParam">Unique Id for this event.</param>
|
||||
/// <param name="eventData">Event Data.</param>
|
||||
/// <param name="unknown">Unknown Parameter.</param>
|
||||
public delegate void ReceiveEventDelegate(AtkEventListener* self, AtkEventType eventType, uint eventParam, AtkEvent* eventData, nint unknown);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
if (this.eventListener is null) return;
|
||||
|
||||
Marshal.FreeHGlobal((nint)this.eventListener->vtbl);
|
||||
Marshal.FreeHGlobal((nint)this.eventListener);
|
||||
|
||||
this.eventListener = null;
|
||||
this.receiveEventDelegate = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register an event to this event handler.
|
||||
/// </summary>
|
||||
/// <param name="addon">Addon that triggers this event.</param>
|
||||
/// <param name="node">Node to attach event to.</param>
|
||||
/// <param name="eventType">Event type to trigger this event.</param>
|
||||
/// <param name="param">Unique id for this event.</param>
|
||||
public void RegisterEvent(AtkUnitBase* addon, AtkResNode* node, AtkEventType eventType, uint param)
|
||||
{
|
||||
if (node is null) return;
|
||||
|
||||
node->AddEvent(eventType, param, this.eventListener, (AtkResNode*)addon, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unregister an event from this event handler.
|
||||
/// </summary>
|
||||
/// <param name="node">Node to remove the event from.</param>
|
||||
/// <param name="eventType">Event type that this event is for.</param>
|
||||
/// <param name="param">Unique id for this event.</param>
|
||||
public void UnregisterEvent(AtkResNode* node, AtkEventType eventType, uint param)
|
||||
{
|
||||
if (node is null) return;
|
||||
|
||||
node->RemoveEvent(eventType, param, this.eventListener, false);
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
private static void NullSub()
|
||||
{
|
||||
/* do nothing */
|
||||
}
|
||||
}
|
||||
253
Dalamud/Game/AddonEventManager/AddonEventManager.cs
Normal file
253
Dalamud/Game/AddonEventManager/AddonEventManager.cs
Normal file
|
|
@ -0,0 +1,253 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
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.Component.GUI;
|
||||
|
||||
namespace Dalamud.Game.AddonEventManager;
|
||||
|
||||
/// <summary>
|
||||
/// Service provider for addon event management.
|
||||
/// </summary>
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
internal unsafe class AddonEventManager : IDisposable, IServiceType, IAddonEventManager
|
||||
{
|
||||
private static readonly ModuleLog Log = new("AddonEventManager");
|
||||
|
||||
private readonly AddonEventManagerAddressResolver address;
|
||||
private readonly Hook<UpdateCursorDelegate> onUpdateCursor;
|
||||
|
||||
private readonly AddonEventListener eventListener;
|
||||
private readonly Dictionary<uint, IAddonEventManager.AddonEventHandler> eventHandlers;
|
||||
|
||||
private AddonCursorType? cursorOverride;
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private AddonEventManager(SigScanner sigScanner)
|
||||
{
|
||||
this.address = new AddonEventManagerAddressResolver();
|
||||
this.address.Setup(sigScanner);
|
||||
|
||||
this.eventHandlers = new Dictionary<uint, IAddonEventManager.AddonEventHandler>();
|
||||
this.eventListener = new AddonEventListener(this.DalamudAddonEventHandler);
|
||||
|
||||
this.cursorOverride = null;
|
||||
|
||||
this.onUpdateCursor = Hook<UpdateCursorDelegate>.FromAddress(this.address.UpdateCursor, this.UpdateCursorDetour);
|
||||
}
|
||||
|
||||
private delegate nint UpdateCursorDelegate(RaptureAtkModule* module);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void AddEvent(uint eventId, IntPtr atkUnitBase, IntPtr atkResNode, AddonEventType eventType, IAddonEventManager.AddonEventHandler eventHandler)
|
||||
{
|
||||
if (!this.eventHandlers.ContainsKey(eventId))
|
||||
{
|
||||
var type = (AtkEventType)eventType;
|
||||
var node = (AtkResNode*)atkResNode;
|
||||
var addon = (AtkUnitBase*)atkUnitBase;
|
||||
|
||||
this.eventHandlers.Add(eventId, eventHandler);
|
||||
this.eventListener.RegisterEvent(addon, node, type, eventId);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Warning($"Attempted to register already registered eventId: {eventId}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void RemoveEvent(uint eventId, IntPtr atkResNode, AddonEventType eventType)
|
||||
{
|
||||
if (this.eventHandlers.ContainsKey(eventId))
|
||||
{
|
||||
var type = (AtkEventType)eventType;
|
||||
var node = (AtkResNode*)atkResNode;
|
||||
|
||||
this.eventListener.UnregisterEvent(node, type, eventId);
|
||||
this.eventHandlers.Remove(eventId);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Warning($"Attempted to unregister already unregistered eventId: {eventId}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
this.onUpdateCursor.Dispose();
|
||||
this.eventListener.Dispose();
|
||||
this.eventHandlers.Clear();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void SetCursor(AddonCursorType cursor) => this.cursorOverride = cursor;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void ResetCursor() => this.cursorOverride = null;
|
||||
|
||||
[ServiceManager.CallWhenServicesReady]
|
||||
private void ContinueConstruction()
|
||||
{
|
||||
this.onUpdateCursor.Enable();
|
||||
}
|
||||
|
||||
private nint UpdateCursorDetour(RaptureAtkModule* module)
|
||||
{
|
||||
try
|
||||
{
|
||||
var atkStage = AtkStage.GetSingleton();
|
||||
|
||||
if (this.cursorOverride is not null && atkStage is not null)
|
||||
{
|
||||
var cursor = (AddonCursorType)atkStage->AtkCursor.Type;
|
||||
if (cursor != this.cursorOverride)
|
||||
{
|
||||
AtkStage.GetSingleton()->AtkCursor.SetCursorType((AtkCursor.CursorType)this.cursorOverride, 1);
|
||||
}
|
||||
|
||||
return nint.Zero;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Exception in UpdateCursorDetour.");
|
||||
}
|
||||
|
||||
return this.onUpdateCursor!.Original(module);
|
||||
}
|
||||
|
||||
private void DalamudAddonEventHandler(AtkEventListener* self, AtkEventType eventType, uint eventParam, AtkEvent* eventData, IntPtr unknown)
|
||||
{
|
||||
if (this.eventHandlers.TryGetValue(eventParam, out var handler) && eventData is not null)
|
||||
{
|
||||
try
|
||||
{
|
||||
// We passed the AtkUnitBase into the EventData.Node field from our AddonEventHandler
|
||||
handler?.Invoke((AddonEventType)eventType, (nint)eventData->Node, (nint)eventData->Target);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.Error(exception, "Exception in DalamudAddonEventHandler custom event invoke.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plugin-scoped version of a AddonEventManager service.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.ScopedService]
|
||||
#pragma warning disable SA1015
|
||||
[ResolveVia<IAddonEventManager>]
|
||||
#pragma warning restore SA1015
|
||||
internal unsafe class AddonEventManagerPluginScoped : IDisposable, IServiceType, IAddonEventManager
|
||||
{
|
||||
private static readonly ModuleLog Log = new("AddonEventManager");
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly AddonEventManager baseEventManager = Service<AddonEventManager>.Get();
|
||||
|
||||
private readonly AddonEventListener eventListener;
|
||||
private readonly Dictionary<uint, IAddonEventManager.AddonEventHandler> eventHandlers;
|
||||
|
||||
private bool isForcingCursor;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AddonEventManagerPluginScoped"/> class.
|
||||
/// </summary>
|
||||
public AddonEventManagerPluginScoped()
|
||||
{
|
||||
this.eventHandlers = new Dictionary<uint, IAddonEventManager.AddonEventHandler>();
|
||||
this.eventListener = new AddonEventListener(this.PluginAddonEventHandler);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
// if multiple plugins force cursors and dispose without un-forcing them then all forces will be cleared.
|
||||
if (this.isForcingCursor)
|
||||
{
|
||||
this.baseEventManager.ResetCursor();
|
||||
}
|
||||
|
||||
this.eventListener.Dispose();
|
||||
this.eventHandlers.Clear();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void AddEvent(uint eventId, IntPtr atkUnitBase, IntPtr atkResNode, AddonEventType eventType, IAddonEventManager.AddonEventHandler eventHandler)
|
||||
{
|
||||
if (!this.eventHandlers.ContainsKey(eventId))
|
||||
{
|
||||
var type = (AtkEventType)eventType;
|
||||
var node = (AtkResNode*)atkResNode;
|
||||
var addon = (AtkUnitBase*)atkUnitBase;
|
||||
|
||||
this.eventHandlers.Add(eventId, eventHandler);
|
||||
this.eventListener.RegisterEvent(addon, node, type, eventId);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Warning($"Attempted to register already registered eventId: {eventId}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void RemoveEvent(uint eventId, IntPtr atkResNode, AddonEventType eventType)
|
||||
{
|
||||
if (this.eventHandlers.ContainsKey(eventId))
|
||||
{
|
||||
var type = (AtkEventType)eventType;
|
||||
var node = (AtkResNode*)atkResNode;
|
||||
|
||||
this.eventListener.UnregisterEvent(node, type, eventId);
|
||||
this.eventHandlers.Remove(eventId);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Warning($"Attempted to unregister already unregistered eventId: {eventId}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void SetCursor(AddonCursorType cursor)
|
||||
{
|
||||
this.isForcingCursor = true;
|
||||
|
||||
this.baseEventManager.SetCursor(cursor);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void ResetCursor()
|
||||
{
|
||||
this.isForcingCursor = false;
|
||||
|
||||
this.baseEventManager.ResetCursor();
|
||||
}
|
||||
|
||||
private void PluginAddonEventHandler(AtkEventListener* self, AtkEventType eventType, uint eventParam, AtkEvent* eventData, IntPtr unknown)
|
||||
{
|
||||
if (this.eventHandlers.TryGetValue(eventParam, out var handler) && eventData is not null)
|
||||
{
|
||||
try
|
||||
{
|
||||
// We passed the AtkUnitBase into the EventData.Node field from our AddonEventHandler
|
||||
handler?.Invoke((AddonEventType)eventType, (nint)eventData->Node, (nint)eventData->Target);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.Error(exception, "Exception in PluginAddonEventHandler custom event invoke.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
namespace Dalamud.Game.AddonEventManager;
|
||||
|
||||
/// <summary>
|
||||
/// AddonEventManager memory address resolver.
|
||||
/// </summary>
|
||||
internal class AddonEventManagerAddressResolver : BaseAddressResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the address of the AtkModule UpdateCursor method.
|
||||
/// </summary>
|
||||
public nint UpdateCursor { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Scan for and setup any configured address pointers.
|
||||
/// </summary>
|
||||
/// <param name="scanner">The signature scanner to facilitate setup.</param>
|
||||
protected override void Setup64Bit(SigScanner scanner)
|
||||
{
|
||||
this.UpdateCursor = scanner.ScanText("48 89 74 24 ?? 48 89 7C 24 ?? 41 56 48 83 EC 20 4C 8B F1 E8 ?? ?? ?? ?? 49 8B CE");
|
||||
}
|
||||
}
|
||||
132
Dalamud/Game/AddonEventManager/AddonEventType.cs
Normal file
132
Dalamud/Game/AddonEventManager/AddonEventType.cs
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
namespace Dalamud.Game.AddonEventManager;
|
||||
|
||||
/// <summary>
|
||||
/// Reimplementation of AtkEventType.
|
||||
/// </summary>
|
||||
public enum AddonEventType : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Mouse Down.
|
||||
/// </summary>
|
||||
MouseDown = 3,
|
||||
|
||||
/// <summary>
|
||||
/// Mouse Up.
|
||||
/// </summary>
|
||||
MouseUp = 4,
|
||||
|
||||
/// <summary>
|
||||
/// Mouse Move.
|
||||
/// </summary>
|
||||
MouseMove = 5,
|
||||
|
||||
/// <summary>
|
||||
/// Mouse Over.
|
||||
/// </summary>
|
||||
MouseOver = 6,
|
||||
|
||||
/// <summary>
|
||||
/// Mouse Out.
|
||||
/// </summary>
|
||||
MouseOut = 7,
|
||||
|
||||
/// <summary>
|
||||
/// Mouse Click.
|
||||
/// </summary>
|
||||
MouseClick = 9,
|
||||
|
||||
/// <summary>
|
||||
/// Input Received.
|
||||
/// </summary>
|
||||
InputReceived = 12,
|
||||
|
||||
/// <summary>
|
||||
/// Focus Start.
|
||||
/// </summary>
|
||||
FocusStart = 18,
|
||||
|
||||
/// <summary>
|
||||
/// Focus Stop.
|
||||
/// </summary>
|
||||
FocusStop = 19,
|
||||
|
||||
/// <summary>
|
||||
/// Button Press, sent on MouseDown on Button.
|
||||
/// </summary>
|
||||
ButtonPress = 23,
|
||||
|
||||
/// <summary>
|
||||
/// Button Release, sent on MouseUp and MouseOut.
|
||||
/// </summary>
|
||||
ButtonRelease = 24,
|
||||
|
||||
/// <summary>
|
||||
/// Button Click, sent on MouseUp and MouseClick on button.
|
||||
/// </summary>
|
||||
ButtonClick = 25,
|
||||
|
||||
/// <summary>
|
||||
/// List Item RollOver.
|
||||
/// </summary>
|
||||
ListItemRollOver = 33,
|
||||
|
||||
/// <summary>
|
||||
/// List Item Roll Out.
|
||||
/// </summary>
|
||||
ListItemRollOut = 34,
|
||||
|
||||
/// <summary>
|
||||
/// List Item Toggle.
|
||||
/// </summary>
|
||||
ListItemToggle = 35,
|
||||
|
||||
/// <summary>
|
||||
/// Drag Drop Roll Over.
|
||||
/// </summary>
|
||||
DragDropRollOver = 52,
|
||||
|
||||
/// <summary>
|
||||
/// Drag Drop Roll Out.
|
||||
/// </summary>
|
||||
DragDropRollOut = 53,
|
||||
|
||||
/// <summary>
|
||||
/// Drag Drop Unknown.
|
||||
/// </summary>
|
||||
DragDropUnk54 = 54,
|
||||
|
||||
/// <summary>
|
||||
/// Drag Drop Unknown.
|
||||
/// </summary>
|
||||
DragDropUnk55 = 55,
|
||||
|
||||
/// <summary>
|
||||
/// Icon Text Roll Over.
|
||||
/// </summary>
|
||||
IconTextRollOver = 56,
|
||||
|
||||
/// <summary>
|
||||
/// Icon Text Roll Out.
|
||||
/// </summary>
|
||||
IconTextRollOut = 57,
|
||||
|
||||
/// <summary>
|
||||
/// Icon Text Click.
|
||||
/// </summary>
|
||||
IconTextClick = 58,
|
||||
|
||||
/// <summary>
|
||||
/// Window Roll Over.
|
||||
/// </summary>
|
||||
WindowRollOver = 67,
|
||||
|
||||
/// <summary>
|
||||
/// Window Roll Out.
|
||||
/// </summary>
|
||||
WindowRollOut = 68,
|
||||
|
||||
/// <summary>
|
||||
/// Window Change Scale.
|
||||
/// </summary>
|
||||
WindowChangeScale = 69,
|
||||
}
|
||||
22
Dalamud/Game/AddonLifecycle/AddonArgs.cs
Normal file
22
Dalamud/Game/AddonLifecycle/AddonArgs.cs
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
using Dalamud.Memory;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
|
||||
namespace Dalamud.Game.AddonLifecycle;
|
||||
|
||||
/// <summary>
|
||||
/// Addon argument data for use in event subscribers.
|
||||
/// </summary>
|
||||
public unsafe class AddonArgs
|
||||
{
|
||||
private string? addonName;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the addon this args referrers to.
|
||||
/// </summary>
|
||||
public string AddonName => this.Addon == nint.Zero ? "NullAddon" : this.addonName ??= MemoryHelper.ReadString((nint)((AtkUnitBase*)this.Addon)->Name, 0x20);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the pointer to the addons AtkUnitBase.
|
||||
/// </summary>
|
||||
required public nint Addon { get; init; }
|
||||
}
|
||||
62
Dalamud/Game/AddonLifecycle/AddonEvent.cs
Normal file
62
Dalamud/Game/AddonLifecycle/AddonEvent.cs
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
namespace Dalamud.Game.AddonLifecycle;
|
||||
|
||||
/// <summary>
|
||||
/// Enumeration for available AddonLifecycle events.
|
||||
/// </summary>
|
||||
public enum AddonEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// Event that is fired before an addon begins it's setup process.
|
||||
/// </summary>
|
||||
PreSetup,
|
||||
|
||||
/// <summary>
|
||||
/// Event that is fired after an addon has completed it's setup process.
|
||||
/// </summary>
|
||||
PostSetup,
|
||||
|
||||
/// <summary>
|
||||
/// Event that is fired before an addon begins update.
|
||||
/// </summary>
|
||||
PreUpdate,
|
||||
|
||||
/// <summary>
|
||||
/// Event that is fired after an addon has completed update.
|
||||
/// </summary>
|
||||
PostUpdate,
|
||||
|
||||
/// <summary>
|
||||
/// Event that is fired before an addon begins draw.
|
||||
/// </summary>
|
||||
PreDraw,
|
||||
|
||||
/// <summary>
|
||||
/// Event that is fired after an addon has completed draw.
|
||||
/// </summary>
|
||||
PostDraw,
|
||||
|
||||
/// <summary>
|
||||
/// Event that is fired before an addon is finalized.
|
||||
/// </summary>
|
||||
PreFinalize,
|
||||
|
||||
/// <summary>
|
||||
/// Event that is fired before an addon begins a requested update.
|
||||
/// </summary>
|
||||
PreRequestedUpdate,
|
||||
|
||||
/// <summary>
|
||||
/// Event that is fired after an addon finishes a requested update.
|
||||
/// </summary>
|
||||
PostRequestedUpdate,
|
||||
|
||||
/// <summary>
|
||||
/// Event that is fired before an addon begins a refresh.
|
||||
/// </summary>
|
||||
PreRefresh,
|
||||
|
||||
/// <summary>
|
||||
/// Event that is fired after an addon has finished a refresh.
|
||||
/// </summary>
|
||||
PostRefresh,
|
||||
}
|
||||
364
Dalamud/Game/AddonLifecycle/AddonLifecycle.cs
Normal file
364
Dalamud/Game/AddonLifecycle/AddonLifecycle.cs
Normal file
|
|
@ -0,0 +1,364 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Hooking.Internal;
|
||||
using Dalamud.IoC;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Logging.Internal;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
|
||||
namespace Dalamud.Game.AddonLifecycle;
|
||||
|
||||
/// <summary>
|
||||
/// This class provides events for in-game addon lifecycles.
|
||||
/// </summary>
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
internal unsafe class AddonLifecycle : IDisposable, IServiceType
|
||||
{
|
||||
private static readonly ModuleLog Log = new("AddonLifecycle");
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly Framework framework = Service<Framework>.Get();
|
||||
|
||||
private readonly AddonLifecycleAddressResolver address;
|
||||
private readonly Hook<AddonSetupDelegate> onAddonSetupHook;
|
||||
private readonly Hook<AddonFinalizeDelegate> onAddonFinalizeHook;
|
||||
private readonly CallHook<AddonDrawDelegate> onAddonDrawHook;
|
||||
private readonly CallHook<AddonUpdateDelegate> onAddonUpdateHook;
|
||||
private readonly Hook<AddonOnRefreshDelegate> onAddonRefreshHook;
|
||||
private readonly CallHook<AddonOnRequestedUpdateDelegate> onAddonRequestedUpdateHook;
|
||||
|
||||
private readonly ConcurrentBag<AddonLifecycleEventListener> newEventListeners = new();
|
||||
private readonly ConcurrentBag<AddonLifecycleEventListener> removeEventListeners = new();
|
||||
private readonly List<AddonLifecycleEventListener> eventListeners = new();
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private AddonLifecycle(SigScanner sigScanner)
|
||||
{
|
||||
this.address = new AddonLifecycleAddressResolver();
|
||||
this.address.Setup(sigScanner);
|
||||
|
||||
this.framework.Update += this.OnFrameworkUpdate;
|
||||
|
||||
this.onAddonSetupHook = Hook<AddonSetupDelegate>.FromAddress(this.address.AddonSetup, this.OnAddonSetup);
|
||||
this.onAddonFinalizeHook = Hook<AddonFinalizeDelegate>.FromAddress(this.address.AddonFinalize, this.OnAddonFinalize);
|
||||
this.onAddonDrawHook = new CallHook<AddonDrawDelegate>(this.address.AddonDraw, this.OnAddonDraw);
|
||||
this.onAddonUpdateHook = new CallHook<AddonUpdateDelegate>(this.address.AddonUpdate, this.OnAddonUpdate);
|
||||
this.onAddonRefreshHook = Hook<AddonOnRefreshDelegate>.FromAddress(this.address.AddonOnRefresh, this.OnAddonRefresh);
|
||||
this.onAddonRequestedUpdateHook = new CallHook<AddonOnRequestedUpdateDelegate>(this.address.AddonOnRequestedUpdate, this.OnRequestedUpdate);
|
||||
}
|
||||
|
||||
private delegate nint AddonSetupDelegate(AtkUnitBase* addon);
|
||||
|
||||
private delegate void AddonFinalizeDelegate(AtkUnitManager* unitManager, AtkUnitBase** atkUnitBase);
|
||||
|
||||
private delegate void AddonDrawDelegate(AtkUnitBase* addon);
|
||||
|
||||
private delegate void AddonUpdateDelegate(AtkUnitBase* addon, float delta);
|
||||
|
||||
private delegate void AddonOnRequestedUpdateDelegate(AtkUnitBase* addon, NumberArrayData** numberArrayData, StringArrayData** stringArrayData);
|
||||
|
||||
private delegate byte AddonOnRefreshDelegate(AtkUnitManager* unitManager, AtkUnitBase* addon, uint valueCount, AtkValue* values);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
this.framework.Update -= this.OnFrameworkUpdate;
|
||||
|
||||
this.onAddonSetupHook.Dispose();
|
||||
this.onAddonFinalizeHook.Dispose();
|
||||
this.onAddonDrawHook.Dispose();
|
||||
this.onAddonUpdateHook.Dispose();
|
||||
this.onAddonRefreshHook.Dispose();
|
||||
this.onAddonRequestedUpdateHook.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register a listener for the target event and addon.
|
||||
/// </summary>
|
||||
/// <param name="listener">The listener to register.</param>
|
||||
internal void RegisterListener(AddonLifecycleEventListener listener)
|
||||
{
|
||||
this.newEventListeners.Add(listener);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unregisters the listener from events.
|
||||
/// </summary>
|
||||
/// <param name="listener">The listener to unregister.</param>
|
||||
internal void UnregisterListener(AddonLifecycleEventListener listener)
|
||||
{
|
||||
this.removeEventListeners.Add(listener);
|
||||
}
|
||||
|
||||
// Used to prevent concurrency issues if plugins try to register during iteration of listeners.
|
||||
private void OnFrameworkUpdate(IFramework unused)
|
||||
{
|
||||
if (this.newEventListeners.Any())
|
||||
{
|
||||
this.eventListeners.AddRange(this.newEventListeners);
|
||||
this.newEventListeners.Clear();
|
||||
}
|
||||
|
||||
if (this.removeEventListeners.Any())
|
||||
{
|
||||
foreach (var toRemoveListener in this.removeEventListeners)
|
||||
{
|
||||
this.eventListeners.Remove(toRemoveListener);
|
||||
}
|
||||
|
||||
this.removeEventListeners.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
[ServiceManager.CallWhenServicesReady]
|
||||
private void ContinueConstruction()
|
||||
{
|
||||
this.onAddonSetupHook.Enable();
|
||||
this.onAddonFinalizeHook.Enable();
|
||||
this.onAddonDrawHook.Enable();
|
||||
this.onAddonUpdateHook.Enable();
|
||||
this.onAddonRefreshHook.Enable();
|
||||
this.onAddonRequestedUpdateHook.Enable();
|
||||
}
|
||||
|
||||
private void InvokeListeners(AddonEvent eventType, AddonArgs args)
|
||||
{
|
||||
// Match on string.empty for listeners that want events for all addons.
|
||||
foreach (var listener in this.eventListeners.Where(listener => listener.EventType == eventType && (listener.AddonName == args.AddonName || listener.AddonName == string.Empty)))
|
||||
{
|
||||
listener.FunctionDelegate.Invoke(eventType, args);
|
||||
}
|
||||
}
|
||||
|
||||
private nint OnAddonSetup(AtkUnitBase* addon)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.InvokeListeners(AddonEvent.PreSetup, new AddonArgs { Addon = (nint)addon });
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Exception in OnAddonSetup pre-setup invoke.");
|
||||
}
|
||||
|
||||
var result = this.onAddonSetupHook.Original(addon);
|
||||
|
||||
try
|
||||
{
|
||||
this.InvokeListeners(AddonEvent.PostSetup, new AddonArgs { Addon = (nint)addon });
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Exception in OnAddonSetup post-setup invoke.");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void OnAddonFinalize(AtkUnitManager* unitManager, AtkUnitBase** atkUnitBase)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.InvokeListeners(AddonEvent.PreFinalize, new AddonArgs { Addon = (nint)atkUnitBase[0] });
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Exception in OnAddonFinalize pre-finalize invoke.");
|
||||
}
|
||||
|
||||
this.onAddonFinalizeHook.Original(unitManager, atkUnitBase);
|
||||
}
|
||||
|
||||
private void OnAddonDraw(AtkUnitBase* addon)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.InvokeListeners(AddonEvent.PreDraw, new AddonArgs { Addon = (nint)addon });
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Exception in OnAddonDraw pre-draw invoke.");
|
||||
}
|
||||
|
||||
addon->Draw();
|
||||
|
||||
try
|
||||
{
|
||||
this.InvokeListeners(AddonEvent.PostDraw, new AddonArgs { Addon = (nint)addon });
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Exception in OnAddonDraw post-draw invoke.");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAddonUpdate(AtkUnitBase* addon, float delta)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.InvokeListeners(AddonEvent.PreUpdate, new AddonArgs { Addon = (nint)addon });
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Exception in OnAddonUpdate pre-update invoke.");
|
||||
}
|
||||
|
||||
addon->Update(delta);
|
||||
|
||||
try
|
||||
{
|
||||
this.InvokeListeners(AddonEvent.PostUpdate, new AddonArgs { Addon = (nint)addon });
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Exception in OnAddonUpdate post-update invoke.");
|
||||
}
|
||||
}
|
||||
|
||||
private byte OnAddonRefresh(AtkUnitManager* atkUnitManager, AtkUnitBase* addon, uint valueCount, AtkValue* values)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.InvokeListeners(AddonEvent.PreRefresh, new AddonArgs { Addon = (nint)addon });
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Exception in OnAddonRefresh pre-refresh invoke.");
|
||||
}
|
||||
|
||||
var result = this.onAddonRefreshHook.Original(atkUnitManager, addon, valueCount, values);
|
||||
|
||||
try
|
||||
{
|
||||
this.InvokeListeners(AddonEvent.PostRefresh, new AddonArgs { Addon = (nint)addon });
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Exception in OnAddonRefresh post-refresh invoke.");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void OnRequestedUpdate(AtkUnitBase* addon, NumberArrayData** numberArrayData, StringArrayData** stringArrayData)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.InvokeListeners(AddonEvent.PreRequestedUpdate, new AddonArgs { Addon = (nint)addon });
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Exception in OnRequestedUpdate pre-requestedUpdate invoke.");
|
||||
}
|
||||
|
||||
addon->OnUpdate(numberArrayData, stringArrayData);
|
||||
|
||||
try
|
||||
{
|
||||
this.InvokeListeners(AddonEvent.PostRequestedUpdate, new AddonArgs { Addon = (nint)addon });
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Exception in OnRequestedUpdate post-requestedUpdate invoke.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plugin-scoped version of a AddonLifecycle service.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.ScopedService]
|
||||
#pragma warning disable SA1015
|
||||
[ResolveVia<IAddonLifecycle>]
|
||||
#pragma warning restore SA1015
|
||||
internal class AddonLifecyclePluginScoped : IDisposable, IServiceType, IAddonLifecycle
|
||||
{
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly AddonLifecycle addonLifecycleService = Service<AddonLifecycle>.Get();
|
||||
|
||||
private readonly List<AddonLifecycleEventListener> eventListeners = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var listener in this.eventListeners)
|
||||
{
|
||||
this.addonLifecycleService.UnregisterListener(listener);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void RegisterListener(AddonEvent eventType, IEnumerable<string> addonNames, IAddonLifecycle.AddonEventDelegate handler)
|
||||
{
|
||||
foreach (var addonName in addonNames)
|
||||
{
|
||||
this.RegisterListener(eventType, addonName, handler);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void RegisterListener(AddonEvent eventType, string addonName, IAddonLifecycle.AddonEventDelegate handler)
|
||||
{
|
||||
var listener = new AddonLifecycleEventListener(eventType, addonName, handler);
|
||||
this.eventListeners.Add(listener);
|
||||
this.addonLifecycleService.RegisterListener(listener);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void RegisterListener(AddonEvent eventType, IAddonLifecycle.AddonEventDelegate handler)
|
||||
{
|
||||
this.RegisterListener(eventType, string.Empty, handler);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void UnregisterListener(AddonEvent eventType, IEnumerable<string> addonNames, IAddonLifecycle.AddonEventDelegate? handler = null)
|
||||
{
|
||||
foreach (var addonName in addonNames)
|
||||
{
|
||||
this.UnregisterListener(eventType, addonName, handler);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void UnregisterListener(AddonEvent eventType, string addonName, IAddonLifecycle.AddonEventDelegate? handler = null)
|
||||
{
|
||||
this.eventListeners.RemoveAll(entry =>
|
||||
{
|
||||
if (entry.EventType != eventType) return false;
|
||||
if (entry.AddonName != addonName) return false;
|
||||
if (handler is not null && entry.FunctionDelegate != handler) return false;
|
||||
|
||||
this.addonLifecycleService.UnregisterListener(entry);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void UnregisterListener(AddonEvent eventType, IAddonLifecycle.AddonEventDelegate? handler = null)
|
||||
{
|
||||
this.UnregisterListener(eventType, string.Empty, handler);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void UnregisterListener(params IAddonLifecycle.AddonEventDelegate[] handlers)
|
||||
{
|
||||
foreach (var handler in handlers)
|
||||
{
|
||||
this.eventListeners.RemoveAll(entry =>
|
||||
{
|
||||
if (entry.FunctionDelegate != handler) return false;
|
||||
|
||||
this.addonLifecycleService.UnregisterListener(entry);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
51
Dalamud/Game/AddonLifecycle/AddonLifecycleAddressResolver.cs
Normal file
51
Dalamud/Game/AddonLifecycle/AddonLifecycleAddressResolver.cs
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
namespace Dalamud.Game.AddonLifecycle;
|
||||
|
||||
/// <summary>
|
||||
/// AddonLifecycleService memory address resolver.
|
||||
/// </summary>
|
||||
internal class AddonLifecycleAddressResolver : BaseAddressResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the address of the addon setup hook invoked by the AtkUnitManager.
|
||||
/// </summary>
|
||||
public nint AddonSetup { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the addon finalize hook invoked by the AtkUnitManager.
|
||||
/// </summary>
|
||||
public nint AddonFinalize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the addon draw hook invoked by virtual function call.
|
||||
/// </summary>
|
||||
public nint AddonDraw { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the addon update hook invoked by virtual function call.
|
||||
/// </summary>
|
||||
public nint AddonUpdate { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the addon onRequestedUpdate hook invoked by virtual function call.
|
||||
/// </summary>
|
||||
public nint AddonOnRequestedUpdate { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of AtkUnitManager_vf10 which triggers addon onRefresh.
|
||||
/// </summary>
|
||||
public nint AddonOnRefresh { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Scan for and setup any configured address pointers.
|
||||
/// </summary>
|
||||
/// <param name="sig">The signature scanner to facilitate setup.</param>
|
||||
protected override void Setup64Bit(SigScanner sig)
|
||||
{
|
||||
this.AddonSetup = sig.ScanText("E8 ?? ?? ?? ?? 8B 83 ?? ?? ?? ?? C1 E8 14");
|
||||
this.AddonFinalize = sig.ScanText("E8 ?? ?? ?? ?? 48 8B 7C 24 ?? 41 8B C6");
|
||||
this.AddonDraw = sig.ScanText("FF 90 ?? ?? ?? ?? 83 EB 01 79 C1");
|
||||
this.AddonUpdate = sig.ScanText("FF 90 ?? ?? ?? ?? 40 88 AF");
|
||||
this.AddonOnRequestedUpdate = sig.ScanText("FF 90 90 01 00 00 48 8B 5C 24 30 48 83 C4 20");
|
||||
this.AddonOnRefresh = sig.ScanText("48 89 5C 24 08 57 48 83 EC 20 41 8B F8 48 8B DA");
|
||||
}
|
||||
}
|
||||
38
Dalamud/Game/AddonLifecycle/AddonLifecycleEventListener.cs
Normal file
38
Dalamud/Game/AddonLifecycle/AddonLifecycleEventListener.cs
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
using Dalamud.Plugin.Services;
|
||||
|
||||
namespace Dalamud.Game.AddonLifecycle;
|
||||
|
||||
/// <summary>
|
||||
/// This class is a helper for tracking and invoking listener delegates.
|
||||
/// </summary>
|
||||
internal class AddonLifecycleEventListener
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AddonLifecycleEventListener"/> class.
|
||||
/// </summary>
|
||||
/// <param name="eventType">Event type to listen for.</param>
|
||||
/// <param name="addonName">Addon name to listen for.</param>
|
||||
/// <param name="functionDelegate">Delegate to invoke.</param>
|
||||
internal AddonLifecycleEventListener(AddonEvent eventType, string addonName, IAddonLifecycle.AddonEventDelegate functionDelegate)
|
||||
{
|
||||
this.EventType = eventType;
|
||||
this.AddonName = addonName;
|
||||
this.FunctionDelegate = functionDelegate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the addon this listener is looking for.
|
||||
/// string.Empty if it wants to be called for any addon.
|
||||
/// </summary>
|
||||
public string AddonName { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the event type this listener is looking for.
|
||||
/// </summary>
|
||||
public AddonEvent EventType { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the delegate this listener invokes.
|
||||
/// </summary>
|
||||
public IAddonLifecycle.AddonEventDelegate FunctionDelegate { get; init; }
|
||||
}
|
||||
|
|
@ -10,7 +10,7 @@ namespace Dalamud.Game;
|
|||
/// <summary>
|
||||
/// Base memory address resolver.
|
||||
/// </summary>
|
||||
public abstract class BaseAddressResolver
|
||||
internal abstract class BaseAddressResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a list of memory addresses that were found, to list in /xldata.
|
||||
|
|
|
|||
|
|
@ -25,10 +25,8 @@ namespace Dalamud.Game;
|
|||
/// <summary>
|
||||
/// Chat events and public helper functions.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
public class ChatHandlers : IServiceType
|
||||
internal class ChatHandlers : IServiceType
|
||||
{
|
||||
// private static readonly Dictionary<string, string> UnicodeToDiscordEmojiDict = new()
|
||||
// {
|
||||
|
|
@ -134,22 +132,6 @@ public class ChatHandlers : IServiceType
|
|||
/// </summary>
|
||||
public bool IsAutoUpdateComplete { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Convert a TextPayload to SeString and wrap in italics payloads.
|
||||
/// </summary>
|
||||
/// <param name="text">Text to convert.</param>
|
||||
/// <returns>SeString payload of italicized text.</returns>
|
||||
public static SeString MakeItalics(string text)
|
||||
=> MakeItalics(new TextPayload(text));
|
||||
|
||||
/// <summary>
|
||||
/// Convert a TextPayload to SeString and wrap in italics payloads.
|
||||
/// </summary>
|
||||
/// <param name="text">Text to convert.</param>
|
||||
/// <returns>SeString payload of italicized text.</returns>
|
||||
public static SeString MakeItalics(TextPayload text)
|
||||
=> new(EmphasisItalicPayload.ItalicsOn, text, EmphasisItalicPayload.ItalicsOff);
|
||||
|
||||
private void OnCheckMessageHandled(XivChatType type, uint senderid, ref SeString sender, ref SeString message, ref bool isHandled)
|
||||
{
|
||||
var textVal = message.TextValue;
|
||||
|
|
@ -264,7 +246,7 @@ public class ChatHandlers : IServiceType
|
|||
|
||||
if (string.IsNullOrEmpty(this.configuration.LastVersion) || !assemblyVersion.StartsWith(this.configuration.LastVersion))
|
||||
{
|
||||
chatGui.PrintChat(new XivChatEntry
|
||||
chatGui.Print(new XivChatEntry
|
||||
{
|
||||
Message = Loc.Localize("DalamudUpdated", "Dalamud has been updated successfully! Please check the discord for a full changelog."),
|
||||
Type = XivChatType.Notice,
|
||||
|
|
@ -321,7 +303,7 @@ public class ChatHandlers : IServiceType
|
|||
}
|
||||
else
|
||||
{
|
||||
chatGui.PrintChat(new XivChatEntry
|
||||
chatGui.Print(new XivChatEntry
|
||||
{
|
||||
Message = new SeString(new List<Payload>()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ namespace Dalamud.Game.ClientState.Aetherytes;
|
|||
#pragma warning disable SA1015
|
||||
[ResolveVia<IAetheryteList>]
|
||||
#pragma warning restore SA1015
|
||||
public sealed unsafe partial class AetheryteList : IServiceType, IAetheryteList
|
||||
internal sealed unsafe partial class AetheryteList : IServiceType, IAetheryteList
|
||||
{
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly ClientState clientState = Service<ClientState>.Get();
|
||||
|
|
@ -78,7 +78,7 @@ public sealed unsafe partial class AetheryteList : IServiceType, IAetheryteList
|
|||
/// <summary>
|
||||
/// This collection represents the list of available Aetherytes in the Teleport window.
|
||||
/// </summary>
|
||||
public sealed partial class AetheryteList
|
||||
internal sealed partial class AetheryteList
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public int Count => this.Length;
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ namespace Dalamud.Game.ClientState.Buddy;
|
|||
#pragma warning disable SA1015
|
||||
[ResolveVia<IBuddyList>]
|
||||
#pragma warning restore SA1015
|
||||
public sealed partial class BuddyList : IServiceType, IBuddyList
|
||||
internal sealed partial class BuddyList : IServiceType, IBuddyList
|
||||
{
|
||||
private const uint InvalidObjectID = 0xE0000000;
|
||||
|
||||
|
|
@ -147,7 +147,7 @@ public sealed partial class BuddyList : IServiceType, IBuddyList
|
|||
/// <summary>
|
||||
/// This collection represents the buddies present in your squadron or trust party.
|
||||
/// </summary>
|
||||
public sealed partial class BuddyList
|
||||
internal sealed partial class BuddyList
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
int IReadOnlyCollection<BuddyMember>.Count => this.Length;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using Dalamud.Data;
|
||||
|
|
@ -25,7 +24,7 @@ namespace Dalamud.Game.ClientState;
|
|||
#pragma warning disable SA1015
|
||||
[ResolveVia<IClientState>]
|
||||
#pragma warning restore SA1015
|
||||
public sealed class ClientState : IDisposable, IServiceType, IClientState
|
||||
internal sealed class ClientState : IDisposable, IServiceType, IClientState
|
||||
{
|
||||
private readonly GameLifecycle lifecycle;
|
||||
private readonly ClientStateAddressResolver address;
|
||||
|
|
@ -102,6 +101,9 @@ public sealed class ClientState : IDisposable, IServiceType, IClientState
|
|||
/// <inheritdoc/>
|
||||
public bool IsPvPExcludingDen { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsGPosing => GameMain.IsInGPose();
|
||||
|
||||
/// <summary>
|
||||
/// Gets client state address resolver.
|
||||
/// </summary>
|
||||
|
|
@ -138,7 +140,7 @@ public sealed class ClientState : IDisposable, IServiceType, IClientState
|
|||
this.CfPop?.InvokeSafely(this, e);
|
||||
}
|
||||
|
||||
private void FrameworkOnOnUpdateEvent(Framework framework1)
|
||||
private void FrameworkOnOnUpdateEvent(IFramework framework1)
|
||||
{
|
||||
var condition = Service<Conditions.Condition>.GetNullable();
|
||||
var gameGui = Service<GameGui>.GetNullable();
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ namespace Dalamud.Game.ClientState;
|
|||
/// <summary>
|
||||
/// Client state memory address resolver.
|
||||
/// </summary>
|
||||
public sealed class ClientStateAddressResolver : BaseAddressResolver
|
||||
internal sealed class ClientStateAddressResolver : BaseAddressResolver
|
||||
{
|
||||
// Static offsets
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
using System;
|
||||
|
||||
using Dalamud.IoC;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Plugin.Services;
|
||||
|
|
@ -10,13 +8,9 @@ namespace Dalamud.Game.ClientState.Conditions;
|
|||
/// <summary>
|
||||
/// Provides access to conditions (generally player state). You can check whether a player is in combat, mounted, etc.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
#pragma warning disable SA1015
|
||||
[ResolveVia<ICondition>]
|
||||
#pragma warning restore SA1015
|
||||
public sealed partial class Condition : IServiceType, ICondition
|
||||
internal sealed partial class Condition : IServiceType, ICondition
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the current max number of conditions. You can get this just by looking at the condition sheet and how many rows it has.
|
||||
|
|
@ -96,7 +90,7 @@ public sealed partial class Condition : IServiceType, ICondition
|
|||
framework.Update += this.FrameworkUpdate;
|
||||
}
|
||||
|
||||
private void FrameworkUpdate(Framework framework)
|
||||
private void FrameworkUpdate(IFramework framework)
|
||||
{
|
||||
for (var i = 0; i < MaxConditionEntries; i++)
|
||||
{
|
||||
|
|
@ -122,7 +116,7 @@ public sealed partial class Condition : IServiceType, ICondition
|
|||
/// <summary>
|
||||
/// Provides access to conditions (generally player state). You can check whether a player is in combat, mounted, etc.
|
||||
/// </summary>
|
||||
public sealed partial class Condition : IDisposable
|
||||
internal sealed partial class Condition : IDisposable
|
||||
{
|
||||
private bool isDisposed;
|
||||
|
||||
|
|
@ -156,3 +150,54 @@ public sealed partial class Condition : IDisposable
|
|||
this.isDisposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plugin-scoped version of a Condition service.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.ScopedService]
|
||||
#pragma warning disable SA1015
|
||||
[ResolveVia<ICondition>]
|
||||
#pragma warning restore SA1015
|
||||
internal class ConditionPluginScoped : IDisposable, IServiceType, ICondition
|
||||
{
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly Condition conditionService = Service<Condition>.Get();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConditionPluginScoped"/> class.
|
||||
/// </summary>
|
||||
internal ConditionPluginScoped()
|
||||
{
|
||||
this.conditionService.ConditionChange += this.ConditionChangedForward;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event ICondition.ConditionChangeDelegate? ConditionChange;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int MaxEntries => this.conditionService.MaxEntries;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IntPtr Address => this.conditionService.Address;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool this[int flag] => this.conditionService[flag];
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
this.conditionService.ConditionChange -= this.ConditionChangedForward;
|
||||
|
||||
this.ConditionChange = null;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Any() => this.conditionService.Any();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Any(params ConditionFlag[] flags) => this.conditionService.Any(flags);
|
||||
|
||||
private void ConditionChangedForward(ConditionFlag flag, bool value) => this.ConditionChange?.Invoke(flag, value);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ namespace Dalamud.Game.ClientState.Fates;
|
|||
#pragma warning disable SA1015
|
||||
[ResolveVia<IFateTable>]
|
||||
#pragma warning restore SA1015
|
||||
public sealed partial class FateTable : IServiceType, IFateTable
|
||||
internal sealed partial class FateTable : IServiceType, IFateTable
|
||||
{
|
||||
private readonly ClientStateAddressResolver address;
|
||||
|
||||
|
|
@ -110,7 +110,7 @@ public sealed partial class FateTable : IServiceType, IFateTable
|
|||
/// <summary>
|
||||
/// This collection represents the currently available Fate events.
|
||||
/// </summary>
|
||||
public sealed partial class FateTable
|
||||
internal sealed partial class FateTable
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
int IReadOnlyCollection<Fate>.Count => this.Length;
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ namespace Dalamud.Game.ClientState.GamePad;
|
|||
#pragma warning disable SA1015
|
||||
[ResolveVia<IGamepadState>]
|
||||
#pragma warning restore SA1015
|
||||
public unsafe class GamepadState : IDisposable, IServiceType, IGamepadState
|
||||
internal unsafe class GamepadState : IDisposable, IServiceType, IGamepadState
|
||||
{
|
||||
private readonly Hook<ControllerPoll>? gamepadPoll;
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ namespace Dalamud.Game.ClientState.JobGauge;
|
|||
#pragma warning disable SA1015
|
||||
[ResolveVia<IJobGauges>]
|
||||
#pragma warning restore SA1015
|
||||
public class JobGauges : IServiceType, IJobGauges
|
||||
internal class JobGauges : IServiceType, IJobGauges
|
||||
{
|
||||
private Dictionary<Type, JobGaugeBase> cache = new();
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ namespace Dalamud.Game.ClientState.Keys;
|
|||
#pragma warning disable SA1015
|
||||
[ResolveVia<IKeyState>]
|
||||
#pragma warning restore SA1015
|
||||
public class KeyState : IServiceType, IKeyState
|
||||
internal class KeyState : IServiceType, IKeyState
|
||||
{
|
||||
// The array is accessed in a way that this limit doesn't appear to exist
|
||||
// but there is other state data past this point, and keys beyond here aren't
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ namespace Dalamud.Game.ClientState.Objects;
|
|||
#pragma warning disable SA1015
|
||||
[ResolveVia<IObjectTable>]
|
||||
#pragma warning restore SA1015
|
||||
public sealed partial class ObjectTable : IServiceType, IObjectTable
|
||||
internal sealed partial class ObjectTable : IServiceType, IObjectTable
|
||||
{
|
||||
private const int ObjectTableLength = 596;
|
||||
|
||||
|
|
@ -109,7 +109,7 @@ public sealed partial class ObjectTable : IServiceType, IObjectTable
|
|||
/// <summary>
|
||||
/// This collection represents the currently spawned FFXIV game objects.
|
||||
/// </summary>
|
||||
public sealed partial class ObjectTable
|
||||
internal sealed partial class ObjectTable
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
int IReadOnlyCollection<GameObject>.Count => this.Length;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
using System;
|
||||
|
||||
using Dalamud.Game.ClientState.Objects.Enums;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Objects.Types;
|
||||
|
|
@ -25,5 +23,5 @@ public unsafe class BattleNpc : BattleChara
|
|||
public BattleNpcSubKind BattleNpcKind => (BattleNpcSubKind)this.Struct->Character.GameObject.SubKind;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override ulong TargetObjectId => this.Struct->Character.TargetObjectID;
|
||||
public override ulong TargetObjectId => this.Struct->Character.TargetId;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
using System;
|
||||
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Game.ClientState.Resolvers;
|
||||
|
||||
|
|
@ -33,5 +31,5 @@ public unsafe class PlayerCharacter : BattleChara
|
|||
/// <summary>
|
||||
/// Gets the target actor ID of the PlayerCharacter.
|
||||
/// </summary>
|
||||
public override ulong TargetObjectId => this.Struct->Character.PlayerTargetObjectID;
|
||||
public override ulong TargetObjectId => this.Struct->Character.LookTargetId;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ namespace Dalamud.Game.ClientState.Objects;
|
|||
#pragma warning disable SA1015
|
||||
[ResolveVia<ITargetManager>]
|
||||
#pragma warning restore SA1015
|
||||
public sealed unsafe class TargetManager : IServiceType, ITargetManager
|
||||
internal sealed unsafe class TargetManager : IServiceType, ITargetManager
|
||||
{
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly ClientState clientState = Service<ClientState>.Get();
|
||||
|
|
@ -70,6 +70,20 @@ public sealed unsafe class TargetManager : IServiceType, ITargetManager
|
|||
set => this.SetSoftTarget(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public GameObject? GPoseTarget
|
||||
{
|
||||
get => this.objectTable.CreateObjectReference((IntPtr)Struct->GPoseTarget);
|
||||
set => Struct->GPoseTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)value?.Address;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public GameObject? MouseOverNameplateTarget
|
||||
{
|
||||
get => this.objectTable.CreateObjectReference((IntPtr)Struct->MouseOverNameplateTarget);
|
||||
set => Struct->MouseOverNameplateTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)value?.Address;
|
||||
}
|
||||
|
||||
private FFXIVClientStructs.FFXIV.Client.Game.Control.TargetSystem* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Control.TargetSystem*)this.Address;
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
using System;
|
||||
|
||||
using Dalamud.Game.ClientState.Objects.Enums;
|
||||
using Dalamud.Game.ClientState.Resolvers;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
|
|
@ -87,7 +85,7 @@ public unsafe class Character : GameObject
|
|||
/// <summary>
|
||||
/// Gets the target object ID of the character.
|
||||
/// </summary>
|
||||
public override ulong TargetObjectId => this.Struct->TargetObjectID;
|
||||
public override ulong TargetObjectId => this.Struct->TargetId;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name ID of the character.
|
||||
|
|
@ -115,5 +113,6 @@ public unsafe class Character : GameObject
|
|||
/// <summary>
|
||||
/// Gets the underlying structure.
|
||||
/// </summary>
|
||||
protected internal new FFXIVClientStructs.FFXIV.Client.Game.Character.Character* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)this.Address;
|
||||
protected internal new FFXIVClientStructs.FFXIV.Client.Game.Character.Character* Struct =>
|
||||
(FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)this.Address;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ namespace Dalamud.Game.ClientState.Party;
|
|||
#pragma warning disable SA1015
|
||||
[ResolveVia<IPartyList>]
|
||||
#pragma warning restore SA1015
|
||||
public sealed unsafe partial class PartyList : IServiceType, IPartyList
|
||||
internal sealed unsafe partial class PartyList : IServiceType, IPartyList
|
||||
{
|
||||
private const int GroupLength = 8;
|
||||
private const int AllianceLength = 20;
|
||||
|
|
@ -130,7 +130,7 @@ public sealed unsafe partial class PartyList : IServiceType, IPartyList
|
|||
/// <summary>
|
||||
/// This collection represents the party members present in your party or alliance.
|
||||
/// </summary>
|
||||
public sealed partial class PartyList
|
||||
internal sealed partial class PartyList
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
int IReadOnlyCollection<PartyMember>.Count => this.Length;
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ public sealed class CommandInfo
|
|||
public CommandInfo(HandlerDelegate handler)
|
||||
{
|
||||
this.Handler = handler;
|
||||
this.LoaderAssemblyName = Assembly.GetCallingAssembly()?.GetName()?.Name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
|
|
@ -9,22 +8,21 @@ using Dalamud.Game.Text;
|
|||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.IoC;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Logging.Internal;
|
||||
using Dalamud.Plugin.Internal.Types;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game.Command;
|
||||
|
||||
/// <summary>
|
||||
/// This class manages registered in-game slash commands.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
#pragma warning disable SA1015
|
||||
[ResolveVia<ICommandManager>]
|
||||
#pragma warning restore SA1015
|
||||
public sealed class CommandManager : IServiceType, IDisposable, ICommandManager
|
||||
internal sealed class CommandManager : IServiceType, IDisposable, ICommandManager
|
||||
{
|
||||
private static readonly ModuleLog Log = new("Command");
|
||||
|
||||
private readonly ConcurrentDictionary<string, CommandInfo> commandMap = new();
|
||||
private readonly Regex commandRegexEn = new(@"^The command (?<command>.+) does not exist\.$", RegexOptions.Compiled);
|
||||
private readonly Regex commandRegexJp = new(@"^そのコマンドはありません。: (?<command>.+)$", RegexOptions.Compiled);
|
||||
|
|
@ -84,7 +82,7 @@ public sealed class CommandManager : IServiceType, IDisposable, ICommandManager
|
|||
// => command: 0-12 (12 chars)
|
||||
// => argument: 13-17 (4 chars)
|
||||
// => content.IndexOf(' ') == 12
|
||||
command = content.Substring(0, separatorPosition);
|
||||
command = content[..separatorPosition];
|
||||
|
||||
var argStart = separatorPosition + 1;
|
||||
argument = content[argStart..];
|
||||
|
|
@ -162,3 +160,93 @@ public sealed class CommandManager : IServiceType, IDisposable, ICommandManager
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plugin-scoped version of a AddonLifecycle service.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.ScopedService]
|
||||
#pragma warning disable SA1015
|
||||
[ResolveVia<ICommandManager>]
|
||||
#pragma warning restore SA1015
|
||||
internal class CommandManagerPluginScoped : IDisposable, IServiceType, ICommandManager
|
||||
{
|
||||
private static readonly ModuleLog Log = new("Command");
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly CommandManager commandManagerService = Service<CommandManager>.Get();
|
||||
|
||||
private readonly List<string> pluginRegisteredCommands = new();
|
||||
private readonly LocalPlugin pluginInfo;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CommandManagerPluginScoped"/> class.
|
||||
/// </summary>
|
||||
/// <param name="localPlugin">Info for the plugin that requests this service.</param>
|
||||
public CommandManagerPluginScoped(LocalPlugin localPlugin)
|
||||
{
|
||||
this.pluginInfo = localPlugin;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ReadOnlyDictionary<string, CommandInfo> Commands => this.commandManagerService.Commands;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var command in this.pluginRegisteredCommands)
|
||||
{
|
||||
this.commandManagerService.RemoveHandler(command);
|
||||
}
|
||||
|
||||
this.pluginRegisteredCommands.Clear();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool ProcessCommand(string content)
|
||||
=> this.commandManagerService.ProcessCommand(content);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void DispatchCommand(string command, string argument, CommandInfo info)
|
||||
=> this.commandManagerService.DispatchCommand(command, argument, info);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool AddHandler(string command, CommandInfo info)
|
||||
{
|
||||
if (!this.pluginRegisteredCommands.Contains(command))
|
||||
{
|
||||
info.LoaderAssemblyName = this.pluginInfo.InternalName;
|
||||
if (this.commandManagerService.AddHandler(command, info))
|
||||
{
|
||||
this.pluginRegisteredCommands.Add(command);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error($"Command {command} is already registered.");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool RemoveHandler(string command)
|
||||
{
|
||||
if (this.pluginRegisteredCommands.Contains(command))
|
||||
{
|
||||
if (this.commandManagerService.RemoveHandler(command))
|
||||
{
|
||||
this.pluginRegisteredCommands.Remove(command);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error($"Command {command} not found.");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ namespace Dalamud.Game.Config;
|
|||
#pragma warning disable SA1015
|
||||
[ResolveVia<IGameConfig>]
|
||||
#pragma warning restore SA1015
|
||||
public sealed class GameConfig : IServiceType, IGameConfig, IDisposable
|
||||
internal sealed class GameConfig : IServiceType, IGameConfig, IDisposable
|
||||
{
|
||||
private readonly GameConfigAddressResolver address = new();
|
||||
private Hook<ConfigChangeDelegate>? configChangeHook;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
/// <summary>
|
||||
/// Game config system address resolver.
|
||||
/// </summary>
|
||||
public sealed class GameConfigAddressResolver : BaseAddressResolver
|
||||
internal sealed class GameConfigAddressResolver : BaseAddressResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the address of the method called when any config option is changed.
|
||||
|
|
|
|||
|
|
@ -1,25 +1,19 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using Dalamud.Game.ClientState.Conditions;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.IoC;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Dalamud.Utility;
|
||||
|
||||
namespace Dalamud.Game.DutyState;
|
||||
|
||||
/// <summary>
|
||||
/// This class represents the state of the currently occupied duty.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
#pragma warning disable SA1015
|
||||
[ResolveVia<IDutyState>]
|
||||
#pragma warning restore SA1015
|
||||
public unsafe class DutyState : IDisposable, IServiceType, IDutyState
|
||||
internal unsafe class DutyState : IDisposable, IServiceType, IDutyState
|
||||
{
|
||||
private readonly DutyStateAddressResolver address;
|
||||
private readonly Hook<SetupContentDirectNetworkMessageDelegate> contentDirectorNetworkMessageHook;
|
||||
|
|
@ -49,16 +43,16 @@ public unsafe class DutyState : IDisposable, IServiceType, IDutyState
|
|||
private delegate byte SetupContentDirectNetworkMessageDelegate(IntPtr a1, IntPtr a2, ushort* a3);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event EventHandler<ushort> DutyStarted;
|
||||
public event EventHandler<ushort>? DutyStarted;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event EventHandler<ushort> DutyWiped;
|
||||
public event EventHandler<ushort>? DutyWiped;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event EventHandler<ushort> DutyRecommenced;
|
||||
public event EventHandler<ushort>? DutyRecommenced;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event EventHandler<ushort> DutyCompleted;
|
||||
public event EventHandler<ushort>? DutyCompleted;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsDutyStarted { get; private set; }
|
||||
|
|
@ -66,7 +60,7 @@ public unsafe class DutyState : IDisposable, IServiceType, IDutyState
|
|||
private bool CompletedThisTerritory { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
void IDisposable.Dispose()
|
||||
public void Dispose()
|
||||
{
|
||||
this.contentDirectorNetworkMessageHook.Dispose();
|
||||
this.framework.Update -= this.FrameworkOnUpdateEvent;
|
||||
|
|
@ -92,33 +86,33 @@ public unsafe class DutyState : IDisposable, IServiceType, IDutyState
|
|||
// Duty Commenced
|
||||
case 0x4000_0001:
|
||||
this.IsDutyStarted = true;
|
||||
this.DutyStarted.InvokeSafely(this, this.clientState.TerritoryType);
|
||||
this.DutyStarted?.Invoke(this, this.clientState.TerritoryType);
|
||||
break;
|
||||
|
||||
// Party Wipe
|
||||
case 0x4000_0005:
|
||||
this.IsDutyStarted = false;
|
||||
this.DutyWiped.InvokeSafely(this, this.clientState.TerritoryType);
|
||||
this.DutyWiped?.Invoke(this, this.clientState.TerritoryType);
|
||||
break;
|
||||
|
||||
// Duty Recommence
|
||||
case 0x4000_0006:
|
||||
this.IsDutyStarted = true;
|
||||
this.DutyRecommenced.InvokeSafely(this, this.clientState.TerritoryType);
|
||||
this.DutyRecommenced?.Invoke(this, this.clientState.TerritoryType);
|
||||
break;
|
||||
|
||||
// Duty Completed Flytext Shown
|
||||
case 0x4000_0002 when !this.CompletedThisTerritory:
|
||||
this.IsDutyStarted = false;
|
||||
this.CompletedThisTerritory = true;
|
||||
this.DutyCompleted.InvokeSafely(this, this.clientState.TerritoryType);
|
||||
this.DutyCompleted?.Invoke(this, this.clientState.TerritoryType);
|
||||
break;
|
||||
|
||||
// Duty Completed
|
||||
case 0x4000_0003 when !this.CompletedThisTerritory:
|
||||
this.IsDutyStarted = false;
|
||||
this.CompletedThisTerritory = true;
|
||||
this.DutyCompleted.InvokeSafely(this, this.clientState.TerritoryType);
|
||||
this.DutyCompleted?.Invoke(this, this.clientState.TerritoryType);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -141,7 +135,7 @@ public unsafe class DutyState : IDisposable, IServiceType, IDutyState
|
|||
/// Joining a duty in progress, or disconnecting and reconnecting will cause the player to miss the event.
|
||||
/// </summary>
|
||||
/// <param name="framework1">Framework reference.</param>
|
||||
private void FrameworkOnUpdateEvent(Framework framework1)
|
||||
private void FrameworkOnUpdateEvent(IFramework framework1)
|
||||
{
|
||||
// If the duty hasn't been started, and has not been completed yet this territory
|
||||
if (!this.IsDutyStarted && !this.CompletedThisTerritory)
|
||||
|
|
@ -161,11 +155,73 @@ public unsafe class DutyState : IDisposable, IServiceType, IDutyState
|
|||
}
|
||||
|
||||
private bool IsBoundByDuty()
|
||||
=> this.condition.Any(ConditionFlag.BoundByDuty,
|
||||
ConditionFlag.BoundByDuty56,
|
||||
ConditionFlag.BoundByDuty95);
|
||||
|
||||
private bool IsInCombat()
|
||||
=> this.condition.Any(ConditionFlag.InCombat);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plugin scoped version of DutyState.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.ScopedService]
|
||||
#pragma warning disable SA1015
|
||||
[ResolveVia<IDutyState>]
|
||||
#pragma warning restore SA1015
|
||||
internal class DutyStatePluginScoped : IDisposable, IServiceType, IDutyState
|
||||
{
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly DutyState dutyStateService = Service<DutyState>.Get();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DutyStatePluginScoped"/> class.
|
||||
/// </summary>
|
||||
internal DutyStatePluginScoped()
|
||||
{
|
||||
return this.condition[ConditionFlag.BoundByDuty] ||
|
||||
this.condition[ConditionFlag.BoundByDuty56] ||
|
||||
this.condition[ConditionFlag.BoundByDuty95];
|
||||
this.dutyStateService.DutyStarted += this.DutyStartedForward;
|
||||
this.dutyStateService.DutyWiped += this.DutyWipedForward;
|
||||
this.dutyStateService.DutyRecommenced += this.DutyRecommencedForward;
|
||||
this.dutyStateService.DutyCompleted += this.DutyCompletedForward;
|
||||
}
|
||||
|
||||
private bool IsInCombat() => this.condition[ConditionFlag.InCombat];
|
||||
/// <inheritdoc/>
|
||||
public event EventHandler<ushort>? DutyStarted;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event EventHandler<ushort>? DutyWiped;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event EventHandler<ushort>? DutyRecommenced;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event EventHandler<ushort>? DutyCompleted;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsDutyStarted => this.dutyStateService.IsDutyStarted;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
this.dutyStateService.DutyStarted -= this.DutyStartedForward;
|
||||
this.dutyStateService.DutyWiped -= this.DutyWipedForward;
|
||||
this.dutyStateService.DutyRecommenced -= this.DutyRecommencedForward;
|
||||
this.dutyStateService.DutyCompleted -= this.DutyCompletedForward;
|
||||
|
||||
this.DutyStarted = null;
|
||||
this.DutyWiped = null;
|
||||
this.DutyRecommenced = null;
|
||||
this.DutyCompleted = null;
|
||||
}
|
||||
|
||||
private void DutyStartedForward(object sender, ushort territoryId) => this.DutyStarted?.Invoke(sender, territoryId);
|
||||
|
||||
private void DutyWipedForward(object sender, ushort territoryId) => this.DutyWiped?.Invoke(sender, territoryId);
|
||||
|
||||
private void DutyRecommencedForward(object sender, ushort territoryId) => this.DutyRecommenced?.Invoke(sender, territoryId);
|
||||
|
||||
private void DutyCompletedForward(object sender, ushort territoryId) => this.DutyCompleted?.Invoke(sender, territoryId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
using System;
|
||||
|
||||
namespace Dalamud.Game.DutyState;
|
||||
|
||||
/// <summary>
|
||||
/// Duty state memory address resolver.
|
||||
/// </summary>
|
||||
public class DutyStateAddressResolver : BaseAddressResolver
|
||||
internal class DutyStateAddressResolver : BaseAddressResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the address of the method which is called when the client receives a content director update.
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ using Dalamud.Game.Gui.Toast;
|
|||
using Dalamud.Hooking;
|
||||
using Dalamud.IoC;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Dalamud.Utility;
|
||||
using Serilog;
|
||||
|
||||
|
|
@ -23,7 +24,10 @@ namespace Dalamud.Game;
|
|||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
public sealed class Framework : IDisposable, IServiceType
|
||||
#pragma warning disable SA1015
|
||||
[ResolveVia<IFramework>]
|
||||
#pragma warning restore SA1015
|
||||
internal sealed class Framework : IDisposable, IServiceType, IFramework
|
||||
{
|
||||
private static readonly Stopwatch StatsStopwatch = new();
|
||||
|
||||
|
|
@ -35,6 +39,8 @@ public sealed class Framework : IDisposable, IServiceType
|
|||
private readonly Hook<OnUpdateDetour> updateHook;
|
||||
private readonly Hook<OnRealDestroyDelegate> destroyHook;
|
||||
|
||||
private readonly FrameworkAddressResolver addressResolver;
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly DalamudConfiguration configuration = Service<DalamudConfiguration>.Get();
|
||||
|
||||
|
|
@ -50,19 +56,13 @@ public sealed class Framework : IDisposable, IServiceType
|
|||
this.lifecycle = lifecycle;
|
||||
this.hitchDetector = new HitchDetector("FrameworkUpdate", this.configuration.FrameworkUpdateHitch);
|
||||
|
||||
this.Address = new FrameworkAddressResolver();
|
||||
this.Address.Setup(sigScanner);
|
||||
this.addressResolver = new FrameworkAddressResolver();
|
||||
this.addressResolver.Setup(sigScanner);
|
||||
|
||||
this.updateHook = Hook<OnUpdateDetour>.FromAddress(this.Address.TickAddress, this.HandleFrameworkUpdate);
|
||||
this.destroyHook = Hook<OnRealDestroyDelegate>.FromAddress(this.Address.DestroyAddress, this.HandleFrameworkDestroy);
|
||||
this.updateHook = Hook<OnUpdateDetour>.FromAddress(this.addressResolver.TickAddress, this.HandleFrameworkUpdate);
|
||||
this.destroyHook = Hook<OnRealDestroyDelegate>.FromAddress(this.addressResolver.DestroyAddress, this.HandleFrameworkDestroy);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A delegate type used with the <see cref="Update"/> event.
|
||||
/// </summary>
|
||||
/// <param name="framework">The Framework instance.</param>
|
||||
public delegate void OnUpdateDelegate(Framework framework);
|
||||
|
||||
/// <summary>
|
||||
/// A delegate type used during the native Framework::destroy.
|
||||
/// </summary>
|
||||
|
|
@ -81,10 +81,8 @@ public sealed class Framework : IDisposable, IServiceType
|
|||
|
||||
private delegate IntPtr OnDestroyDetour(); // OnDestroyDelegate
|
||||
|
||||
/// <summary>
|
||||
/// Event that gets fired every time the game framework updates.
|
||||
/// </summary>
|
||||
public event OnUpdateDelegate Update;
|
||||
/// <inheritdoc/>
|
||||
public event IFramework.OnUpdateDelegate Update;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the collection of stats is enabled.
|
||||
|
|
@ -96,34 +94,19 @@ public sealed class Framework : IDisposable, IServiceType
|
|||
/// </summary>
|
||||
public static Dictionary<string, List<double>> StatsHistory { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a raw pointer to the instance of Client::Framework.
|
||||
/// </summary>
|
||||
public FrameworkAddressResolver Address { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the last time that the Framework Update event was triggered.
|
||||
/// </summary>
|
||||
/// <inheritdoc/>
|
||||
public DateTime LastUpdate { get; private set; } = DateTime.MinValue;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the last time in UTC that the Framework Update event was triggered.
|
||||
/// </summary>
|
||||
/// <inheritdoc/>
|
||||
public DateTime LastUpdateUTC { get; private set; } = DateTime.MinValue;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the delta between the last Framework Update and the currently executing one.
|
||||
/// </summary>
|
||||
/// <inheritdoc/>
|
||||
public TimeSpan UpdateDelta { get; private set; } = TimeSpan.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether currently executing code is running in the game's framework update thread.
|
||||
/// </summary>
|
||||
/// <inheritdoc/>
|
||||
public bool IsInFrameworkUpdateThread => Thread.CurrentThread == this.frameworkUpdateThread;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether game Framework is unloading.
|
||||
/// </summary>
|
||||
/// <inheritdoc/>
|
||||
public bool IsFrameworkUnloading { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -131,20 +114,11 @@ public sealed class Framework : IDisposable, IServiceType
|
|||
/// </summary>
|
||||
internal bool DispatchUpdateEvents { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Run given function right away if this function has been called from game's Framework.Update thread, or otherwise run on next Framework.Update call.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Return type.</typeparam>
|
||||
/// <param name="func">Function to call.</param>
|
||||
/// <returns>Task representing the pending or already completed function.</returns>
|
||||
/// <inheritdoc/>
|
||||
public Task<T> RunOnFrameworkThread<T>(Func<T> func) =>
|
||||
this.IsInFrameworkUpdateThread || this.IsFrameworkUnloading ? Task.FromResult(func()) : this.RunOnTick(func);
|
||||
|
||||
/// <summary>
|
||||
/// Run given function right away if this function has been called from game's Framework.Update thread, or otherwise run on next Framework.Update call.
|
||||
/// </summary>
|
||||
/// <param name="action">Function to call.</param>
|
||||
/// <returns>Task representing the pending or already completed function.</returns>
|
||||
/// <inheritdoc/>
|
||||
public Task RunOnFrameworkThread(Action action)
|
||||
{
|
||||
if (this.IsInFrameworkUpdateThread || this.IsFrameworkUnloading)
|
||||
|
|
@ -165,32 +139,15 @@ public sealed class Framework : IDisposable, IServiceType
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Run given function right away if this function has been called from game's Framework.Update thread, or otherwise run on next Framework.Update call.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Return type.</typeparam>
|
||||
/// <param name="func">Function to call.</param>
|
||||
/// <returns>Task representing the pending or already completed function.</returns>
|
||||
/// <inheritdoc/>
|
||||
public Task<T> RunOnFrameworkThread<T>(Func<Task<T>> func) =>
|
||||
this.IsInFrameworkUpdateThread || this.IsFrameworkUnloading ? func() : this.RunOnTick(func);
|
||||
|
||||
/// <summary>
|
||||
/// Run given function right away if this function has been called from game's Framework.Update thread, or otherwise run on next Framework.Update call.
|
||||
/// </summary>
|
||||
/// <param name="func">Function to call.</param>
|
||||
/// <returns>Task representing the pending or already completed function.</returns>
|
||||
/// <inheritdoc/>
|
||||
public Task RunOnFrameworkThread(Func<Task> func) =>
|
||||
this.IsInFrameworkUpdateThread || this.IsFrameworkUnloading ? func() : this.RunOnTick(func);
|
||||
|
||||
/// <summary>
|
||||
/// Run given function in upcoming Framework.Tick call.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Return type.</typeparam>
|
||||
/// <param name="func">Function to call.</param>
|
||||
/// <param name="delay">Wait for given timespan before calling this function.</param>
|
||||
/// <param name="delayTicks">Count given number of Framework.Tick calls before calling this function. This takes precedence over delay parameter.</param>
|
||||
/// <param name="cancellationToken">Cancellation token which will prevent the execution of this function if wait conditions are not met.</param>
|
||||
/// <returns>Task representing the pending function.</returns>
|
||||
/// <inheritdoc/>
|
||||
public Task<T> RunOnTick<T>(Func<T> func, TimeSpan delay = default, int delayTicks = default, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (this.IsFrameworkUnloading)
|
||||
|
|
@ -219,14 +176,7 @@ public sealed class Framework : IDisposable, IServiceType
|
|||
return tcs.Task;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Run given function in upcoming Framework.Tick call.
|
||||
/// </summary>
|
||||
/// <param name="action">Function to call.</param>
|
||||
/// <param name="delay">Wait for given timespan before calling this function.</param>
|
||||
/// <param name="delayTicks">Count given number of Framework.Tick calls before calling this function. This takes precedence over delay parameter.</param>
|
||||
/// <param name="cancellationToken">Cancellation token which will prevent the execution of this function if wait conditions are not met.</param>
|
||||
/// <returns>Task representing the pending function.</returns>
|
||||
/// <inheritdoc/>
|
||||
public Task RunOnTick(Action action, TimeSpan delay = default, int delayTicks = default, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (this.IsFrameworkUnloading)
|
||||
|
|
@ -255,15 +205,7 @@ public sealed class Framework : IDisposable, IServiceType
|
|||
return tcs.Task;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Run given function in upcoming Framework.Tick call.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Return type.</typeparam>
|
||||
/// <param name="func">Function to call.</param>
|
||||
/// <param name="delay">Wait for given timespan before calling this function.</param>
|
||||
/// <param name="delayTicks">Count given number of Framework.Tick calls before calling this function. This takes precedence over delay parameter.</param>
|
||||
/// <param name="cancellationToken">Cancellation token which will prevent the execution of this function if wait conditions are not met.</param>
|
||||
/// <returns>Task representing the pending function.</returns>
|
||||
/// <inheritdoc/>
|
||||
public Task<T> RunOnTick<T>(Func<Task<T>> func, TimeSpan delay = default, int delayTicks = default, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (this.IsFrameworkUnloading)
|
||||
|
|
@ -292,14 +234,7 @@ public sealed class Framework : IDisposable, IServiceType
|
|||
return tcs.Task.ContinueWith(x => x.Result, cancellationToken).Unwrap();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Run given function in upcoming Framework.Tick call.
|
||||
/// </summary>
|
||||
/// <param name="func">Function to call.</param>
|
||||
/// <param name="delay">Wait for given timespan before calling this function.</param>
|
||||
/// <param name="delayTicks">Count given number of Framework.Tick calls before calling this function. This takes precedence over delay parameter.</param>
|
||||
/// <param name="cancellationToken">Cancellation token which will prevent the execution of this function if wait conditions are not met.</param>
|
||||
/// <returns>Task representing the pending function.</returns>
|
||||
/// <inheritdoc/>
|
||||
public Task RunOnTick(Func<Task> func, TimeSpan delay = default, int delayTicks = default, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (this.IsFrameworkUnloading)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ namespace Dalamud.Game;
|
|||
/// <summary>
|
||||
/// The address resolver for the <see cref="Framework"/> class.
|
||||
/// </summary>
|
||||
public sealed class FrameworkAddressResolver : BaseAddressResolver
|
||||
internal sealed class FrameworkAddressResolver : BaseAddressResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the address for the function that is called once the Framework is destroyed.
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ namespace Dalamud.Game;
|
|||
#pragma warning disable SA1015
|
||||
[ResolveVia<IGameLifecycle>]
|
||||
#pragma warning restore SA1015
|
||||
public class GameLifecycle : IServiceType, IGameLifecycle
|
||||
internal class GameLifecycle : IServiceType, IGameLifecycle
|
||||
{
|
||||
private readonly CancellationTokenSource dalamudUnloadCts = new();
|
||||
private readonly CancellationTokenSource gameShutdownCts = new();
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ using Dalamud.Game.Text.SeStringHandling.Payloads;
|
|||
using Dalamud.Hooking;
|
||||
using Dalamud.IoC;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Dalamud.Utility;
|
||||
using Serilog;
|
||||
|
||||
|
|
@ -19,10 +20,9 @@ namespace Dalamud.Game.Gui;
|
|||
/// <summary>
|
||||
/// This class handles interacting with the native chat UI.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
public sealed class ChatGui : IDisposable, IServiceType
|
||||
internal sealed class ChatGui : IDisposable, IServiceType, IChatGui
|
||||
{
|
||||
private readonly ChatGuiAddressResolver address;
|
||||
|
||||
|
|
@ -51,45 +51,7 @@ public sealed class ChatGui : IDisposable, IServiceType
|
|||
this.populateItemLinkHook = Hook<PopulateItemLinkDelegate>.FromAddress(this.address.PopulateItemLinkObject, this.HandlePopulateItemLinkDetour);
|
||||
this.interactableLinkClickedHook = Hook<InteractableLinkClickedDelegate>.FromAddress(this.address.InteractableLinkClicked, this.InteractableLinkClickedDetour);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A delegate type used with the <see cref="ChatGui.ChatMessage"/> event.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of chat.</param>
|
||||
/// <param name="senderId">The sender ID.</param>
|
||||
/// <param name="sender">The sender name.</param>
|
||||
/// <param name="message">The message sent.</param>
|
||||
/// <param name="isHandled">A value indicating whether the message was handled or should be propagated.</param>
|
||||
public delegate void OnMessageDelegate(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled);
|
||||
|
||||
/// <summary>
|
||||
/// A delegate type used with the <see cref="ChatGui.CheckMessageHandled"/> event.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of chat.</param>
|
||||
/// <param name="senderId">The sender ID.</param>
|
||||
/// <param name="sender">The sender name.</param>
|
||||
/// <param name="message">The message sent.</param>
|
||||
/// <param name="isHandled">A value indicating whether the message was handled or should be propagated.</param>
|
||||
public delegate void OnCheckMessageHandledDelegate(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled);
|
||||
|
||||
/// <summary>
|
||||
/// A delegate type used with the <see cref="ChatGui.ChatMessageHandled"/> event.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of chat.</param>
|
||||
/// <param name="senderId">The sender ID.</param>
|
||||
/// <param name="sender">The sender name.</param>
|
||||
/// <param name="message">The message sent.</param>
|
||||
public delegate void OnMessageHandledDelegate(XivChatType type, uint senderId, SeString sender, SeString message);
|
||||
|
||||
/// <summary>
|
||||
/// A delegate type used with the <see cref="ChatGui.ChatMessageUnhandled"/> event.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of chat.</param>
|
||||
/// <param name="senderId">The sender ID.</param>
|
||||
/// <param name="sender">The sender name.</param>
|
||||
/// <param name="message">The message sent.</param>
|
||||
public delegate void OnMessageUnhandledDelegate(XivChatType type, uint senderId, SeString sender, SeString message);
|
||||
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate IntPtr PrintMessageDelegate(IntPtr manager, XivChatType chatType, IntPtr senderName, IntPtr message, uint senderId, IntPtr parameter);
|
||||
|
||||
|
|
@ -99,34 +61,22 @@ public sealed class ChatGui : IDisposable, IServiceType
|
|||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate void InteractableLinkClickedDelegate(IntPtr managerPtr, IntPtr messagePtr);
|
||||
|
||||
/// <summary>
|
||||
/// Event that will be fired when a chat message is sent to chat by the game.
|
||||
/// </summary>
|
||||
public event OnMessageDelegate ChatMessage;
|
||||
/// <inheritdoc/>
|
||||
public event IChatGui.OnMessageDelegate? ChatMessage;
|
||||
|
||||
/// <summary>
|
||||
/// Event that allows you to stop messages from appearing in chat by setting the isHandled parameter to true.
|
||||
/// </summary>
|
||||
public event OnCheckMessageHandledDelegate CheckMessageHandled;
|
||||
/// <inheritdoc/>
|
||||
public event IChatGui.OnCheckMessageHandledDelegate? CheckMessageHandled;
|
||||
|
||||
/// <summary>
|
||||
/// Event that will be fired when a chat message is handled by Dalamud or a Plugin.
|
||||
/// </summary>
|
||||
public event OnMessageHandledDelegate ChatMessageHandled;
|
||||
/// <inheritdoc/>
|
||||
public event IChatGui.OnMessageHandledDelegate? ChatMessageHandled;
|
||||
|
||||
/// <summary>
|
||||
/// Event that will be fired when a chat message is not handled by Dalamud or a Plugin.
|
||||
/// </summary>
|
||||
public event OnMessageUnhandledDelegate ChatMessageUnhandled;
|
||||
/// <inheritdoc/>
|
||||
public event IChatGui.OnMessageUnhandledDelegate? ChatMessageUnhandled;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ID of the last linked item.
|
||||
/// </summary>
|
||||
/// <inheritdoc/>
|
||||
public int LastLinkedItemId { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the flags of the last linked item.
|
||||
/// </summary>
|
||||
/// <inheritdoc/>
|
||||
public byte LastLinkedItemFlags { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -139,76 +89,36 @@ public sealed class ChatGui : IDisposable, IServiceType
|
|||
this.interactableLinkClickedHook.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queue a chat message. While method is named as PrintChat, it only add a entry to the queue,
|
||||
/// later to be processed when UpdateQueue() is called.
|
||||
/// </summary>
|
||||
/// <param name="chat">A message to send.</param>
|
||||
public void PrintChat(XivChatEntry chat)
|
||||
/// <inheritdoc/>
|
||||
public void Print(XivChatEntry chat)
|
||||
{
|
||||
this.chatQueue.Enqueue(chat);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queue a chat message. While method is named as PrintChat (it calls it internally), it only add a entry to the queue,
|
||||
/// later to be processed when UpdateQueue() is called.
|
||||
/// </summary>
|
||||
/// <param name="message">A message to send.</param>
|
||||
public void Print(string message)
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Print(string message, string? messageTag = null, ushort? tagColor = null)
|
||||
{
|
||||
// Log.Verbose("[CHATGUI PRINT REGULAR]{0}", message);
|
||||
this.PrintChat(new XivChatEntry
|
||||
{
|
||||
Message = message,
|
||||
Type = this.configuration.GeneralChatType,
|
||||
});
|
||||
this.PrintTagged(message, this.configuration.GeneralChatType, messageTag, tagColor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queue a chat message. While method is named as PrintChat (it calls it internally), it only add a entry to the queue,
|
||||
/// later to be processed when UpdateQueue() is called.
|
||||
/// </summary>
|
||||
/// <param name="message">A message to send.</param>
|
||||
public void Print(SeString message)
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Print(SeString message, string? messageTag = null, ushort? tagColor = null)
|
||||
{
|
||||
// Log.Verbose("[CHATGUI PRINT SESTRING]{0}", message.TextValue);
|
||||
this.PrintChat(new XivChatEntry
|
||||
{
|
||||
Message = message,
|
||||
Type = this.configuration.GeneralChatType,
|
||||
});
|
||||
this.PrintTagged(message, this.configuration.GeneralChatType, messageTag, tagColor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queue an error chat message. While method is named as PrintChat (it calls it internally), it only add a entry to
|
||||
/// the queue, later to be processed when UpdateQueue() is called.
|
||||
/// </summary>
|
||||
/// <param name="message">A message to send.</param>
|
||||
public void PrintError(string message)
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void PrintError(string message, string? messageTag = null, ushort? tagColor = null)
|
||||
{
|
||||
// Log.Verbose("[CHATGUI PRINT REGULAR ERROR]{0}", message);
|
||||
this.PrintChat(new XivChatEntry
|
||||
{
|
||||
Message = message,
|
||||
Type = XivChatType.Urgent,
|
||||
});
|
||||
this.PrintTagged(message, XivChatType.Urgent, messageTag, tagColor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queue an error chat message. While method is named as PrintChat (it calls it internally), it only add a entry to
|
||||
/// the queue, later to be processed when UpdateQueue() is called.
|
||||
/// </summary>
|
||||
/// <param name="message">A message to send.</param>
|
||||
public void PrintError(SeString message)
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void PrintError(SeString message, string? messageTag = null, ushort? tagColor = null)
|
||||
{
|
||||
// Log.Verbose("[CHATGUI PRINT SESTRING ERROR]{0}", message.TextValue);
|
||||
this.PrintChat(new XivChatEntry
|
||||
{
|
||||
Message = message,
|
||||
Type = XivChatType.Urgent,
|
||||
});
|
||||
this.PrintTagged(message, XivChatType.Urgent, messageTag, tagColor);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Process a chat queue.
|
||||
/// </summary>
|
||||
|
|
@ -242,7 +152,7 @@ public sealed class ChatGui : IDisposable, IServiceType
|
|||
/// <returns>A payload for handling.</returns>
|
||||
internal DalamudLinkPayload AddChatLinkHandler(string pluginName, uint commandId, Action<uint, SeString> commandAction)
|
||||
{
|
||||
var payload = new DalamudLinkPayload() { Plugin = pluginName, CommandId = commandId };
|
||||
var payload = new DalamudLinkPayload { Plugin = pluginName, CommandId = commandId };
|
||||
this.dalamudLinkHandlers.Add((pluginName, commandId), commandAction);
|
||||
return payload;
|
||||
}
|
||||
|
|
@ -266,20 +176,63 @@ public sealed class ChatGui : IDisposable, IServiceType
|
|||
/// <param name="commandId">The ID of the command to be removed.</param>
|
||||
internal void RemoveChatLinkHandler(string pluginName, uint commandId)
|
||||
{
|
||||
if (this.dalamudLinkHandlers.ContainsKey((pluginName, commandId)))
|
||||
{
|
||||
this.dalamudLinkHandlers.Remove((pluginName, commandId));
|
||||
}
|
||||
this.dalamudLinkHandlers.Remove((pluginName, commandId));
|
||||
}
|
||||
|
||||
[ServiceManager.CallWhenServicesReady]
|
||||
private void ContinueConstruction(GameGui gameGui, LibcFunction libcFunction)
|
||||
private void ContinueConstruction()
|
||||
{
|
||||
this.printMessageHook.Enable();
|
||||
this.populateItemLinkHook.Enable();
|
||||
this.interactableLinkClickedHook.Enable();
|
||||
}
|
||||
|
||||
private void PrintTagged(string message, XivChatType channel, string? tag, ushort? color)
|
||||
{
|
||||
var builder = new SeStringBuilder();
|
||||
|
||||
if (!tag.IsNullOrEmpty())
|
||||
{
|
||||
if (color is not null)
|
||||
{
|
||||
builder.AddUiForeground($"[{tag}] ", color.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.AddText($"[{tag}] ");
|
||||
}
|
||||
}
|
||||
|
||||
this.Print(new XivChatEntry
|
||||
{
|
||||
Message = builder.AddText(message).Build(),
|
||||
Type = channel,
|
||||
});
|
||||
}
|
||||
|
||||
private void PrintTagged(SeString message, XivChatType channel, string? tag, ushort? color)
|
||||
{
|
||||
var builder = new SeStringBuilder();
|
||||
|
||||
if (!tag.IsNullOrEmpty())
|
||||
{
|
||||
if (color is not null)
|
||||
{
|
||||
builder.AddUiForeground($"[{tag}] ", color.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.AddText($"[{tag}] ");
|
||||
}
|
||||
}
|
||||
|
||||
this.Print(new XivChatEntry
|
||||
{
|
||||
Message = builder.Build().Append(message),
|
||||
Type = channel,
|
||||
});
|
||||
}
|
||||
|
||||
private void HandlePopulateItemLinkDetour(IntPtr linkObjectPtr, IntPtr itemInfoPtr)
|
||||
{
|
||||
try
|
||||
|
|
@ -298,7 +251,7 @@ public sealed class ChatGui : IDisposable, IServiceType
|
|||
}
|
||||
}
|
||||
|
||||
private IntPtr HandlePrintMessageDetour(IntPtr manager, XivChatType chattype, IntPtr pSenderName, IntPtr pMessage, uint senderid, IntPtr parameter)
|
||||
private IntPtr HandlePrintMessageDetour(IntPtr manager, XivChatType chatType, IntPtr pSenderName, IntPtr pMessage, uint senderId, IntPtr parameter)
|
||||
{
|
||||
var retVal = IntPtr.Zero;
|
||||
|
||||
|
|
@ -325,13 +278,13 @@ public sealed class ChatGui : IDisposable, IServiceType
|
|||
// Call events
|
||||
var isHandled = false;
|
||||
|
||||
var invocationList = this.CheckMessageHandled.GetInvocationList();
|
||||
var invocationList = this.CheckMessageHandled!.GetInvocationList();
|
||||
foreach (var @delegate in invocationList)
|
||||
{
|
||||
try
|
||||
{
|
||||
var messageHandledDelegate = @delegate as OnCheckMessageHandledDelegate;
|
||||
messageHandledDelegate!.Invoke(chattype, senderid, ref parsedSender, ref parsedMessage, ref isHandled);
|
||||
var messageHandledDelegate = @delegate as IChatGui.OnCheckMessageHandledDelegate;
|
||||
messageHandledDelegate!.Invoke(chatType, senderId, ref parsedSender, ref parsedMessage, ref isHandled);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
|
@ -341,13 +294,13 @@ public sealed class ChatGui : IDisposable, IServiceType
|
|||
|
||||
if (!isHandled)
|
||||
{
|
||||
invocationList = this.ChatMessage.GetInvocationList();
|
||||
invocationList = this.ChatMessage!.GetInvocationList();
|
||||
foreach (var @delegate in invocationList)
|
||||
{
|
||||
try
|
||||
{
|
||||
var messageHandledDelegate = @delegate as OnMessageDelegate;
|
||||
messageHandledDelegate!.Invoke(chattype, senderid, ref parsedSender, ref parsedMessage, ref isHandled);
|
||||
var messageHandledDelegate = @delegate as IChatGui.OnMessageDelegate;
|
||||
messageHandledDelegate!.Invoke(chatType, senderId, ref parsedSender, ref parsedMessage, ref isHandled);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
|
@ -390,12 +343,12 @@ public sealed class ChatGui : IDisposable, IServiceType
|
|||
// Print the original chat if it's handled.
|
||||
if (isHandled)
|
||||
{
|
||||
this.ChatMessageHandled?.Invoke(chattype, senderid, parsedSender, parsedMessage);
|
||||
this.ChatMessageHandled?.Invoke(chatType, senderId, parsedSender, parsedMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
retVal = this.printMessageHook.Original(manager, chattype, senderPtr, messagePtr, senderid, parameter);
|
||||
this.ChatMessageUnhandled?.Invoke(chattype, senderid, parsedSender, parsedMessage);
|
||||
retVal = this.printMessageHook.Original(manager, chatType, senderPtr, messagePtr, senderId, parameter);
|
||||
this.ChatMessageUnhandled?.Invoke(chatType, senderId, parsedSender, parsedMessage);
|
||||
}
|
||||
|
||||
if (this.baseAddress == IntPtr.Zero)
|
||||
|
|
@ -407,7 +360,7 @@ public sealed class ChatGui : IDisposable, IServiceType
|
|||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Exception on OnChatMessage hook.");
|
||||
retVal = this.printMessageHook.Original(manager, chattype, pSenderName, pMessage, senderid, parameter);
|
||||
retVal = this.printMessageHook.Original(manager, chatType, pSenderName, pMessage, senderId, parameter);
|
||||
}
|
||||
|
||||
return retVal;
|
||||
|
|
@ -439,10 +392,10 @@ public sealed class ChatGui : IDisposable, IServiceType
|
|||
var linkPayload = payloads[0];
|
||||
if (linkPayload is DalamudLinkPayload link)
|
||||
{
|
||||
if (this.dalamudLinkHandlers.ContainsKey((link.Plugin, link.CommandId)))
|
||||
if (this.dalamudLinkHandlers.TryGetValue((link.Plugin, link.CommandId), out var value))
|
||||
{
|
||||
Log.Verbose($"Sending DalamudLink to {link.Plugin}: {link.CommandId}");
|
||||
this.dalamudLinkHandlers[(link.Plugin, link.CommandId)].Invoke(link.CommandId, new SeString(payloads));
|
||||
value.Invoke(link.CommandId, new SeString(payloads));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -456,3 +409,93 @@ public sealed class ChatGui : IDisposable, IServiceType
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plugin scoped version of ChatGui.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.ScopedService]
|
||||
#pragma warning disable SA1015
|
||||
[ResolveVia<IChatGui>]
|
||||
#pragma warning restore SA1015
|
||||
internal class ChatGuiPluginScoped : IDisposable, IServiceType, IChatGui
|
||||
{
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly ChatGui chatGuiService = Service<ChatGui>.Get();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ChatGuiPluginScoped"/> class.
|
||||
/// </summary>
|
||||
internal ChatGuiPluginScoped()
|
||||
{
|
||||
this.chatGuiService.ChatMessage += this.OnMessageForward;
|
||||
this.chatGuiService.CheckMessageHandled += this.OnCheckMessageForward;
|
||||
this.chatGuiService.ChatMessageHandled += this.OnMessageHandledForward;
|
||||
this.chatGuiService.ChatMessageUnhandled += this.OnMessageUnhandledForward;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event IChatGui.OnMessageDelegate? ChatMessage;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event IChatGui.OnCheckMessageHandledDelegate? CheckMessageHandled;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event IChatGui.OnMessageHandledDelegate? ChatMessageHandled;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event IChatGui.OnMessageUnhandledDelegate? ChatMessageUnhandled;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int LastLinkedItemId => this.chatGuiService.LastLinkedItemId;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public byte LastLinkedItemFlags => this.chatGuiService.LastLinkedItemFlags;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
this.chatGuiService.ChatMessage -= this.OnMessageForward;
|
||||
this.chatGuiService.CheckMessageHandled -= this.OnCheckMessageForward;
|
||||
this.chatGuiService.ChatMessageHandled -= this.OnMessageHandledForward;
|
||||
this.chatGuiService.ChatMessageUnhandled -= this.OnMessageUnhandledForward;
|
||||
|
||||
this.ChatMessage = null;
|
||||
this.CheckMessageHandled = null;
|
||||
this.ChatMessageHandled = null;
|
||||
this.ChatMessageUnhandled = null;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Print(XivChatEntry chat)
|
||||
=> this.chatGuiService.Print(chat);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Print(string message, string? messageTag = null, ushort? tagColor = null)
|
||||
=> this.chatGuiService.Print(message, messageTag, tagColor);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Print(SeString message, string? messageTag = null, ushort? tagColor = null)
|
||||
=> this.chatGuiService.Print(message, messageTag, tagColor);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void PrintError(string message, string? messageTag = null, ushort? tagColor = null)
|
||||
=> this.chatGuiService.PrintError(message, messageTag, tagColor);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void PrintError(SeString message, string? messageTag = null, ushort? tagColor = null)
|
||||
=> this.chatGuiService.PrintError(message, messageTag, tagColor);
|
||||
|
||||
private void OnMessageForward(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled)
|
||||
=> this.ChatMessage?.Invoke(type, senderId, ref sender, ref message, ref isHandled);
|
||||
|
||||
private void OnCheckMessageForward(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled)
|
||||
=> this.CheckMessageHandled?.Invoke(type, senderId, ref sender, ref message, ref isHandled);
|
||||
|
||||
private void OnMessageHandledForward(XivChatType type, uint senderId, SeString sender, SeString message)
|
||||
=> this.ChatMessageHandled?.Invoke(type, senderId, sender, message);
|
||||
|
||||
private void OnMessageUnhandledForward(XivChatType type, uint senderId, SeString sender, SeString message)
|
||||
=> this.ChatMessageUnhandled?.Invoke(type, senderId, sender, message);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
using System;
|
||||
|
||||
namespace Dalamud.Game.Gui;
|
||||
|
||||
/// <summary>
|
||||
/// The address resolver for the <see cref="ChatGui"/> class.
|
||||
/// </summary>
|
||||
public sealed class ChatGuiAddressResolver : BaseAddressResolver
|
||||
internal sealed class ChatGuiAddressResolver : BaseAddressResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the address of the native PrintMessage method.
|
||||
|
|
|
|||
|
|
@ -1,31 +1,38 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using Dalamud.Configuration.Internal;
|
||||
using Dalamud.Game.AddonEventManager;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.IoC;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Logging.Internal;
|
||||
using Dalamud.Memory;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics;
|
||||
using FFXIVClientStructs.FFXIV.Client.System.Memory;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using Serilog;
|
||||
|
||||
using DalamudAddonEventManager = Dalamud.Game.AddonEventManager.AddonEventManager;
|
||||
|
||||
namespace Dalamud.Game.Gui.Dtr;
|
||||
|
||||
/// <summary>
|
||||
/// Class used to interface with the server info bar.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
#pragma warning disable SA1015
|
||||
[ResolveVia<IDtrBar>]
|
||||
#pragma warning restore SA1015
|
||||
public sealed unsafe class DtrBar : IDisposable, IServiceType, IDtrBar
|
||||
internal sealed unsafe class DtrBar : IDisposable, IServiceType, IDtrBar
|
||||
{
|
||||
private const uint BaseNodeId = 1000;
|
||||
private const uint MouseOverEventIdOffset = 10000;
|
||||
private const uint MouseOutEventIdOffset = 20000;
|
||||
private const uint MouseClickEventIdOffset = 30000;
|
||||
|
||||
private static readonly ModuleLog Log = new("DtrBar");
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly Framework framework = Service<Framework>.Get();
|
||||
|
||||
|
|
@ -35,12 +42,25 @@ public sealed unsafe class DtrBar : IDisposable, IServiceType, IDtrBar
|
|||
[ServiceManager.ServiceDependency]
|
||||
private readonly DalamudConfiguration configuration = Service<DalamudConfiguration>.Get();
|
||||
|
||||
private List<DtrBarEntry> entries = new();
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly DalamudAddonEventManager uiEventManager = Service<DalamudAddonEventManager>.Get();
|
||||
|
||||
private readonly DtrBarAddressResolver address;
|
||||
private readonly ConcurrentBag<DtrBarEntry> newEntries = new();
|
||||
private readonly List<DtrBarEntry> entries = new();
|
||||
private readonly Hook<AddonDrawDelegate> onAddonDrawHook;
|
||||
private readonly Hook<AddonRequestedUpdateDelegate> onAddonRequestedUpdateHook;
|
||||
private uint runningNodeIds = BaseNodeId;
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private DtrBar()
|
||||
private DtrBar(SigScanner sigScanner)
|
||||
{
|
||||
this.address = new DtrBarAddressResolver();
|
||||
this.address.Setup(sigScanner);
|
||||
|
||||
this.onAddonDrawHook = Hook<AddonDrawDelegate>.FromAddress(this.address.AtkUnitBaseDraw, this.OnAddonDrawDetour);
|
||||
this.onAddonRequestedUpdateHook = Hook<AddonRequestedUpdateDelegate>.FromAddress(this.address.AddonRequestedUpdate, this.OnAddonRequestedUpdateDetour);
|
||||
|
||||
this.framework.Update += this.Update;
|
||||
|
||||
this.configuration.DtrOrder ??= new List<string>();
|
||||
|
|
@ -48,28 +68,43 @@ public sealed unsafe class DtrBar : IDisposable, IServiceType, IDtrBar
|
|||
this.configuration.QueueSave();
|
||||
}
|
||||
|
||||
private delegate void AddonDrawDelegate(AtkUnitBase* addon);
|
||||
|
||||
private delegate void AddonRequestedUpdateDelegate(AtkUnitBase* addon, NumberArrayData** numberArrayData, StringArrayData** stringArrayData);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public DtrBarEntry Get(string title, SeString? text = null)
|
||||
{
|
||||
if (this.entries.Any(x => x.Title == title))
|
||||
if (this.entries.Any(x => x.Title == title) || this.newEntries.Any(x => x.Title == title))
|
||||
throw new ArgumentException("An entry with the same title already exists.");
|
||||
|
||||
var node = this.MakeNode(++this.runningNodeIds);
|
||||
var entry = new DtrBarEntry(title, node);
|
||||
var entry = new DtrBarEntry(title, null);
|
||||
entry.Text = text;
|
||||
|
||||
// Add the entry to the end of the order list, if it's not there already.
|
||||
if (!this.configuration.DtrOrder!.Contains(title))
|
||||
this.configuration.DtrOrder!.Add(title);
|
||||
this.entries.Add(entry);
|
||||
this.ApplySort();
|
||||
|
||||
this.newEntries.Add(entry);
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Remove(string title)
|
||||
{
|
||||
if (this.entries.FirstOrDefault(entry => entry.Title == title) is { } dtrBarEntry)
|
||||
{
|
||||
dtrBarEntry.Remove();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
this.onAddonDrawHook.Dispose();
|
||||
this.onAddonRequestedUpdateHook.Dispose();
|
||||
|
||||
foreach (var entry in this.entries)
|
||||
this.RemoveNode(entry.TextNode);
|
||||
|
||||
|
|
@ -130,12 +165,20 @@ public sealed unsafe class DtrBar : IDisposable, IServiceType, IDtrBar
|
|||
return xPos.CompareTo(yPos);
|
||||
});
|
||||
}
|
||||
|
||||
[ServiceManager.CallWhenServicesReady]
|
||||
private void ContinueConstruction()
|
||||
{
|
||||
this.onAddonDrawHook.Enable();
|
||||
this.onAddonRequestedUpdateHook.Enable();
|
||||
}
|
||||
|
||||
private AtkUnitBase* GetDtr() => (AtkUnitBase*)this.gameGui.GetAddonByName("_DTR").ToPointer();
|
||||
|
||||
private void Update(Framework unused)
|
||||
private void Update(IFramework unused)
|
||||
{
|
||||
this.HandleRemovedNodes();
|
||||
this.HandleAddedNodes();
|
||||
|
||||
var dtr = this.GetDtr();
|
||||
if (dtr == null) return;
|
||||
|
|
@ -148,7 +191,7 @@ public sealed unsafe class DtrBar : IDisposable, IServiceType, IDtrBar
|
|||
if (!this.CheckForDalamudNodes())
|
||||
this.RecreateNodes();
|
||||
|
||||
var collisionNode = dtr->UldManager.NodeList[1];
|
||||
var collisionNode = dtr->GetNodeById(17);
|
||||
if (collisionNode == null) return;
|
||||
|
||||
// If we are drawing backwards, we should start from the right side of the collision node. That is,
|
||||
|
|
@ -157,28 +200,24 @@ public sealed unsafe class DtrBar : IDisposable, IServiceType, IDtrBar
|
|||
? collisionNode->X + collisionNode->Width
|
||||
: collisionNode->X;
|
||||
|
||||
for (var i = 0; i < this.entries.Count; i++)
|
||||
foreach (var data in this.entries)
|
||||
{
|
||||
var data = this.entries[i];
|
||||
var isHide = this.configuration.DtrIgnore!.Any(x => x == data.Title) || !data.Shown;
|
||||
|
||||
if (data.Dirty && data.Added && data.Text != null && data.TextNode != null)
|
||||
if (data is { Dirty: true, Added: true, Text: not null, TextNode: not null })
|
||||
{
|
||||
var node = data.TextNode;
|
||||
node->SetText(data.Text?.Encode());
|
||||
node->SetText(data.Text.Encode());
|
||||
ushort w = 0, h = 0;
|
||||
|
||||
if (isHide)
|
||||
if (!isHide)
|
||||
{
|
||||
node->AtkResNode.ToggleVisibility(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
node->AtkResNode.ToggleVisibility(true);
|
||||
node->GetTextDrawSize(&w, &h, node->NodeText.StringPtr);
|
||||
node->AtkResNode.SetWidth(w);
|
||||
}
|
||||
|
||||
node->AtkResNode.ToggleVisibility(!isHide);
|
||||
|
||||
data.Dirty = false;
|
||||
}
|
||||
|
||||
|
|
@ -202,8 +241,91 @@ public sealed unsafe class DtrBar : IDisposable, IServiceType, IDtrBar
|
|||
data.TextNode->AtkResNode.SetPositionFloat(runningXPos, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.entries[i] = data;
|
||||
private void HandleAddedNodes()
|
||||
{
|
||||
if (this.newEntries.Any())
|
||||
{
|
||||
foreach (var newEntry in this.newEntries)
|
||||
{
|
||||
newEntry.TextNode = this.MakeNode(++this.runningNodeIds);
|
||||
this.entries.Add(newEntry);
|
||||
}
|
||||
|
||||
this.newEntries.Clear();
|
||||
this.ApplySort();
|
||||
}
|
||||
}
|
||||
|
||||
// This hooks all AtkUnitBase.Draw calls, then checks for our specific addon name.
|
||||
// AddonDtr doesn't implement it's own Draw method, would need to replace vtable entry to be more efficient.
|
||||
private void OnAddonDrawDetour(AtkUnitBase* addon)
|
||||
{
|
||||
this.onAddonDrawHook!.Original(addon);
|
||||
|
||||
try
|
||||
{
|
||||
if (MemoryHelper.ReadString((nint)addon->Name, 0x20) is not "_DTR") return;
|
||||
|
||||
this.UpdateNodePositions(addon);
|
||||
|
||||
if (!this.configuration.DtrSwapDirection)
|
||||
{
|
||||
var targetSize = (ushort)this.CalculateTotalSize();
|
||||
var sizeDelta = targetSize - addon->RootNode->Width;
|
||||
|
||||
if (addon->RootNode->Width != targetSize)
|
||||
{
|
||||
addon->RootNode->SetWidth(targetSize);
|
||||
addon->SetX((short)(addon->GetX() - sizeDelta));
|
||||
|
||||
// force a RequestedUpdate immediately to force the game to right-justify it immediately.
|
||||
addon->OnUpdate(AtkStage.GetSingleton()->GetNumberArrayData(), AtkStage.GetSingleton()->GetStringArrayData());
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Exception in OnAddonDraw.");
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateNodePositions(AtkUnitBase* addon)
|
||||
{
|
||||
// If we grow to the right, we need to left-justify the original elements.
|
||||
// else if we grow to the left, the game right-justifies it for us.
|
||||
if (this.configuration.DtrSwapDirection)
|
||||
{
|
||||
var targetSize = (ushort)this.CalculateTotalSize();
|
||||
addon->RootNode->SetWidth(targetSize);
|
||||
var sizeOffset = addon->GetNodeById(17)->GetX();
|
||||
|
||||
var node = addon->RootNode->ChildNode;
|
||||
while (node is not null)
|
||||
{
|
||||
if (node->NodeID < 1000 && node->IsVisible)
|
||||
{
|
||||
node->SetX(node->GetX() - sizeOffset);
|
||||
}
|
||||
|
||||
node = node->PrevSiblingNode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAddonRequestedUpdateDetour(AtkUnitBase* addon, NumberArrayData** numberArrayData, StringArrayData** stringArrayData)
|
||||
{
|
||||
this.onAddonRequestedUpdateHook.Original(addon, numberArrayData, stringArrayData);
|
||||
|
||||
try
|
||||
{
|
||||
this.UpdateNodePositions(addon);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Exception in OnAddonRequestedUpdate.");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -235,11 +357,37 @@ public sealed unsafe class DtrBar : IDisposable, IServiceType, IDtrBar
|
|||
}
|
||||
}
|
||||
|
||||
// Calculates the total width the dtr bar should be
|
||||
private float CalculateTotalSize()
|
||||
{
|
||||
var addon = this.GetDtr();
|
||||
if (addon is null || addon->RootNode is null || addon->UldManager.NodeList is null) return 0;
|
||||
|
||||
var totalSize = 0.0f;
|
||||
|
||||
foreach (var index in Enumerable.Range(0, addon->UldManager.NodeListCount))
|
||||
{
|
||||
var node = addon->UldManager.NodeList[index];
|
||||
|
||||
// Node 17 is the default CollisionNode that fits over the existing elements
|
||||
if (node->NodeID is 17) totalSize += node->Width;
|
||||
|
||||
// Node > 1000, are our custom nodes
|
||||
if (node->NodeID is > 1000 && node->IsVisible) totalSize += node->Width + this.configuration.DtrSpacing;
|
||||
}
|
||||
|
||||
return totalSize;
|
||||
}
|
||||
|
||||
private bool AddNode(AtkTextNode* node)
|
||||
{
|
||||
var dtr = this.GetDtr();
|
||||
if (dtr == null || dtr->RootNode == null || dtr->UldManager.NodeList == null || node == null) return false;
|
||||
|
||||
this.uiEventManager.AddEvent(node->AtkResNode.NodeID + MouseOverEventIdOffset, (nint)dtr, (nint)node, AddonEventType.MouseOver, this.DtrEventHandler);
|
||||
this.uiEventManager.AddEvent(node->AtkResNode.NodeID + MouseOutEventIdOffset, (nint)dtr, (nint)node, AddonEventType.MouseOut, this.DtrEventHandler);
|
||||
this.uiEventManager.AddEvent(node->AtkResNode.NodeID + MouseClickEventIdOffset, (nint)dtr, (nint)node, AddonEventType.MouseClick, this.DtrEventHandler);
|
||||
|
||||
var lastChild = dtr->RootNode->ChildNode;
|
||||
while (lastChild->PrevSiblingNode != null) lastChild = lastChild->PrevSiblingNode;
|
||||
Log.Debug($"Found last sibling: {(ulong)lastChild:X}");
|
||||
|
|
@ -251,6 +399,7 @@ public sealed unsafe class DtrBar : IDisposable, IServiceType, IDtrBar
|
|||
Log.Debug("Set last sibling of DTR and updated child count");
|
||||
|
||||
dtr->UldManager.UpdateDrawNodeList();
|
||||
dtr->UpdateCollisionNodeList(false);
|
||||
Log.Debug("Updated node draw list");
|
||||
return true;
|
||||
}
|
||||
|
|
@ -260,6 +409,10 @@ public sealed unsafe class DtrBar : IDisposable, IServiceType, IDtrBar
|
|||
var dtr = this.GetDtr();
|
||||
if (dtr == null || dtr->RootNode == null || dtr->UldManager.NodeList == null || node == null) return false;
|
||||
|
||||
this.uiEventManager.RemoveEvent(node->AtkResNode.NodeID + MouseOverEventIdOffset, (nint)node, AddonEventType.MouseOver);
|
||||
this.uiEventManager.RemoveEvent(node->AtkResNode.NodeID + MouseOutEventIdOffset, (nint)node, AddonEventType.MouseOut);
|
||||
this.uiEventManager.RemoveEvent(node->AtkResNode.NodeID + MouseClickEventIdOffset, (nint)node, AddonEventType.MouseClick);
|
||||
|
||||
var tmpPrevNode = node->AtkResNode.PrevSiblingNode;
|
||||
var tmpNextNode = node->AtkResNode.NextSiblingNode;
|
||||
|
||||
|
|
@ -272,25 +425,23 @@ public sealed unsafe class DtrBar : IDisposable, IServiceType, IDtrBar
|
|||
dtr->RootNode->ChildCount = (ushort)(dtr->RootNode->ChildCount - 1);
|
||||
Log.Debug("Set last sibling of DTR and updated child count");
|
||||
dtr->UldManager.UpdateDrawNodeList();
|
||||
dtr->UpdateCollisionNodeList(false);
|
||||
Log.Debug("Updated node draw list");
|
||||
return true;
|
||||
}
|
||||
|
||||
private AtkTextNode* MakeNode(uint nodeId)
|
||||
{
|
||||
var newTextNode = (AtkTextNode*)IMemorySpace.GetUISpace()->Malloc((ulong)sizeof(AtkTextNode), 8);
|
||||
var newTextNode = IMemorySpace.GetUISpace()->Create<AtkTextNode>();
|
||||
if (newTextNode == null)
|
||||
{
|
||||
Log.Debug("Failed to allocate memory for text node");
|
||||
Log.Debug("Failed to allocate memory for AtkTextNode");
|
||||
return null;
|
||||
}
|
||||
|
||||
IMemorySpace.Memset(newTextNode, 0, (ulong)sizeof(AtkTextNode));
|
||||
newTextNode->Ctor();
|
||||
|
||||
newTextNode->AtkResNode.NodeID = nodeId;
|
||||
newTextNode->AtkResNode.Type = NodeType.Text;
|
||||
newTextNode->AtkResNode.NodeFlags = NodeFlags.AnchorLeft | NodeFlags.AnchorTop;
|
||||
newTextNode->AtkResNode.NodeFlags = NodeFlags.AnchorLeft | NodeFlags.AnchorTop | NodeFlags.Enabled | NodeFlags.RespondToMouse | NodeFlags.HasCollision | NodeFlags.EmitsEvents;
|
||||
newTextNode->AtkResNode.DrawFlags = 12;
|
||||
newTextNode->AtkResNode.SetWidth(22);
|
||||
newTextNode->AtkResNode.SetHeight(22);
|
||||
|
|
@ -304,16 +455,96 @@ public sealed unsafe class DtrBar : IDisposable, IServiceType, IDtrBar
|
|||
|
||||
newTextNode->SetText(" ");
|
||||
|
||||
newTextNode->TextColor.R = 255;
|
||||
newTextNode->TextColor.G = 255;
|
||||
newTextNode->TextColor.B = 255;
|
||||
newTextNode->TextColor.A = 255;
|
||||
|
||||
newTextNode->EdgeColor.R = 142;
|
||||
newTextNode->EdgeColor.G = 106;
|
||||
newTextNode->EdgeColor.B = 12;
|
||||
newTextNode->EdgeColor.A = 255;
|
||||
newTextNode->TextColor = new ByteColor { R = 255, G = 255, B = 255, A = 255 };
|
||||
newTextNode->EdgeColor = new ByteColor { R = 142, G = 106, B = 12, A = 255 };
|
||||
|
||||
return newTextNode;
|
||||
}
|
||||
|
||||
private void DtrEventHandler(AddonEventType atkEventType, IntPtr atkUnitBase, IntPtr atkResNode)
|
||||
{
|
||||
var addon = (AtkUnitBase*)atkUnitBase;
|
||||
var node = (AtkResNode*)atkResNode;
|
||||
|
||||
if (this.entries.FirstOrDefault(entry => entry.TextNode == node) is not { } dtrBarEntry) return;
|
||||
|
||||
if (dtrBarEntry is { Tooltip: not null })
|
||||
{
|
||||
switch (atkEventType)
|
||||
{
|
||||
case AddonEventType.MouseOver:
|
||||
AtkStage.GetSingleton()->TooltipManager.ShowTooltip(addon->ID, node, dtrBarEntry.Tooltip.Encode());
|
||||
break;
|
||||
|
||||
case AddonEventType.MouseOut:
|
||||
AtkStage.GetSingleton()->TooltipManager.HideTooltip(addon->ID);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (dtrBarEntry is { OnClick: not null })
|
||||
{
|
||||
switch (atkEventType)
|
||||
{
|
||||
case AddonEventType.MouseOver:
|
||||
this.uiEventManager.SetCursor(AddonCursorType.Clickable);
|
||||
break;
|
||||
|
||||
case AddonEventType.MouseOut:
|
||||
this.uiEventManager.ResetCursor();
|
||||
break;
|
||||
|
||||
case AddonEventType.MouseClick:
|
||||
dtrBarEntry.OnClick.Invoke();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plugin-scoped version of a AddonEventManager service.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.ScopedService]
|
||||
#pragma warning disable SA1015
|
||||
[ResolveVia<IDtrBar>]
|
||||
#pragma warning restore SA1015
|
||||
internal class DtrBarPluginScoped : IDisposable, IServiceType, IDtrBar
|
||||
{
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly DtrBar dtrBarService = Service<DtrBar>.Get();
|
||||
|
||||
private readonly Dictionary<string, DtrBarEntry> pluginEntries = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var entry in this.pluginEntries)
|
||||
{
|
||||
entry.Value.Remove();
|
||||
}
|
||||
|
||||
this.pluginEntries.Clear();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public DtrBarEntry Get(string title, SeString? text = null)
|
||||
{
|
||||
// If we already have a known entry for this plugin, return it.
|
||||
if (this.pluginEntries.TryGetValue(title, out var existingEntry)) return existingEntry;
|
||||
|
||||
return this.pluginEntries[title] = this.dtrBarService.Get(title, text);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Remove(string title)
|
||||
{
|
||||
if (this.pluginEntries.TryGetValue(title, out var existingEntry))
|
||||
{
|
||||
existingEntry.Remove();
|
||||
this.pluginEntries.Remove(title);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
29
Dalamud/Game/Gui/Dtr/DtrBarAddressResolver.cs
Normal file
29
Dalamud/Game/Gui/Dtr/DtrBarAddressResolver.cs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
namespace Dalamud.Game.Gui.Dtr;
|
||||
|
||||
/// <summary>
|
||||
/// DtrBar memory address resolver.
|
||||
/// </summary>
|
||||
internal class DtrBarAddressResolver : BaseAddressResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the address of the AtkUnitBaseDraw method.
|
||||
/// This is the base handler for all addons.
|
||||
/// We will use this here because _DTR does not have a overloaded handler, so we must use the base handler.
|
||||
/// </summary>
|
||||
public nint AtkUnitBaseDraw { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the DTRRequestUpdate method.
|
||||
/// </summary>
|
||||
public nint AddonRequestedUpdate { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Scan for and setup any configured address pointers.
|
||||
/// </summary>
|
||||
/// <param name="scanner">The signature scanner to facilitate setup.</param>
|
||||
protected override void Setup64Bit(SigScanner scanner)
|
||||
{
|
||||
this.AtkUnitBaseDraw = scanner.ScanText("48 83 EC 28 F6 81 ?? ?? ?? ?? ?? 4C 8B C1");
|
||||
this.AddonRequestedUpdate = scanner.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8B BA ?? ?? ?? ?? 48 8B F1 49 8B 98 ?? ?? ?? ?? 33 D2");
|
||||
}
|
||||
}
|
||||
|
|
@ -41,6 +41,16 @@ public sealed unsafe class DtrBarEntry : IDisposable
|
|||
this.Dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a tooltip to be shown when the user mouses over the dtr entry.
|
||||
/// </summary>
|
||||
public SeString? Tooltip { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a action to be invoked when the user clicks on the dtr entry.
|
||||
/// </summary>
|
||||
public Action? OnClick { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this entry is visible.
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ using Dalamud.Hooking;
|
|||
using Dalamud.IoC;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Memory;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game.Gui.FlyText;
|
||||
|
|
@ -14,10 +15,9 @@ namespace Dalamud.Game.Gui.FlyText;
|
|||
/// <summary>
|
||||
/// This class facilitates interacting with and creating native in-game "fly text".
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
public sealed class FlyTextGui : IDisposable, IServiceType
|
||||
internal sealed class FlyTextGui : IDisposable, IServiceType, IFlyTextGui
|
||||
{
|
||||
/// <summary>
|
||||
/// The native function responsible for adding fly text to the UI. See <see cref="FlyTextGuiAddressResolver.AddFlyText"/>.
|
||||
|
|
@ -39,32 +39,6 @@ public sealed class FlyTextGui : IDisposable, IServiceType
|
|||
this.createFlyTextHook = Hook<CreateFlyTextDelegate>.FromAddress(this.Address.CreateFlyText, this.CreateFlyTextDetour);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The delegate defining the type for the FlyText event.
|
||||
/// </summary>
|
||||
/// <param name="kind">The FlyTextKind. See <see cref="FlyTextKind"/>.</param>
|
||||
/// <param name="val1">Value1 passed to the native flytext function.</param>
|
||||
/// <param name="val2">Value2 passed to the native flytext function. Seems unused.</param>
|
||||
/// <param name="text1">Text1 passed to the native flytext function.</param>
|
||||
/// <param name="text2">Text2 passed to the native flytext function.</param>
|
||||
/// <param name="color">Color passed to the native flytext function. Changes flytext color.</param>
|
||||
/// <param name="icon">Icon ID passed to the native flytext function. Only displays with select FlyTextKind.</param>
|
||||
/// <param name="damageTypeIcon">Damage Type Icon ID passed to the native flytext function. Displayed next to damage values to denote damage type.</param>
|
||||
/// <param name="yOffset">The vertical offset to place the flytext at. 0 is default. Negative values result
|
||||
/// in text appearing higher on the screen. This does not change where the element begins to fade.</param>
|
||||
/// <param name="handled">Whether this flytext has been handled. If a subscriber sets this to true, the FlyText will not appear.</param>
|
||||
public delegate void OnFlyTextCreatedDelegate(
|
||||
ref FlyTextKind kind,
|
||||
ref int val1,
|
||||
ref int val2,
|
||||
ref SeString text1,
|
||||
ref SeString text2,
|
||||
ref uint color,
|
||||
ref uint icon,
|
||||
ref uint damageTypeIcon,
|
||||
ref float yOffset,
|
||||
ref bool handled);
|
||||
|
||||
/// <summary>
|
||||
/// Private delegate for the native CreateFlyText function's hook.
|
||||
/// </summary>
|
||||
|
|
@ -95,12 +69,8 @@ public sealed class FlyTextGui : IDisposable, IServiceType
|
|||
uint offsetStrMax,
|
||||
int unknown);
|
||||
|
||||
/// <summary>
|
||||
/// The FlyText event that can be subscribed to.
|
||||
/// </summary>
|
||||
public event OnFlyTextCreatedDelegate? FlyTextCreated;
|
||||
|
||||
private Dalamud Dalamud { get; }
|
||||
/// <inheritdoc/>
|
||||
public event IFlyTextGui.OnFlyTextCreatedDelegate? FlyTextCreated;
|
||||
|
||||
private FlyTextGuiAddressResolver Address { get; }
|
||||
|
||||
|
|
@ -112,18 +82,7 @@ public sealed class FlyTextGui : IDisposable, IServiceType
|
|||
this.createFlyTextHook.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Displays a fly text in-game on the local player.
|
||||
/// </summary>
|
||||
/// <param name="kind">The FlyTextKind. See <see cref="FlyTextKind"/>.</param>
|
||||
/// <param name="actorIndex">The index of the actor to place flytext on. Indexing unknown. 1 places flytext on local player.</param>
|
||||
/// <param name="val1">Value1 passed to the native flytext function.</param>
|
||||
/// <param name="val2">Value2 passed to the native flytext function. Seems unused.</param>
|
||||
/// <param name="text1">Text1 passed to the native flytext function.</param>
|
||||
/// <param name="text2">Text2 passed to the native flytext function.</param>
|
||||
/// <param name="color">Color passed to the native flytext function. Changes flytext color.</param>
|
||||
/// <param name="icon">Icon ID passed to the native flytext function. Only displays with select FlyTextKind.</param>
|
||||
/// <param name="damageTypeIcon">Damage Type Icon ID passed to the native flytext function. Displayed next to damage values to denote damage type.</param>
|
||||
/// <inheritdoc/>
|
||||
public unsafe void AddFlyText(FlyTextKind kind, uint actorIndex, uint val1, uint val2, SeString text1, SeString text2, uint color, uint icon, uint damageTypeIcon)
|
||||
{
|
||||
// Known valid flytext region within the atk arrays
|
||||
|
|
@ -318,3 +277,46 @@ public sealed class FlyTextGui : IDisposable, IServiceType
|
|||
return retVal;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plugin scoped version of FlyTextGui.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.ScopedService]
|
||||
#pragma warning disable SA1015
|
||||
[ResolveVia<IFlyTextGui>]
|
||||
#pragma warning restore SA1015
|
||||
internal class FlyTextGuiPluginScoped : IDisposable, IServiceType, IFlyTextGui
|
||||
{
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly FlyTextGui flyTextGuiService = Service<FlyTextGui>.Get();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FlyTextGuiPluginScoped"/> class.
|
||||
/// </summary>
|
||||
internal FlyTextGuiPluginScoped()
|
||||
{
|
||||
this.flyTextGuiService.FlyTextCreated += this.FlyTextCreatedForward;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event IFlyTextGui.OnFlyTextCreatedDelegate? FlyTextCreated;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
this.flyTextGuiService.FlyTextCreated -= this.FlyTextCreatedForward;
|
||||
|
||||
this.FlyTextCreated = null;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void AddFlyText(FlyTextKind kind, uint actorIndex, uint val1, uint val2, SeString text1, SeString text2, uint color, uint icon, uint damageTypeIcon)
|
||||
{
|
||||
this.flyTextGuiService.AddFlyText(kind, actorIndex, val1, val2, text1, text2, color, icon, damageTypeIcon);
|
||||
}
|
||||
|
||||
private void FlyTextCreatedForward(ref FlyTextKind kind, ref int val1, ref int val2, ref SeString text1, ref SeString text2, ref uint color, ref uint icon, ref uint damageTypeIcon, ref float yOffset, ref bool handled)
|
||||
=> this.FlyTextCreated?.Invoke(ref kind, ref val1, ref val2, ref text1, ref text2, ref color, ref icon, ref damageTypeIcon, ref yOffset, ref handled);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
using System;
|
||||
|
||||
namespace Dalamud.Game.Gui.FlyText;
|
||||
|
||||
/// <summary>
|
||||
/// An address resolver for the <see cref="FlyTextGui"/> class.
|
||||
/// </summary>
|
||||
public class FlyTextGuiAddressResolver : BaseAddressResolver
|
||||
internal class FlyTextGuiAddressResolver : BaseAddressResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the address of the native AddFlyText method, which occurs
|
||||
|
|
|
|||
|
|
@ -1,57 +1,58 @@
|
|||
namespace Dalamud.Game.Gui.FlyText;
|
||||
|
||||
/// <summary>
|
||||
/// Enum of FlyTextKind values. Members suffixed with
|
||||
/// a number seem to be a duplicate, or perform duplicate behavior.
|
||||
/// Enum of FlyTextKind values.
|
||||
/// </summary>
|
||||
public enum FlyTextKind : int
|
||||
{
|
||||
/// <summary>
|
||||
/// Val1 in serif font, Text2 in sans-serif as subtitle.
|
||||
/// Used for autos and incoming DoTs.
|
||||
/// </summary>
|
||||
AutoAttack = 0,
|
||||
AutoAttackOrDot = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Val1 in serif font, Text2 in sans-serif as subtitle.
|
||||
/// Does a bounce effect on appearance.
|
||||
/// </summary>
|
||||
DirectHit = 1,
|
||||
AutoAttackOrDotDh = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle.
|
||||
/// Does a bigger bounce effect on appearance.
|
||||
/// </summary>
|
||||
CriticalHit = 2,
|
||||
AutoAttackOrDotCrit = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Val1 in even larger serif font with 2 exclamations, Text2 in
|
||||
/// sans-serif as subtitle. Does a large bounce effect on appearance.
|
||||
/// Does not scroll up or down the screen.
|
||||
/// Val1 in even larger serif font with 2 exclamations, Text2 in sans-serif as subtitle.
|
||||
/// Does a large bounce effect on appearance. Does not scroll up or down the screen.
|
||||
/// </summary>
|
||||
CriticalDirectHit = 3,
|
||||
AutoAttackOrDotCritDh = 3,
|
||||
|
||||
/// <summary>
|
||||
/// AutoAttack with sans-serif Text1 to the left of the Val1.
|
||||
/// Val1 in serif font, Text2 in sans-serif as subtitle with sans-serif Text1 to the left of the Val1.
|
||||
/// </summary>
|
||||
NamedAttack = 4,
|
||||
Damage = 4,
|
||||
|
||||
/// <summary>
|
||||
/// DirectHit with sans-serif Text1 to the left of the Val1.
|
||||
/// Val1 in serif font, Text2 in sans-serif as subtitle with sans-serif Text1 to the left of the Val1.
|
||||
/// Does a bounce effect on appearance.
|
||||
/// </summary>
|
||||
NamedDirectHit = 5,
|
||||
DamageDh = 5,
|
||||
|
||||
/// <summary>
|
||||
/// CriticalHit with sans-serif Text1 to the left of the Val1.
|
||||
/// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle with sans-serif Text1 to the left of the Val1.
|
||||
/// Does a bigger bounce effect on appearance.
|
||||
/// </summary>
|
||||
NamedCriticalHit = 6,
|
||||
DamageCrit = 6,
|
||||
|
||||
/// <summary>
|
||||
/// CriticalDirectHit with sans-serif Text1 to the left of the Val1.
|
||||
/// Val1 in even larger serif font with 2 exclamations, Text2 in sans-serif as subtitle with sans-serif Text1 to the left of the Val1.
|
||||
/// Does a large bounce effect on appearance. Does not scroll up or down the screen.
|
||||
/// </summary>
|
||||
NamedCriticalDirectHit = 7,
|
||||
DamageCritDh = 7,
|
||||
|
||||
/// <summary>
|
||||
/// The text changes to DODGE under certain circumstances.
|
||||
/// All caps, serif MISS.
|
||||
/// </summary>
|
||||
Miss = 8,
|
||||
|
|
@ -74,12 +75,12 @@ public enum FlyTextKind : int
|
|||
/// <summary>
|
||||
/// Icon next to sans-serif Text1.
|
||||
/// </summary>
|
||||
NamedIcon = 12,
|
||||
Buff = 12,
|
||||
|
||||
/// <summary>
|
||||
/// Icon next to sans-serif Text1 (2).
|
||||
/// Icon next to sans-serif Text1.
|
||||
/// </summary>
|
||||
NamedIcon2 = 13,
|
||||
Debuff = 13,
|
||||
|
||||
/// <summary>
|
||||
/// Serif Val1 with all caps condensed font EXP with Text2 in sans-serif as subtitle.
|
||||
|
|
@ -94,42 +95,44 @@ public enum FlyTextKind : int
|
|||
/// <summary>
|
||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font MP with Text2 in sans-serif as subtitle.
|
||||
/// </summary>
|
||||
NamedMp = 16,
|
||||
MpDrain = 16,
|
||||
|
||||
/// <summary>
|
||||
/// Currently not used by the game.
|
||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font TP with Text2 in sans-serif as subtitle.
|
||||
/// </summary>
|
||||
NamedTp = 17,
|
||||
|
||||
/// <summary>
|
||||
/// AutoAttack with sans-serif Text1 to the left of the Val1 (2).
|
||||
/// Val1 in serif font, Text2 in sans-serif as subtitle with sans-serif Text1 to the left of the Val1.
|
||||
/// </summary>
|
||||
NamedAttack2 = 18,
|
||||
Healing = 18,
|
||||
|
||||
/// <summary>
|
||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font MP with Text2 in sans-serif as subtitle (2).
|
||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font MP with Text2 in sans-serif as subtitle.
|
||||
/// </summary>
|
||||
NamedMp2 = 19,
|
||||
MpRegen = 19,
|
||||
|
||||
/// <summary>
|
||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font TP with Text2 in sans-serif as subtitle (2).
|
||||
/// Currently not used by the game.
|
||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font TP with Text2 in sans-serif as subtitle.
|
||||
/// </summary>
|
||||
NamedTp2 = 20,
|
||||
|
||||
/// <summary>
|
||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font EP with Text2 in sans-serif as subtitle.
|
||||
/// </summary>
|
||||
NamedEp = 21,
|
||||
EpRegen = 21,
|
||||
|
||||
/// <summary>
|
||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font CP with Text2 in sans-serif as subtitle.
|
||||
/// </summary>
|
||||
NamedCp = 22,
|
||||
CpRegen = 22,
|
||||
|
||||
/// <summary>
|
||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font GP with Text2 in sans-serif as subtitle.
|
||||
/// </summary>
|
||||
NamedGp = 23,
|
||||
GpRegen = 23,
|
||||
|
||||
/// <summary>
|
||||
/// Displays nothing.
|
||||
|
|
@ -149,57 +152,59 @@ public enum FlyTextKind : int
|
|||
Interrupted = 26,
|
||||
|
||||
/// <summary>
|
||||
/// AutoAttack with no Text2.
|
||||
/// Val1 in serif font.
|
||||
/// </summary>
|
||||
AutoAttackNoText = 27,
|
||||
CraftingProgress = 27,
|
||||
|
||||
/// <summary>
|
||||
/// AutoAttack with no Text2 (2).
|
||||
/// Val1 in serif font.
|
||||
/// </summary>
|
||||
AutoAttackNoText2 = 28,
|
||||
CraftingQuality = 28,
|
||||
|
||||
/// <summary>
|
||||
/// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle. Does a bigger bounce effect on appearance (2).
|
||||
/// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle. Does a bigger bounce effect on appearance.
|
||||
/// </summary>
|
||||
CriticalHit2 = 29,
|
||||
CraftingQualityCrit = 29,
|
||||
|
||||
/// <summary>
|
||||
/// AutoAttack with no Text2 (3).
|
||||
/// Currently not used by the game.
|
||||
/// Val1 in serif font.
|
||||
/// </summary>
|
||||
AutoAttackNoText3 = 30,
|
||||
|
||||
/// <summary>
|
||||
/// CriticalHit with sans-serif Text1 to the left of the Val1 (2).
|
||||
/// </summary>
|
||||
NamedCriticalHit2 = 31,
|
||||
HealingCrit = 31,
|
||||
|
||||
/// <summary>
|
||||
/// Same as NamedCriticalHit with a green (cannot change) MP in condensed font to the right of Val1.
|
||||
/// Currently not used by the game.
|
||||
/// Same as DamageCrit with a MP in condensed font to the right of Val1.
|
||||
/// Does a jiggle effect to the right on appearance.
|
||||
/// </summary>
|
||||
NamedCriticalHitWithMp = 32,
|
||||
|
||||
/// <summary>
|
||||
/// Same as NamedCriticalHit with a yellow (cannot change) TP in condensed font to the right of Val1.
|
||||
/// Currently not used by the game.
|
||||
/// Same as DamageCrit with a TP in condensed font to the right of Val1.
|
||||
/// Does a jiggle effect to the right on appearance.
|
||||
/// </summary>
|
||||
NamedCriticalHitWithTp = 33,
|
||||
|
||||
/// <summary>
|
||||
/// Same as NamedIcon with sans-serif "has no effect!" to the right.
|
||||
/// Icon next to sans-serif Text1 with sans-serif "has no effect!" to the right.
|
||||
/// </summary>
|
||||
NamedIconHasNoEffect = 34,
|
||||
DebuffNoEffect = 34,
|
||||
|
||||
/// <summary>
|
||||
/// Same as NamedIcon but Text1 is slightly faded. Used for buff expiration.
|
||||
/// Icon next to sans-serif slightly faded Text1.
|
||||
/// </summary>
|
||||
NamedIconFaded = 35,
|
||||
BuffFading = 35,
|
||||
|
||||
/// <summary>
|
||||
/// Same as NamedIcon but Text1 is slightly faded (2).
|
||||
/// Used for buff expiration.
|
||||
/// Icon next to sans-serif slightly faded Text1.
|
||||
/// </summary>
|
||||
NamedIconFaded2 = 36,
|
||||
DebuffFading = 36,
|
||||
|
||||
/// <summary>
|
||||
/// Text1 in sans-serif font.
|
||||
|
|
@ -207,9 +212,9 @@ public enum FlyTextKind : int
|
|||
Named = 37,
|
||||
|
||||
/// <summary>
|
||||
/// Same as NamedIcon with sans-serif "(fully resisted)" to the right.
|
||||
/// Icon next to sans-serif Text1 with sans-serif "(fully resisted)" to the right.
|
||||
/// </summary>
|
||||
NamedIconFullyResisted = 38,
|
||||
DebuffResisted = 38,
|
||||
|
||||
/// <summary>
|
||||
/// All caps serif 'INCAPACITATED!'.
|
||||
|
|
@ -219,32 +224,34 @@ public enum FlyTextKind : int
|
|||
/// <summary>
|
||||
/// Text1 with sans-serif "(fully resisted)" to the right.
|
||||
/// </summary>
|
||||
NamedFullyResisted = 40,
|
||||
FullyResisted = 40,
|
||||
|
||||
/// <summary>
|
||||
/// Text1 with sans-serif "has no effect!" to the right.
|
||||
/// </summary>
|
||||
NamedHasNoEffect = 41,
|
||||
HasNoEffect = 41,
|
||||
|
||||
/// <summary>
|
||||
/// AutoAttack with sans-serif Text1 to the left of the Val1 (3).
|
||||
/// Val1 in serif font, Text2 in sans-serif as subtitle with sans-serif Text1 to the left of the Val1.
|
||||
/// </summary>
|
||||
NamedAttack3 = 42,
|
||||
HpDrain = 42,
|
||||
|
||||
/// <summary>
|
||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font MP with Text2 in sans-serif as subtitle (3).
|
||||
/// Currently not used by the game.
|
||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font MP with Text2 in sans-serif as subtitle.
|
||||
/// </summary>
|
||||
NamedMp3 = 43,
|
||||
|
||||
/// <summary>
|
||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font TP with Text2 in sans-serif as subtitle (3).
|
||||
/// Currently not used by the game.
|
||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font TP with Text2 in sans-serif as subtitle.
|
||||
/// </summary>
|
||||
NamedTp3 = 44,
|
||||
|
||||
/// <summary>
|
||||
/// Same as NamedIcon with serif "INVULNERABLE!" beneath the Text1.
|
||||
/// Icon next to sans-serif Text1 with serif "INVULNERABLE!" beneath the Text1.
|
||||
/// </summary>
|
||||
NamedIconInvulnerable = 45,
|
||||
DebuffInvulnerable = 45,
|
||||
|
||||
/// <summary>
|
||||
/// All caps serif RESIST.
|
||||
|
|
@ -252,20 +259,20 @@ public enum FlyTextKind : int
|
|||
Resist = 46,
|
||||
|
||||
/// <summary>
|
||||
/// Same as NamedIcon but places the given icon in the item icon outline.
|
||||
/// Icon with an item icon outline next to sans-serif Text1.
|
||||
/// </summary>
|
||||
NamedIconWithItemOutline = 47,
|
||||
LootedItem = 47,
|
||||
|
||||
/// <summary>
|
||||
/// AutoAttack with no Text2 (4).
|
||||
/// Val1 in serif font.
|
||||
/// </summary>
|
||||
AutoAttackNoText4 = 48,
|
||||
Collectability = 48,
|
||||
|
||||
/// <summary>
|
||||
/// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle (3).
|
||||
/// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle.
|
||||
/// Does a bigger bounce effect on appearance.
|
||||
/// </summary>
|
||||
CriticalHit3 = 49,
|
||||
CollectabilityCrit = 49,
|
||||
|
||||
/// <summary>
|
||||
/// All caps serif REFLECT.
|
||||
|
|
@ -278,20 +285,21 @@ public enum FlyTextKind : int
|
|||
Reflected = 51,
|
||||
|
||||
/// <summary>
|
||||
/// Val1 in serif font, Text2 in sans-serif as subtitle (2).
|
||||
/// Val1 in serif font, Text2 in sans-serif as subtitle.
|
||||
/// Does a bounce effect on appearance.
|
||||
/// </summary>
|
||||
DirectHit2 = 52,
|
||||
CraftingQualityDh = 52,
|
||||
|
||||
/// <summary>
|
||||
/// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle (4).
|
||||
/// Currently not used by the game.
|
||||
/// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle.
|
||||
/// Does a bigger bounce effect on appearance.
|
||||
/// </summary>
|
||||
CriticalHit4 = 53,
|
||||
|
||||
/// <summary>
|
||||
/// Val1 in even larger serif font with 2 exclamations, Text2 in sans-serif as subtitle (2).
|
||||
/// Val1 in even larger serif font with 2 exclamations, Text2 in sans-serif as subtitle.
|
||||
/// Does a large bounce effect on appearance. Does not scroll up or down the screen.
|
||||
/// </summary>
|
||||
CriticalDirectHit2 = 54,
|
||||
CraftingQualityCritDh = 54,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
using System;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.IoC;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Logging.Internal;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Dalamud.Utility;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
|
||||
|
|
@ -16,7 +15,6 @@ using FFXIVClientStructs.FFXIV.Client.UI;
|
|||
using FFXIVClientStructs.FFXIV.Common.Component.BGCollision;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using ImGuiNET;
|
||||
using Serilog;
|
||||
using SharpDX;
|
||||
|
||||
using Vector2 = System.Numerics.Vector2;
|
||||
|
|
@ -27,14 +25,12 @@ namespace Dalamud.Game.Gui;
|
|||
/// <summary>
|
||||
/// A class handling many aspects of the in-game UI.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
#pragma warning disable SA1015
|
||||
[ResolveVia<IGameGui>]
|
||||
#pragma warning restore SA1015
|
||||
public sealed unsafe class GameGui : IDisposable, IServiceType, IGameGui
|
||||
internal sealed unsafe class GameGui : IDisposable, IServiceType, IGameGui
|
||||
{
|
||||
private static readonly ModuleLog Log = new("GameGui");
|
||||
|
||||
private readonly GameGuiAddressResolver address;
|
||||
|
||||
private readonly GetMatrixSingletonDelegate getMatrixSingleton;
|
||||
|
|
@ -48,8 +44,8 @@ public sealed unsafe class GameGui : IDisposable, IServiceType, IGameGui
|
|||
private readonly Hook<ToggleUiHideDelegate> toggleUiHideHook;
|
||||
private readonly Hook<Utf8StringFromSequenceDelegate> utf8StringFromSequenceHook;
|
||||
|
||||
private GetUIMapObjectDelegate getUIMapObject;
|
||||
private OpenMapWithFlagDelegate openMapWithFlag;
|
||||
private GetUIMapObjectDelegate? getUIMapObject;
|
||||
private OpenMapWithFlagDelegate? openMapWithFlag;
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private GameGui(SigScanner sigScanner)
|
||||
|
|
@ -116,16 +112,16 @@ public sealed unsafe class GameGui : IDisposable, IServiceType, IGameGui
|
|||
private delegate char HandleImmDelegate(IntPtr framework, char a2, byte a3);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate IntPtr ToggleUiHideDelegate(IntPtr thisPtr, byte unknownByte);
|
||||
private delegate IntPtr ToggleUiHideDelegate(IntPtr thisPtr, bool uiVisible);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event EventHandler<bool> UiHideToggled;
|
||||
public event EventHandler<bool>? UiHideToggled;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event EventHandler<ulong> HoveredItemChanged;
|
||||
public event EventHandler<ulong>? HoveredItemChanged;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event EventHandler<HoveredAction> HoveredActionChanged;
|
||||
public event EventHandler<HoveredAction>? HoveredActionChanged;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool GameUiHidden { get; private set; }
|
||||
|
|
@ -147,7 +143,7 @@ public sealed unsafe class GameGui : IDisposable, IServiceType, IGameGui
|
|||
return false;
|
||||
}
|
||||
|
||||
this.getUIMapObject = this.address.GetVirtualFunction<GetUIMapObjectDelegate>(uiModule, 0, 8);
|
||||
this.getUIMapObject ??= this.address.GetVirtualFunction<GetUIMapObjectDelegate>(uiModule, 0, 8);
|
||||
|
||||
var uiMapObjectPtr = this.getUIMapObject(uiModule);
|
||||
|
||||
|
|
@ -157,7 +153,7 @@ public sealed unsafe class GameGui : IDisposable, IServiceType, IGameGui
|
|||
return false;
|
||||
}
|
||||
|
||||
this.openMapWithFlag = this.address.GetVirtualFunction<OpenMapWithFlagDelegate>(uiMapObjectPtr, 0, 63);
|
||||
this.openMapWithFlag ??= this.address.GetVirtualFunction<OpenMapWithFlagDelegate>(uiMapObjectPtr, 0, 63);
|
||||
|
||||
var mapLinkString = mapLink.DataString;
|
||||
|
||||
|
|
@ -217,14 +213,13 @@ public sealed unsafe class GameGui : IDisposable, IServiceType, IGameGui
|
|||
|
||||
// Read current ViewProjectionMatrix plus game window size
|
||||
var viewProjectionMatrix = default(Matrix);
|
||||
float width, height;
|
||||
var rawMatrix = (float*)(matrixSingleton + 0x1b4).ToPointer();
|
||||
|
||||
for (var i = 0; i < 16; i++, rawMatrix++)
|
||||
viewProjectionMatrix[i] = *rawMatrix;
|
||||
|
||||
width = *rawMatrix;
|
||||
height = *(rawMatrix + 1);
|
||||
var width = *rawMatrix;
|
||||
var height = *(rawMatrix + 1);
|
||||
|
||||
viewProjectionMatrix.Invert();
|
||||
|
||||
|
|
@ -414,7 +409,7 @@ public sealed unsafe class GameGui : IDisposable, IServiceType, IGameGui
|
|||
|
||||
this.HoveredItemChanged?.InvokeSafely(this, itemId);
|
||||
|
||||
Log.Verbose("HoverItemId:{0} this:{1}", itemId, hoverState.ToInt64().ToString("X"));
|
||||
Log.Verbose($"HoverItemId:{itemId} this:{hoverState.ToInt64()}");
|
||||
}
|
||||
|
||||
return retVal;
|
||||
|
|
@ -456,7 +451,7 @@ public sealed unsafe class GameGui : IDisposable, IServiceType, IGameGui
|
|||
this.HoveredAction.ActionID = (uint)Marshal.ReadInt32(hoverState, 0x3C);
|
||||
this.HoveredActionChanged?.InvokeSafely(this, this.HoveredAction);
|
||||
|
||||
Log.Verbose("HoverActionId: {0}/{1} this:{2}", actionKind, actionId, hoverState.ToInt64().ToString("X"));
|
||||
Log.Verbose($"HoverActionId: {actionKind}/{actionId} this:{hoverState.ToInt64():X}");
|
||||
}
|
||||
|
||||
private IntPtr HandleActionOutDetour(IntPtr agentActionDetail, IntPtr a2, IntPtr a3, int a4)
|
||||
|
|
@ -489,16 +484,15 @@ public sealed unsafe class GameGui : IDisposable, IServiceType, IGameGui
|
|||
return retVal;
|
||||
}
|
||||
|
||||
private IntPtr ToggleUiHideDetour(IntPtr thisPtr, byte unknownByte)
|
||||
private IntPtr ToggleUiHideDetour(IntPtr thisPtr, bool uiVisible)
|
||||
{
|
||||
// TODO(goat): We should read this from memory directly, instead of relying on catching every toggle.
|
||||
this.GameUiHidden = !this.GameUiHidden;
|
||||
this.GameUiHidden = !RaptureAtkModule.Instance()->IsUiVisible;
|
||||
|
||||
this.UiHideToggled?.InvokeSafely(this, this.GameUiHidden);
|
||||
|
||||
Log.Debug("UiHide toggled: {0}", this.GameUiHidden);
|
||||
|
||||
return this.toggleUiHideHook.Original(thisPtr, unknownByte);
|
||||
return this.toggleUiHideHook.Original(thisPtr, uiVisible);
|
||||
}
|
||||
|
||||
private char HandleImmDetour(IntPtr framework, char a2, byte a3)
|
||||
|
|
@ -514,8 +508,109 @@ public sealed unsafe class GameGui : IDisposable, IServiceType, IGameGui
|
|||
if (sourcePtr != null)
|
||||
this.utf8StringFromSequenceHook.Original(thisPtr, sourcePtr, sourceLen);
|
||||
else
|
||||
thisPtr->Ctor(); // this is in clientstructs but you could do it manually too
|
||||
thisPtr->Ctor(); // this is in ClientStructs but you could do it manually too
|
||||
|
||||
return thisPtr; // this function shouldn't need to return but the original asm moves this into rax before returning so be safe?
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plugin-scoped version of a AddonLifecycle service.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.ScopedService]
|
||||
#pragma warning disable SA1015
|
||||
[ResolveVia<IGameGui>]
|
||||
#pragma warning restore SA1015
|
||||
internal class GameGuiPluginScoped : IDisposable, IServiceType, IGameGui
|
||||
{
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly GameGui gameGuiService = Service<GameGui>.Get();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GameGuiPluginScoped"/> class.
|
||||
/// </summary>
|
||||
internal GameGuiPluginScoped()
|
||||
{
|
||||
this.gameGuiService.UiHideToggled += this.UiHideToggledForward;
|
||||
this.gameGuiService.HoveredItemChanged += this.HoveredItemForward;
|
||||
this.gameGuiService.HoveredActionChanged += this.HoveredActionForward;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event EventHandler<bool>? UiHideToggled;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event EventHandler<ulong>? HoveredItemChanged;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event EventHandler<HoveredAction>? HoveredActionChanged;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool GameUiHidden => this.gameGuiService.GameUiHidden;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ulong HoveredItem
|
||||
{
|
||||
get => this.gameGuiService.HoveredItem;
|
||||
set => this.gameGuiService.HoveredItem = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public HoveredAction HoveredAction => this.gameGuiService.HoveredAction;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
this.gameGuiService.UiHideToggled -= this.UiHideToggledForward;
|
||||
this.gameGuiService.HoveredItemChanged -= this.HoveredItemForward;
|
||||
this.gameGuiService.HoveredActionChanged -= this.HoveredActionForward;
|
||||
|
||||
this.UiHideToggled = null;
|
||||
this.HoveredItemChanged = null;
|
||||
this.HoveredActionChanged = null;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool OpenMapWithMapLink(MapLinkPayload mapLink)
|
||||
=> this.gameGuiService.OpenMapWithMapLink(mapLink);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool WorldToScreen(Vector3 worldPos, out Vector2 screenPos)
|
||||
=> this.gameGuiService.WorldToScreen(worldPos, out screenPos);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool WorldToScreen(Vector3 worldPos, out Vector2 screenPos, out bool inView)
|
||||
=> this.gameGuiService.WorldToScreen(worldPos, out screenPos, out inView);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool ScreenToWorld(Vector2 screenPos, out Vector3 worldPos, float rayDistance = 100000)
|
||||
=> this.gameGuiService.ScreenToWorld(screenPos, out worldPos, rayDistance);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IntPtr GetUIModule()
|
||||
=> this.gameGuiService.GetUIModule();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IntPtr GetAddonByName(string name, int index = 1)
|
||||
=> this.gameGuiService.GetAddonByName(name, index);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IntPtr FindAgentInterface(string addonName)
|
||||
=> this.gameGuiService.FindAgentInterface(addonName);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public unsafe IntPtr FindAgentInterface(void* addon)
|
||||
=> this.gameGuiService.FindAgentInterface(addon);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IntPtr FindAgentInterface(IntPtr addonPtr)
|
||||
=> this.gameGuiService.FindAgentInterface(addonPtr);
|
||||
|
||||
private void UiHideToggledForward(object sender, bool toggled) => this.UiHideToggled?.Invoke(sender, toggled);
|
||||
|
||||
private void HoveredItemForward(object sender, ulong itemId) => this.HoveredItemChanged?.Invoke(sender, itemId);
|
||||
|
||||
private void HoveredActionForward(object sender, HoveredAction hoverAction) => this.HoveredActionChanged?.Invoke(sender, hoverAction);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
using System;
|
||||
|
||||
namespace Dalamud.Game.Gui.PartyFinder;
|
||||
|
||||
/// <summary>
|
||||
/// The address resolver for the <see cref="PartyFinderGui"/> class.
|
||||
/// </summary>
|
||||
public class PartyFinderAddressResolver : BaseAddressResolver
|
||||
internal class PartyFinderAddressResolver : BaseAddressResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the address of the native ReceiveListing method.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using Dalamud.Game.Gui.PartyFinder.Internal;
|
||||
|
|
@ -6,6 +5,7 @@ using Dalamud.Game.Gui.PartyFinder.Types;
|
|||
using Dalamud.Hooking;
|
||||
using Dalamud.IoC;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game.Gui.PartyFinder;
|
||||
|
|
@ -13,10 +13,9 @@ namespace Dalamud.Game.Gui.PartyFinder;
|
|||
/// <summary>
|
||||
/// This class handles interacting with the native PartyFinder window.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
public sealed class PartyFinderGui : IDisposable, IServiceType
|
||||
internal sealed class PartyFinderGui : IDisposable, IServiceType, IPartyFinderGui
|
||||
{
|
||||
private readonly PartyFinderAddressResolver address;
|
||||
private readonly IntPtr memory;
|
||||
|
|
@ -35,25 +34,14 @@ public sealed class PartyFinderGui : IDisposable, IServiceType
|
|||
|
||||
this.memory = Marshal.AllocHGlobal(PartyFinderPacket.PacketSize);
|
||||
|
||||
this.receiveListingHook = Hook<ReceiveListingDelegate>.FromAddress(this.address.ReceiveListing, new ReceiveListingDelegate(this.HandleReceiveListingDetour));
|
||||
this.receiveListingHook = Hook<ReceiveListingDelegate>.FromAddress(this.address.ReceiveListing, this.HandleReceiveListingDetour);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event type fired each time the game receives an individual Party Finder listing.
|
||||
/// Cannot modify listings but can hide them.
|
||||
/// </summary>
|
||||
/// <param name="listing">The listings received.</param>
|
||||
/// <param name="args">Additional arguments passed by the game.</param>
|
||||
public delegate void PartyFinderListingEventDelegate(PartyFinderListing listing, PartyFinderListingEventArgs args);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate void ReceiveListingDelegate(IntPtr managerPtr, IntPtr data);
|
||||
|
||||
/// <summary>
|
||||
/// Event fired each time the game receives an individual Party Finder listing.
|
||||
/// Cannot modify listings but can hide them.
|
||||
/// </summary>
|
||||
public event PartyFinderListingEventDelegate ReceiveListing;
|
||||
/// <inheritdoc/>
|
||||
public event IPartyFinderGui.PartyFinderListingEventDelegate? ReceiveListing;
|
||||
|
||||
/// <summary>
|
||||
/// Dispose of managed and unmanaged resources.
|
||||
|
|
@ -138,3 +126,39 @@ public sealed class PartyFinderGui : IDisposable, IServiceType
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A scoped variant of the PartyFinderGui service.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.ScopedService]
|
||||
#pragma warning disable SA1015
|
||||
[ResolveVia<IPartyFinderGui>]
|
||||
#pragma warning restore SA1015
|
||||
internal class PartyFinderGuiPluginScoped : IDisposable, IServiceType, IPartyFinderGui
|
||||
{
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly PartyFinderGui partyFinderGuiService = Service<PartyFinderGui>.Get();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PartyFinderGuiPluginScoped"/> class.
|
||||
/// </summary>
|
||||
internal PartyFinderGuiPluginScoped()
|
||||
{
|
||||
this.partyFinderGuiService.ReceiveListing += this.ReceiveListingForward;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event IPartyFinderGui.PartyFinderListingEventDelegate? ReceiveListing;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
this.partyFinderGuiService.ReceiveListing -= this.ReceiveListingForward;
|
||||
|
||||
this.ReceiveListing = null;
|
||||
}
|
||||
|
||||
private void ReceiveListingForward(PartyFinderListing listing, PartyFinderListingEventArgs args) => this.ReceiveListing?.Invoke(listing, args);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using Dalamud.Data;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
|
||||
namespace Dalamud.Game.Gui.PartyFinder.Types;
|
||||
|
|
@ -14,7 +14,7 @@ public static class JobFlagsExtensions
|
|||
/// <param name="job">A JobFlags enum member.</param>
|
||||
/// <param name="data">A DataManager to get the ClassJob from.</param>
|
||||
/// <returns>A ClassJob if found or null if not.</returns>
|
||||
public static ClassJob ClassJob(this JobFlags job, DataManager data)
|
||||
public static ClassJob? ClassJob(this JobFlags job, IDataManager data)
|
||||
{
|
||||
var jobs = data.GetExcelSheet<ClassJob>();
|
||||
|
||||
|
|
@ -52,6 +52,6 @@ public static class JobFlagsExtensions
|
|||
_ => null,
|
||||
};
|
||||
|
||||
return row == null ? null : jobs.GetRow((uint)row);
|
||||
return row == null ? null : jobs?.GetRow((uint)row);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
|
|
@ -6,16 +5,16 @@ using Dalamud.Game.Text.SeStringHandling;
|
|||
using Dalamud.Hooking;
|
||||
using Dalamud.IoC;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Plugin.Services;
|
||||
|
||||
namespace Dalamud.Game.Gui.Toast;
|
||||
|
||||
/// <summary>
|
||||
/// This class facilitates interacting with and creating native toast windows.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
public sealed partial class ToastGui : IDisposable, IServiceType
|
||||
internal sealed partial class ToastGui : IDisposable, IServiceType, IToastGui
|
||||
{
|
||||
private const uint QuestToastCheckmarkMagic = 60081;
|
||||
|
||||
|
|
@ -39,38 +38,11 @@ public sealed partial class ToastGui : IDisposable, IServiceType
|
|||
this.address = new ToastGuiAddressResolver();
|
||||
this.address.Setup(sigScanner);
|
||||
|
||||
this.showNormalToastHook = Hook<ShowNormalToastDelegate>.FromAddress(this.address.ShowNormalToast, new ShowNormalToastDelegate(this.HandleNormalToastDetour));
|
||||
this.showQuestToastHook = Hook<ShowQuestToastDelegate>.FromAddress(this.address.ShowQuestToast, new ShowQuestToastDelegate(this.HandleQuestToastDetour));
|
||||
this.showErrorToastHook = Hook<ShowErrorToastDelegate>.FromAddress(this.address.ShowErrorToast, new ShowErrorToastDelegate(this.HandleErrorToastDetour));
|
||||
this.showNormalToastHook = Hook<ShowNormalToastDelegate>.FromAddress(this.address.ShowNormalToast, this.HandleNormalToastDetour);
|
||||
this.showQuestToastHook = Hook<ShowQuestToastDelegate>.FromAddress(this.address.ShowQuestToast, this.HandleQuestToastDetour);
|
||||
this.showErrorToastHook = Hook<ShowErrorToastDelegate>.FromAddress(this.address.ShowErrorToast, this.HandleErrorToastDetour);
|
||||
}
|
||||
|
||||
#region Event delegates
|
||||
|
||||
/// <summary>
|
||||
/// A delegate type used when a normal toast window appears.
|
||||
/// </summary>
|
||||
/// <param name="message">The message displayed.</param>
|
||||
/// <param name="options">Assorted toast options.</param>
|
||||
/// <param name="isHandled">Whether the toast has been handled or should be propagated.</param>
|
||||
public delegate void OnNormalToastDelegate(ref SeString message, ref ToastOptions options, ref bool isHandled);
|
||||
|
||||
/// <summary>
|
||||
/// A delegate type used when a quest toast window appears.
|
||||
/// </summary>
|
||||
/// <param name="message">The message displayed.</param>
|
||||
/// <param name="options">Assorted toast options.</param>
|
||||
/// <param name="isHandled">Whether the toast has been handled or should be propagated.</param>
|
||||
public delegate void OnQuestToastDelegate(ref SeString message, ref QuestToastOptions options, ref bool isHandled);
|
||||
|
||||
/// <summary>
|
||||
/// A delegate type used when an error toast window appears.
|
||||
/// </summary>
|
||||
/// <param name="message">The message displayed.</param>
|
||||
/// <param name="isHandled">Whether the toast has been handled or should be propagated.</param>
|
||||
public delegate void OnErrorToastDelegate(ref SeString message, ref bool isHandled);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Marshal delegates
|
||||
|
||||
private delegate IntPtr ShowNormalToastDelegate(IntPtr manager, IntPtr text, int layer, byte isTop, byte isFast, int logMessageId);
|
||||
|
|
@ -82,21 +54,15 @@ public sealed partial class ToastGui : IDisposable, IServiceType
|
|||
#endregion
|
||||
|
||||
#region Events
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event IToastGui.OnNormalToastDelegate? Toast;
|
||||
|
||||
/// <summary>
|
||||
/// Event that will be fired when a toast is sent by the game or a plugin.
|
||||
/// </summary>
|
||||
public event OnNormalToastDelegate Toast;
|
||||
/// <inheritdoc/>
|
||||
public event IToastGui.OnQuestToastDelegate? QuestToast;
|
||||
|
||||
/// <summary>
|
||||
/// Event that will be fired when a quest toast is sent by the game or a plugin.
|
||||
/// </summary>
|
||||
public event OnQuestToastDelegate QuestToast;
|
||||
|
||||
/// <summary>
|
||||
/// Event that will be fired when an error toast is sent by the game or a plugin.
|
||||
/// </summary>
|
||||
public event OnErrorToastDelegate ErrorToast;
|
||||
/// <inheritdoc/>
|
||||
public event IToastGui.OnErrorToastDelegate? ErrorToast;
|
||||
|
||||
#endregion
|
||||
|
||||
|
|
@ -172,31 +138,23 @@ public sealed partial class ToastGui : IDisposable, IServiceType
|
|||
/// <summary>
|
||||
/// Handles normal toasts.
|
||||
/// </summary>
|
||||
public sealed partial class ToastGui
|
||||
internal sealed partial class ToastGui
|
||||
{
|
||||
/// <summary>
|
||||
/// Show a toast message with the given content.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to be shown.</param>
|
||||
/// <param name="options">Options for the toast.</param>
|
||||
public void ShowNormal(string message, ToastOptions options = null)
|
||||
/// <inheritdoc/>
|
||||
public void ShowNormal(string message, ToastOptions? options = null)
|
||||
{
|
||||
options ??= new ToastOptions();
|
||||
this.normalQueue.Enqueue((Encoding.UTF8.GetBytes(message), options));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show a toast message with the given content.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to be shown.</param>
|
||||
/// <param name="options">Options for the toast.</param>
|
||||
public void ShowNormal(SeString message, ToastOptions options = null)
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void ShowNormal(SeString message, ToastOptions? options = null)
|
||||
{
|
||||
options ??= new ToastOptions();
|
||||
this.normalQueue.Enqueue((message.Encode(), options));
|
||||
}
|
||||
|
||||
private void ShowNormal(byte[] bytes, ToastOptions options = null)
|
||||
private void ShowNormal(byte[] bytes, ToastOptions? options = null)
|
||||
{
|
||||
options ??= new ToastOptions();
|
||||
|
||||
|
|
@ -255,31 +213,23 @@ public sealed partial class ToastGui
|
|||
/// <summary>
|
||||
/// Handles quest toasts.
|
||||
/// </summary>
|
||||
public sealed partial class ToastGui
|
||||
internal sealed partial class ToastGui
|
||||
{
|
||||
/// <summary>
|
||||
/// Show a quest toast message with the given content.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to be shown.</param>
|
||||
/// <param name="options">Options for the toast.</param>
|
||||
public void ShowQuest(string message, QuestToastOptions options = null)
|
||||
/// <inheritdoc/>
|
||||
public void ShowQuest(string message, QuestToastOptions? options = null)
|
||||
{
|
||||
options ??= new QuestToastOptions();
|
||||
this.questQueue.Enqueue((Encoding.UTF8.GetBytes(message), options));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show a quest toast message with the given content.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to be shown.</param>
|
||||
/// <param name="options">Options for the toast.</param>
|
||||
public void ShowQuest(SeString message, QuestToastOptions options = null)
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void ShowQuest(SeString message, QuestToastOptions? options = null)
|
||||
{
|
||||
options ??= new QuestToastOptions();
|
||||
this.questQueue.Enqueue((message.Encode(), options));
|
||||
}
|
||||
|
||||
private void ShowQuest(byte[] bytes, QuestToastOptions options = null)
|
||||
private void ShowQuest(byte[] bytes, QuestToastOptions? options = null)
|
||||
{
|
||||
options ??= new QuestToastOptions();
|
||||
|
||||
|
|
@ -365,21 +315,15 @@ public sealed partial class ToastGui
|
|||
/// <summary>
|
||||
/// Handles error toasts.
|
||||
/// </summary>
|
||||
public sealed partial class ToastGui
|
||||
internal sealed partial class ToastGui
|
||||
{
|
||||
/// <summary>
|
||||
/// Show an error toast message with the given content.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to be shown.</param>
|
||||
/// <inheritdoc/>
|
||||
public void ShowError(string message)
|
||||
{
|
||||
this.errorQueue.Enqueue(Encoding.UTF8.GetBytes(message));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show an error toast message with the given content.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to be shown.</param>
|
||||
/// <inheritdoc/>
|
||||
public void ShowError(SeString message)
|
||||
{
|
||||
this.errorQueue.Enqueue(message.Encode());
|
||||
|
|
@ -433,3 +377,76 @@ public sealed partial class ToastGui
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plugin scoped version of ToastGui.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.ScopedService]
|
||||
#pragma warning disable SA1015
|
||||
[ResolveVia<IToastGui>]
|
||||
#pragma warning restore SA1015
|
||||
internal class ToastGuiPluginScoped : IDisposable, IServiceType, IToastGui
|
||||
{
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly ToastGui toastGuiService = Service<ToastGui>.Get();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ToastGuiPluginScoped"/> class.
|
||||
/// </summary>
|
||||
internal ToastGuiPluginScoped()
|
||||
{
|
||||
this.toastGuiService.Toast += this.ToastForward;
|
||||
this.toastGuiService.QuestToast += this.QuestToastForward;
|
||||
this.toastGuiService.ErrorToast += this.ErrorToastForward;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event IToastGui.OnNormalToastDelegate? Toast;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event IToastGui.OnQuestToastDelegate? QuestToast;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event IToastGui.OnErrorToastDelegate? ErrorToast;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
this.toastGuiService.Toast -= this.ToastForward;
|
||||
this.toastGuiService.QuestToast -= this.QuestToastForward;
|
||||
this.toastGuiService.ErrorToast -= this.ErrorToastForward;
|
||||
|
||||
this.Toast = null;
|
||||
this.QuestToast = null;
|
||||
this.ErrorToast = null;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void ShowNormal(string message, ToastOptions? options = null) => this.toastGuiService.ShowNormal(message, options);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void ShowNormal(SeString message, ToastOptions? options = null) => this.toastGuiService.ShowNormal(message, options);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void ShowQuest(string message, QuestToastOptions? options = null) => this.toastGuiService.ShowQuest(message, options);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void ShowQuest(SeString message, QuestToastOptions? options = null) => this.toastGuiService.ShowQuest(message, options);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void ShowError(string message) => this.toastGuiService.ShowError(message);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void ShowError(SeString message) => this.toastGuiService.ShowError(message);
|
||||
|
||||
private void ToastForward(ref SeString message, ref ToastOptions options, ref bool isHandled)
|
||||
=> this.Toast?.Invoke(ref message, ref options, ref isHandled);
|
||||
|
||||
private void QuestToastForward(ref SeString message, ref QuestToastOptions options, ref bool isHandled)
|
||||
=> this.QuestToast?.Invoke(ref message, ref options, ref isHandled);
|
||||
|
||||
private void ErrorToastForward(ref SeString message, ref bool isHandled)
|
||||
=> this.ErrorToast?.Invoke(ref message, ref isHandled);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
using System;
|
||||
|
||||
namespace Dalamud.Game.Gui.Toast;
|
||||
|
||||
/// <summary>
|
||||
/// An address resolver for the <see cref="ToastGui"/> class.
|
||||
/// </summary>
|
||||
public class ToastGuiAddressResolver : BaseAddressResolver
|
||||
internal class ToastGuiAddressResolver : BaseAddressResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the address of the native ShowNormalToast method.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
|
||||
|
|
@ -10,7 +9,7 @@ namespace Dalamud.Game.Internal.DXGI;
|
|||
/// The address resolver for native D3D11 methods to facilitate displaying the Dalamud UI.
|
||||
/// </summary>
|
||||
[Obsolete("This has been deprecated in favor of the VTable resolver.")]
|
||||
public sealed class SwapChainSigResolver : BaseAddressResolver, ISwapChainAddressResolver
|
||||
internal sealed class SwapChainSigResolver : BaseAddressResolver, ISwapChainAddressResolver
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public IntPtr Present { get; set; }
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
|
@ -15,7 +14,7 @@ namespace Dalamud.Game.Internal.DXGI;
|
|||
/// <remarks>
|
||||
/// If the normal signature based method of resolution fails, this is the backup.
|
||||
/// </remarks>
|
||||
public class SwapChainVtableResolver : BaseAddressResolver, ISwapChainAddressResolver
|
||||
internal class SwapChainVtableResolver : BaseAddressResolver, ISwapChainAddressResolver
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public IntPtr Present { get; set; }
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ namespace Dalamud.Game.Libc;
|
|||
#pragma warning disable SA1015
|
||||
[ResolveVia<ILibcFunction>]
|
||||
#pragma warning restore SA1015
|
||||
public sealed class LibcFunction : IServiceType, ILibcFunction
|
||||
internal sealed class LibcFunction : IServiceType, ILibcFunction
|
||||
{
|
||||
private readonly LibcFunctionAddressResolver address;
|
||||
private readonly StdStringFromCStringDelegate stdStringCtorCString;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ namespace Dalamud.Game.Libc;
|
|||
/// <summary>
|
||||
/// The address resolver for the <see cref="LibcFunction"/> class.
|
||||
/// </summary>
|
||||
public sealed class LibcFunctionAddressResolver : BaseAddressResolver
|
||||
internal sealed class LibcFunctionAddressResolver : BaseAddressResolver
|
||||
{
|
||||
private delegate IntPtr StringFromCString();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using Dalamud.Configuration.Internal;
|
||||
|
|
@ -14,13 +13,9 @@ namespace Dalamud.Game.Network;
|
|||
/// <summary>
|
||||
/// This class handles interacting with game network events.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
#pragma warning disable SA1015
|
||||
[ResolveVia<IGameNetwork>]
|
||||
#pragma warning restore SA1015
|
||||
public sealed class GameNetwork : IDisposable, IServiceType, IGameNetwork
|
||||
internal sealed class GameNetwork : IDisposable, IServiceType, IGameNetwork
|
||||
{
|
||||
private readonly GameNetworkAddressResolver address;
|
||||
private readonly Hook<ProcessZonePacketDownDelegate> processZonePacketDownHook;
|
||||
|
|
@ -57,14 +52,10 @@ public sealed class GameNetwork : IDisposable, IServiceType, IGameNetwork
|
|||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate byte ProcessZonePacketUpDelegate(IntPtr a1, IntPtr dataPtr, IntPtr a3, byte a4);
|
||||
|
||||
/// <summary>
|
||||
/// Event that is called when a network message is sent/received.
|
||||
/// </summary>
|
||||
public event IGameNetwork.OnNetworkMessageDelegate NetworkMessage;
|
||||
/// <inheritdoc/>
|
||||
public event IGameNetwork.OnNetworkMessageDelegate? NetworkMessage;
|
||||
|
||||
/// <summary>
|
||||
/// Dispose of managed and unmanaged resources.
|
||||
/// </summary>
|
||||
/// <inheritdoc/>
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
this.processZonePacketDownHook.Dispose();
|
||||
|
|
@ -148,3 +139,40 @@ public sealed class GameNetwork : IDisposable, IServiceType, IGameNetwork
|
|||
return this.processZonePacketUpHook.Original(a1, dataPtr, a3, a4);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plugin-scoped version of a AddonLifecycle service.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.ScopedService]
|
||||
#pragma warning disable SA1015
|
||||
[ResolveVia<IGameNetwork>]
|
||||
#pragma warning restore SA1015
|
||||
internal class GameNetworkPluginScoped : IDisposable, IServiceType, IGameNetwork
|
||||
{
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly GameNetwork gameNetworkService = Service<GameNetwork>.Get();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GameNetworkPluginScoped"/> class.
|
||||
/// </summary>
|
||||
internal GameNetworkPluginScoped()
|
||||
{
|
||||
this.gameNetworkService.NetworkMessage += this.NetworkMessageForward;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event IGameNetwork.OnNetworkMessageDelegate? NetworkMessage;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
this.gameNetworkService.NetworkMessage -= this.NetworkMessageForward;
|
||||
|
||||
this.NetworkMessage = null;
|
||||
}
|
||||
|
||||
private void NetworkMessageForward(nint dataPtr, ushort opCode, uint sourceActorId, uint targetActorId, NetworkMessageDirection direction)
|
||||
=> this.NetworkMessage?.Invoke(dataPtr, opCode, sourceActorId, targetActorId, direction);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
using System;
|
||||
|
||||
namespace Dalamud.Game.Network;
|
||||
|
||||
/// <summary>
|
||||
/// The address resolver for the <see cref="GameNetwork"/> class.
|
||||
/// </summary>
|
||||
public sealed class GameNetworkAddressResolver : BaseAddressResolver
|
||||
internal sealed class GameNetworkAddressResolver : BaseAddressResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the address of the ProcessZonePacketDown method.
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ namespace Dalamud.Game;
|
|||
#pragma warning disable SA1015
|
||||
[ResolveVia<ISigScanner>]
|
||||
#pragma warning restore SA1015
|
||||
public class SigScanner : IDisposable, IServiceType, ISigScanner
|
||||
internal class SigScanner : IDisposable, IServiceType, ISigScanner
|
||||
{
|
||||
private readonly FileInfo? cacheFile;
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using System.IO;
|
|||
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Newtonsoft.Json;
|
||||
using Serilog;
|
||||
|
||||
|
|
@ -27,12 +28,6 @@ public abstract partial class Payload
|
|||
// To force-invalidate it, Dirty can be set to true
|
||||
private byte[] encodedData;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Lumina instance to use for any necessary data lookups.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public DataManager DataResolver => Service<DataManager>.Get();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of this payload.
|
||||
/// </summary>
|
||||
|
|
@ -43,6 +38,13 @@ public abstract partial class Payload
|
|||
/// </summary>
|
||||
public bool Dirty { get; protected set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Lumina instance to use for any necessary data lookups.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
// TODO: We should refactor this. It should not be possible to get IDataManager through here.
|
||||
protected IDataManager DataResolver => Service<DataManager>.Get();
|
||||
|
||||
/// <summary>
|
||||
/// Decodes a binary representation of a payload into its corresponding nice object payload.
|
||||
/// </summary>
|
||||
|
|
@ -206,9 +208,9 @@ public abstract partial class Payload
|
|||
case SeStringChunkType.Icon:
|
||||
payload = new IconPayload();
|
||||
break;
|
||||
|
||||
|
||||
default:
|
||||
Log.Verbose("Unhandled SeStringChunkType: {0}", chunkType);
|
||||
// Log.Verbose("Unhandled SeStringChunkType: {0}", chunkType);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -307,6 +309,11 @@ public abstract partial class Payload
|
|||
/// </summary>
|
||||
protected enum SeStringChunkType
|
||||
{
|
||||
/// <summary>
|
||||
/// See the <see cref="NewLinePayload"/>.
|
||||
/// </summary>
|
||||
NewLine = 0x10,
|
||||
|
||||
/// <summary>
|
||||
/// See the <see cref="IconPayload"/> class.
|
||||
/// </summary>
|
||||
|
|
@ -317,11 +324,6 @@ public abstract partial class Payload
|
|||
/// </summary>
|
||||
EmphasisItalic = 0x1A,
|
||||
|
||||
/// <summary>
|
||||
/// See the <see cref="NewLinePayload"/>.
|
||||
/// </summary>
|
||||
NewLine = 0x10,
|
||||
|
||||
/// <summary>
|
||||
/// See the <see cref="SeHyphenPayload"/> class.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -421,7 +421,7 @@ public class SeString
|
|||
/// </summary>
|
||||
/// <param name="payloads">The Payloads to append.</param>
|
||||
/// <returns>This object.</returns>
|
||||
public SeString Append(List<Payload> payloads)
|
||||
public SeString Append(IEnumerable<Payload> payloads)
|
||||
{
|
||||
this.Payloads.AddRange(payloads);
|
||||
return this;
|
||||
|
|
|
|||
|
|
@ -38,7 +38,11 @@ public class SeStringBuilder
|
|||
/// </summary>
|
||||
/// <param name="payloads">A list of payloads.</param>
|
||||
/// <returns>The current builder.</returns>
|
||||
public SeStringBuilder Append(IEnumerable<Payload> payloads) => this.Append(new SeString(payloads.ToList()));
|
||||
public SeStringBuilder Append(IEnumerable<Payload> payloads)
|
||||
{
|
||||
this.BuiltString.Payloads.AddRange(payloads);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Append raw text to the builder.
|
||||
|
|
|
|||
89
Dalamud/Hooking/Internal/CallHook.cs
Normal file
89
Dalamud/Hooking/Internal/CallHook.cs
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using Reloaded.Hooks.Definitions;
|
||||
|
||||
namespace Dalamud.Hooking.Internal;
|
||||
|
||||
/// <summary>
|
||||
/// This class represents a callsite hook. Only the specific address's instructions are replaced with this hook.
|
||||
/// This is a destructive operation, no other callsite hooks can coexist at the same address.
|
||||
///
|
||||
/// There's no .Original for this hook type.
|
||||
/// This is only intended for be for functions where the parameters provided allow you to invoke the original call.
|
||||
///
|
||||
/// This class was specifically added for hooking virtual function callsites.
|
||||
/// Only the specific callsite hooked is modified, if the game calls the virtual function from other locations this hook will not be triggered.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Delegate signature for this hook.</typeparam>
|
||||
internal class CallHook<T> : IDisposable where T : Delegate
|
||||
{
|
||||
private readonly Reloaded.Hooks.AsmHook asmHook;
|
||||
|
||||
private T? detour;
|
||||
private bool activated;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CallHook{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="address">Address of the instruction to replace.</param>
|
||||
/// <param name="detour">Delegate to invoke.</param>
|
||||
internal CallHook(nint address, T detour)
|
||||
{
|
||||
this.detour = detour;
|
||||
|
||||
var detourPtr = Marshal.GetFunctionPointerForDelegate(this.detour);
|
||||
var code = new[]
|
||||
{
|
||||
"use64",
|
||||
$"mov rax, 0x{detourPtr:X8}",
|
||||
"call rax",
|
||||
};
|
||||
|
||||
var opt = new AsmHookOptions
|
||||
{
|
||||
PreferRelativeJump = true,
|
||||
Behaviour = Reloaded.Hooks.Definitions.Enums.AsmHookBehaviour.DoNotExecuteOriginal,
|
||||
MaxOpcodeSize = 5,
|
||||
};
|
||||
|
||||
this.asmHook = new Reloaded.Hooks.AsmHook(code, (nuint)address, opt);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not the hook is enabled.
|
||||
/// </summary>
|
||||
public bool IsEnabled => this.asmHook.IsEnabled;
|
||||
|
||||
/// <summary>
|
||||
/// Starts intercepting a call to the function.
|
||||
/// </summary>
|
||||
public void Enable()
|
||||
{
|
||||
if (!this.activated)
|
||||
{
|
||||
this.activated = true;
|
||||
this.asmHook.Activate();
|
||||
return;
|
||||
}
|
||||
|
||||
this.asmHook.Enable();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops intercepting a call to the function.
|
||||
/// </summary>
|
||||
public void Disable()
|
||||
{
|
||||
this.asmHook.Disable();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove a hook from the current process.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
this.asmHook.Disable();
|
||||
this.detour = null;
|
||||
}
|
||||
}
|
||||
270
Dalamud/Interface/ColorHelpers.cs
Normal file
270
Dalamud/Interface/ColorHelpers.cs
Normal file
|
|
@ -0,0 +1,270 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Dalamud.Interface;
|
||||
|
||||
/// <summary>
|
||||
/// Class containing various methods for manipulating colors.
|
||||
/// </summary>
|
||||
public static class ColorHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// A struct representing a color using HSVA coordinates.
|
||||
/// </summary>
|
||||
/// <param name="H">The hue represented by this struct.</param>
|
||||
/// <param name="S">The saturation represented by this struct.</param>
|
||||
/// <param name="V">The value represented by this struct.</param>
|
||||
/// <param name="A">The alpha represented by this struct.</param>
|
||||
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1313:Parameter names should begin with lower-case letter",
|
||||
Justification = "I don't like it.")]
|
||||
public record struct HsvaColor(float H, float S, float V, float A);
|
||||
|
||||
/// <summary>
|
||||
/// Pack a vector4 color into a uint for use in ImGui APIs.
|
||||
/// </summary>
|
||||
/// <param name="color">The color to pack.</param>
|
||||
/// <returns>The packed color.</returns>
|
||||
public static uint RgbaVector4ToUint(Vector4 color)
|
||||
{
|
||||
var r = (byte)(color.X * 255);
|
||||
var g = (byte)(color.Y * 255);
|
||||
var b = (byte)(color.Z * 255);
|
||||
var a = (byte)(color.W * 255);
|
||||
|
||||
return (uint)((a << 24) | (b << 16) | (g << 8) | r);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert a RGBA color in the range of 0.f to 1.f to a uint.
|
||||
/// </summary>
|
||||
/// <param name="color">The color to pack.</param>
|
||||
/// <returns>The packed color.</returns>
|
||||
public static Vector4 RgbaUintToVector4(uint color)
|
||||
{
|
||||
var r = (color & 0x000000FF) / 255f;
|
||||
var g = ((color & 0x0000FF00) >> 8) / 255f;
|
||||
var b = ((color & 0x00FF0000) >> 16) / 255f;
|
||||
var a = ((color & 0xFF000000) >> 24) / 255f;
|
||||
|
||||
return new Vector4(r, g, b, a);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert a RGBA color in the range of 0.f to 1.f to a HSV color.
|
||||
/// </summary>
|
||||
/// <param name="color">The color to convert.</param>
|
||||
/// <returns>The color in a HSV representation.</returns>
|
||||
public static HsvaColor RgbaToHsv(Vector4 color)
|
||||
{
|
||||
var r = color.X;
|
||||
var g = color.Y;
|
||||
var b = color.Z;
|
||||
|
||||
var max = Math.Max(r, Math.Max(g, b));
|
||||
var min = Math.Min(r, Math.Min(g, b));
|
||||
|
||||
var h = max;
|
||||
var s = max;
|
||||
var v = max;
|
||||
|
||||
var d = max - min;
|
||||
s = max == 0 ? 0 : d / max;
|
||||
|
||||
if (max == min)
|
||||
{
|
||||
h = 0; // achromatic
|
||||
}
|
||||
else
|
||||
{
|
||||
if (max == r)
|
||||
{
|
||||
h = ((g - b) / d) + (g < b ? 6 : 0);
|
||||
}
|
||||
else if (max == g)
|
||||
{
|
||||
h = ((b - r) / d) + 2;
|
||||
}
|
||||
else if (max == b)
|
||||
{
|
||||
h = ((r - g) / d) + 4;
|
||||
}
|
||||
|
||||
h /= 6;
|
||||
}
|
||||
|
||||
return new HsvaColor(h, s, v, color.W);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert a HSV color to a RGBA color in the range of 0.f to 1.f.
|
||||
/// </summary>
|
||||
/// <param name="hsv">The color to convert.</param>
|
||||
/// <returns>The RGB color.</returns>
|
||||
public static Vector4 HsvToRgb(HsvaColor hsv)
|
||||
{
|
||||
var h = hsv.H;
|
||||
var s = hsv.S;
|
||||
var v = hsv.V;
|
||||
|
||||
var r = 0f;
|
||||
var g = 0f;
|
||||
var b = 0f;
|
||||
|
||||
var i = (int)Math.Floor(h * 6);
|
||||
var f = (h * 6) - i;
|
||||
var p = v * (1 - s);
|
||||
var q = v * (1 - (f * s));
|
||||
var t = v * (1 - ((1 - f) * s));
|
||||
|
||||
switch (i % 6)
|
||||
{
|
||||
case 0:
|
||||
r = v;
|
||||
g = t;
|
||||
b = p;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
r = q;
|
||||
g = v;
|
||||
b = p;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
r = p;
|
||||
g = v;
|
||||
b = t;
|
||||
break;
|
||||
|
||||
case 3:
|
||||
r = p;
|
||||
g = q;
|
||||
b = v;
|
||||
break;
|
||||
|
||||
case 4:
|
||||
r = t;
|
||||
g = p;
|
||||
b = v;
|
||||
break;
|
||||
|
||||
case 5:
|
||||
r = v;
|
||||
g = p;
|
||||
b = q;
|
||||
break;
|
||||
}
|
||||
|
||||
return new Vector4(r, g, b, hsv.A);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lighten a color.
|
||||
/// </summary>
|
||||
/// <param name="color">The color to lighten.</param>
|
||||
/// <param name="amount">The amount to lighten.</param>
|
||||
/// <returns>The lightened color.</returns>
|
||||
public static Vector4 Lighten(this Vector4 color, float amount)
|
||||
{
|
||||
var hsv = RgbaToHsv(color);
|
||||
hsv.V += amount;
|
||||
return HsvToRgb(hsv);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lighten a color.
|
||||
/// </summary>
|
||||
/// <param name="color">The color to lighten.</param>
|
||||
/// <param name="amount">The amount to lighten.</param>
|
||||
/// <returns>The lightened color.</returns>
|
||||
public static uint Lighten(uint color, float amount)
|
||||
=> RgbaVector4ToUint(Lighten(RgbaUintToVector4(color), amount));
|
||||
|
||||
/// <summary>
|
||||
/// Darken a color.
|
||||
/// </summary>
|
||||
/// <param name="color">The color to lighten.</param>
|
||||
/// <param name="amount">The amount to lighten.</param>
|
||||
/// <returns>The darkened color.</returns>
|
||||
public static Vector4 Darken(this Vector4 color, float amount)
|
||||
{
|
||||
var hsv = RgbaToHsv(color);
|
||||
hsv.V -= amount;
|
||||
return HsvToRgb(hsv);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Darken a color.
|
||||
/// </summary>
|
||||
/// <param name="color">The color to lighten.</param>
|
||||
/// <param name="amount">The amount to lighten.</param>
|
||||
/// <returns>The darkened color.</returns>
|
||||
public static uint Darken(uint color, float amount)
|
||||
=> RgbaVector4ToUint(Darken(RgbaUintToVector4(color), amount));
|
||||
|
||||
/// <summary>
|
||||
/// Saturate a color.
|
||||
/// </summary>
|
||||
/// <param name="color">The color to lighten.</param>
|
||||
/// <param name="amount">The amount to lighten.</param>
|
||||
/// <returns>The saturated color.</returns>
|
||||
public static Vector4 Saturate(this Vector4 color, float amount)
|
||||
{
|
||||
var hsv = RgbaToHsv(color);
|
||||
hsv.S += amount;
|
||||
return HsvToRgb(hsv);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saturate a color.
|
||||
/// </summary>
|
||||
/// <param name="color">The color to lighten.</param>
|
||||
/// <param name="amount">The amount to lighten.</param>
|
||||
/// <returns>The saturated color.</returns>
|
||||
public static uint Saturate(uint color, float amount)
|
||||
=> RgbaVector4ToUint(Saturate(RgbaUintToVector4(color), amount));
|
||||
|
||||
/// <summary>
|
||||
/// Desaturate a color.
|
||||
/// </summary>
|
||||
/// <param name="color">The color to lighten.</param>
|
||||
/// <param name="amount">The amount to lighten.</param>
|
||||
/// <returns>The desaturated color.</returns>
|
||||
public static Vector4 Desaturate(this Vector4 color, float amount)
|
||||
{
|
||||
var hsv = RgbaToHsv(color);
|
||||
hsv.S -= amount;
|
||||
return HsvToRgb(hsv);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Desaturate a color.
|
||||
/// </summary>
|
||||
/// <param name="color">The color to lighten.</param>
|
||||
/// <param name="amount">The amount to lighten.</param>
|
||||
/// <returns>The desaturated color.</returns>
|
||||
public static uint Desaturate(uint color, float amount)
|
||||
=> RgbaVector4ToUint(Desaturate(RgbaUintToVector4(color), amount));
|
||||
|
||||
/// <summary>
|
||||
/// Fade a color.
|
||||
/// </summary>
|
||||
/// <param name="color">The color to lighten.</param>
|
||||
/// <param name="amount">The amount to lighten.</param>
|
||||
/// <returns>The faded color.</returns>
|
||||
public static Vector4 Fade(this Vector4 color, float amount)
|
||||
{
|
||||
var hsv = RgbaToHsv(color);
|
||||
hsv.A -= amount;
|
||||
return HsvToRgb(hsv);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fade a color.
|
||||
/// </summary>
|
||||
/// <param name="color">The color to lighten.</param>
|
||||
/// <param name="amount">The amount to lighten.</param>
|
||||
/// <returns>The faded color.</returns>
|
||||
public static uint Fade(uint color, float amount)
|
||||
=> RgbaVector4ToUint(Fade(RgbaUintToVector4(color), amount));
|
||||
}
|
||||
|
|
@ -25,7 +25,7 @@ public static partial class ImGuiComponents
|
|||
|
||||
var text = icon.ToIconString();
|
||||
if (id.HasValue)
|
||||
text = $"{text}{id}";
|
||||
text = $"{text}##{id}";
|
||||
|
||||
var button = DisabledButton(text, defaultColor, activeColor, hoveredColor, alphaMult);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
using System;
|
||||
using System.Numerics;
|
||||
|
||||
using Dalamud.Interface.Utility;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Dalamud.Interface.Components;
|
||||
|
|
@ -119,4 +121,71 @@ public static partial class ImGuiComponents
|
|||
|
||||
return button;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// IconButton component to use an icon as a button with color options.
|
||||
/// </summary>
|
||||
/// <param name="icon">Icon to show.</param>
|
||||
/// <param name="text">Text to show.</param>
|
||||
/// <param name="defaultColor">The default color of the button.</param>
|
||||
/// <param name="activeColor">The color of the button when active.</param>
|
||||
/// <param name="hoveredColor">The color of the button when hovered.</param>
|
||||
/// <returns>Indicator if button is clicked.</returns>
|
||||
public static bool IconButtonWithText(FontAwesomeIcon icon, string text, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null)
|
||||
{
|
||||
var numColors = 0;
|
||||
|
||||
if (defaultColor.HasValue)
|
||||
{
|
||||
ImGui.PushStyleColor(ImGuiCol.Button, defaultColor.Value);
|
||||
numColors++;
|
||||
}
|
||||
|
||||
if (activeColor.HasValue)
|
||||
{
|
||||
ImGui.PushStyleColor(ImGuiCol.ButtonActive, activeColor.Value);
|
||||
numColors++;
|
||||
}
|
||||
|
||||
if (hoveredColor.HasValue)
|
||||
{
|
||||
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, hoveredColor.Value);
|
||||
numColors++;
|
||||
}
|
||||
|
||||
ImGui.PushID(text);
|
||||
|
||||
ImGui.PushFont(UiBuilder.IconFont);
|
||||
var iconSize = ImGui.CalcTextSize(icon.ToIconString());
|
||||
ImGui.PopFont();
|
||||
|
||||
var textSize = ImGui.CalcTextSize(text);
|
||||
var dl = ImGui.GetWindowDrawList();
|
||||
var cursor = ImGui.GetCursorScreenPos();
|
||||
|
||||
var iconPadding = 3 * ImGuiHelpers.GlobalScale;
|
||||
|
||||
// Draw an ImGui button with the icon and text
|
||||
var buttonWidth = iconSize.X + textSize.X + (ImGui.GetStyle().FramePadding.X * 2) + iconPadding;
|
||||
var buttonHeight = Math.Max(iconSize.Y, textSize.Y) + (ImGui.GetStyle().FramePadding.Y * 2);
|
||||
var button = ImGui.Button(string.Empty, new Vector2(buttonWidth, buttonHeight));
|
||||
|
||||
// Draw the icon on the window drawlist
|
||||
var iconPos = new Vector2(cursor.X + ImGui.GetStyle().FramePadding.X, cursor.Y + ImGui.GetStyle().FramePadding.Y);
|
||||
|
||||
ImGui.PushFont(UiBuilder.IconFont);
|
||||
dl.AddText(iconPos, ImGui.GetColorU32(ImGuiCol.Text), icon.ToIconString());
|
||||
ImGui.PopFont();
|
||||
|
||||
// Draw the text on the window drawlist
|
||||
var textPos = new Vector2(iconPos.X + iconSize.X + iconPadding, cursor.Y + ImGui.GetStyle().FramePadding.Y);
|
||||
dl.AddText(textPos, ImGui.GetColorU32(ImGuiCol.Text), text);
|
||||
|
||||
ImGui.PopID();
|
||||
|
||||
if (numColors > 0)
|
||||
ImGui.PopStyleColor(numColors);
|
||||
|
||||
return button;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ internal partial class DragDropManager
|
|||
public static extern int RevokeDragDrop(nint hwnd);
|
||||
|
||||
[DllImport("shell32.dll")]
|
||||
public static extern int DragQueryFile(IntPtr hDrop, uint iFile, StringBuilder lpszFile, int cch);
|
||||
public static extern int DragQueryFileW(IntPtr hDrop, uint iFile, [MarshalAs(UnmanagedType.LPWStr)] StringBuilder lpszFile, int cch);
|
||||
}
|
||||
}
|
||||
#pragma warning restore SA1600 // Elements should be documented
|
||||
|
|
|
|||
|
|
@ -204,7 +204,7 @@ internal partial class DragDropManager : DragDropManager.IDropTarget
|
|||
try
|
||||
{
|
||||
data.GetData(ref this.formatEtc, out var stgMedium);
|
||||
var numFiles = DragDropInterop.DragQueryFile(stgMedium.unionmember, uint.MaxValue, new StringBuilder(), 0);
|
||||
var numFiles = DragDropInterop.DragQueryFileW(stgMedium.unionmember, uint.MaxValue, new StringBuilder(), 0);
|
||||
var files = new string[numFiles];
|
||||
var sb = new StringBuilder(1024);
|
||||
var directoryCount = 0;
|
||||
|
|
@ -212,11 +212,11 @@ internal partial class DragDropManager : DragDropManager.IDropTarget
|
|||
for (var i = 0u; i < numFiles; ++i)
|
||||
{
|
||||
sb.Clear();
|
||||
var ret = DragDropInterop.DragQueryFile(stgMedium.unionmember, i, sb, sb.Capacity);
|
||||
var ret = DragDropInterop.DragQueryFileW(stgMedium.unionmember, i, sb, sb.Capacity);
|
||||
if (ret >= sb.Capacity)
|
||||
{
|
||||
sb.Capacity = ret + 1;
|
||||
ret = DragDropInterop.DragQueryFile(stgMedium.unionmember, i, sb, sb.Capacity);
|
||||
ret = DragDropInterop.DragQueryFileW(stgMedium.unionmember, i, sb, sb.Capacity);
|
||||
}
|
||||
|
||||
if (ret > 0 && ret < sb.Capacity)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// Generated by Dalamud.FASharpGen - don't modify this file directly.
|
||||
// Font-Awesome Version: 6.3.0
|
||||
// Font-Awesome Version: 6.4.2
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
|
|
@ -19,12 +19,6 @@ public enum FontAwesomeIcon
|
|||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The Font Awesome "acquisitionsincorporated" icon unicode character.
|
||||
/// </summary>
|
||||
[Obsolete]
|
||||
AcquisitionsIncorporated = 0xF6AF,
|
||||
|
||||
/// <summary>
|
||||
/// The Font Awesome "rectangle-ad" icon unicode character.
|
||||
/// </summary>
|
||||
|
|
@ -43,7 +37,7 @@ public enum FontAwesomeIcon
|
|||
/// The Font Awesome "address-card" icon unicode character.
|
||||
/// </summary>
|
||||
[FontAwesomeSearchTerms(new[] { "address card", "about", "contact", "id", "identification", "postcard", "profile", "registration" })]
|
||||
[FontAwesomeCategoriesAttribute(new[] { "Business", "Communication", "Users + People" })]
|
||||
[FontAwesomeCategoriesAttribute(new[] { "Accessibility", "Alphabet", "Business", "Communication", "Users + People" })]
|
||||
AddressCard = 0xF2BB,
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -53,12 +47,6 @@ public enum FontAwesomeIcon
|
|||
[FontAwesomeCategoriesAttribute(new[] { "Charts + Diagrams", "Design", "Editing", "Photos + Images", "Shapes" })]
|
||||
Adjust = 0xF042,
|
||||
|
||||
/// <summary>
|
||||
/// The Font Awesome "adobe" icon unicode character.
|
||||
/// </summary>
|
||||
[Obsolete]
|
||||
Adobe = 0xF778,
|
||||
|
||||
/// <summary>
|
||||
/// The Font Awesome "spray-can-sparkles" icon unicode character.
|
||||
/// </summary>
|
||||
|
|
@ -884,7 +872,7 @@ public enum FontAwesomeIcon
|
|||
/// The Font Awesome "binoculars" icon unicode character.
|
||||
/// </summary>
|
||||
[FontAwesomeSearchTerms(new[] { "binoculars", "glasses", "magnify", "scenic", "spyglass", "view" })]
|
||||
[FontAwesomeCategoriesAttribute(new[] { "Camping", "Maps", "Nature" })]
|
||||
[FontAwesomeCategoriesAttribute(new[] { "Astronomy", "Camping", "Maps", "Nature" })]
|
||||
Binoculars = 0xF1E5,
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -1359,7 +1347,7 @@ public enum FontAwesomeIcon
|
|||
/// <summary>
|
||||
/// The Font Awesome "bullseye" icon unicode character.
|
||||
/// </summary>
|
||||
[FontAwesomeSearchTerms(new[] { "bullseye", "archery", "goal", "objective", "target" })]
|
||||
[FontAwesomeSearchTerms(new[] { "bullseye", "archery", "goal", "objective", "strategy", "target" })]
|
||||
[FontAwesomeCategoriesAttribute(new[] { "Business", "Marketing", "Toggle" })]
|
||||
Bullseye = 0xF140,
|
||||
|
||||
|
|
@ -2202,7 +2190,7 @@ public enum FontAwesomeIcon
|
|||
/// The Font Awesome "gear" icon unicode character.
|
||||
/// </summary>
|
||||
[FontAwesomeSearchTerms(new[] { "cog", "cogwheel", "gear", "mechanical", "settings", "sprocket", "tool", "wheel" })]
|
||||
[FontAwesomeCategoriesAttribute(new[] { "Spinners" })]
|
||||
[FontAwesomeCategoriesAttribute(new[] { "Coding", "Editing", "Spinners" })]
|
||||
Cog = 0xF013,
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -3423,14 +3411,14 @@ public enum FontAwesomeIcon
|
|||
/// <summary>
|
||||
/// The Font Awesome "flask" icon unicode character.
|
||||
/// </summary>
|
||||
[FontAwesomeSearchTerms(new[] { "flask", "beaker", "experimental", "labs", "science" })]
|
||||
[FontAwesomeSearchTerms(new[] { "flask", "beaker", "chemicals", "experiment", "experimental", "labs", "liquid", "potion", "science", "vial" })]
|
||||
[FontAwesomeCategoriesAttribute(new[] { "Food + Beverage", "Maps", "Medical + Health", "Science" })]
|
||||
Flask = 0xF0C3,
|
||||
|
||||
/// <summary>
|
||||
/// The Font Awesome "flask-vial" icon unicode character.
|
||||
/// </summary>
|
||||
[FontAwesomeSearchTerms(new[] { "flask vial", "ampule", "chemistry", "lab", "laboratory", "test", "test tube" })]
|
||||
[FontAwesomeSearchTerms(new[] { "flask vial", " beaker", " chemicals", " experiment", " experimental", " labs", " liquid", " science", " vial", "ampule", "chemistry", "lab", "laboratory", "potion", "test", "test tube" })]
|
||||
[FontAwesomeCategoriesAttribute(new[] { "Humanitarian", "Medical + Health", "Science" })]
|
||||
FlaskVial = 0xE4F3,
|
||||
|
||||
|
|
@ -5088,7 +5076,7 @@ public enum FontAwesomeIcon
|
|||
/// <summary>
|
||||
/// The Font Awesome "lightbulb" icon unicode character.
|
||||
/// </summary>
|
||||
[FontAwesomeSearchTerms(new[] { "lightbulb", "bulb", "comic", "electric", "energy", "idea", "inspiration", "light", "light bulb" })]
|
||||
[FontAwesomeSearchTerms(new[] { "lightbulb", " comic", " electric", " idea", " innovation", " inspiration", " light", " light bulb", " bulb", "bulb", "comic", "electric", "energy", "idea", "inspiration", "mechanical" })]
|
||||
[FontAwesomeCategoriesAttribute(new[] { "Energy", "Household", "Maps", "Marketing" })]
|
||||
Lightbulb = 0xF0EB,
|
||||
|
||||
|
|
@ -5270,7 +5258,7 @@ public enum FontAwesomeIcon
|
|||
/// <summary>
|
||||
/// The Font Awesome "magnifying-glass-chart" icon unicode character.
|
||||
/// </summary>
|
||||
[FontAwesomeSearchTerms(new[] { "magnifying glass chart", "analysis", "chart" })]
|
||||
[FontAwesomeSearchTerms(new[] { "magnifying glass chart", " data", " graph", " intelligence", "analysis", "chart", "market" })]
|
||||
[FontAwesomeCategoriesAttribute(new[] { "Business", "Humanitarian", "Marketing" })]
|
||||
MagnifyingGlassChart = 0xE522,
|
||||
|
||||
|
|
@ -5484,12 +5472,6 @@ public enum FontAwesomeIcon
|
|||
[FontAwesomeCategoriesAttribute(new[] { "Astronomy", "Weather" })]
|
||||
Meteor = 0xF753,
|
||||
|
||||
/// <summary>
|
||||
/// The Font Awesome "microblog" icon unicode character.
|
||||
/// </summary>
|
||||
[Obsolete]
|
||||
Microblog = 0xF91A,
|
||||
|
||||
/// <summary>
|
||||
/// The Font Awesome "microchip" icon unicode character.
|
||||
/// </summary>
|
||||
|
|
@ -5676,7 +5658,7 @@ public enum FontAwesomeIcon
|
|||
/// The Font Awesome "monument" icon unicode character.
|
||||
/// </summary>
|
||||
[FontAwesomeSearchTerms(new[] { "monument", "building", "historic", "landmark", "memorable" })]
|
||||
[FontAwesomeCategoriesAttribute(new[] { "Buildings", "Travel + Hotel" })]
|
||||
[FontAwesomeCategoriesAttribute(new[] { "Buildings", "Maps", "Travel + Hotel" })]
|
||||
Monument = 0xF5A6,
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -6043,12 +6025,6 @@ public enum FontAwesomeIcon
|
|||
[FontAwesomeCategoriesAttribute(new[] { "Business", "Design", "Editing" })]
|
||||
PenNib = 0xF5AD,
|
||||
|
||||
/// <summary>
|
||||
/// The Font Awesome "pennyarcade" icon unicode character.
|
||||
/// </summary>
|
||||
[Obsolete]
|
||||
PennyArcade = 0xF704,
|
||||
|
||||
/// <summary>
|
||||
/// The Font Awesome "square-pen" icon unicode character.
|
||||
/// </summary>
|
||||
|
|
@ -6415,12 +6391,6 @@ public enum FontAwesomeIcon
|
|||
[FontAwesomeCategoriesAttribute(new[] { "Files", "Film + Video", "Photos + Images", "Social" })]
|
||||
PhotoVideo = 0xF87C,
|
||||
|
||||
/// <summary>
|
||||
/// The Font Awesome "piedpipersquare" icon unicode character.
|
||||
/// </summary>
|
||||
[Obsolete]
|
||||
PiedPiperSquare = 0xF91E,
|
||||
|
||||
/// <summary>
|
||||
/// The Font Awesome "piggy-bank" icon unicode character.
|
||||
/// </summary>
|
||||
|
|
@ -8720,12 +8690,6 @@ public enum FontAwesomeIcon
|
|||
[FontAwesomeCategoriesAttribute(new[] { "Buildings", "Humanitarian", "Travel + Hotel" })]
|
||||
TreeCity = 0xE587,
|
||||
|
||||
/// <summary>
|
||||
/// The Font Awesome "tripadvisor" icon unicode character.
|
||||
/// </summary>
|
||||
[Obsolete]
|
||||
Tripadvisor = 0xF262,
|
||||
|
||||
/// <summary>
|
||||
/// The Font Awesome "trophy" icon unicode character.
|
||||
/// </summary>
|
||||
|
|
@ -8887,12 +8851,6 @@ public enum FontAwesomeIcon
|
|||
[FontAwesomeCategoriesAttribute(new[] { "Arrows", "Media Playback" })]
|
||||
UndoAlt = 0xF2EA,
|
||||
|
||||
/// <summary>
|
||||
/// The Font Awesome "unity" icon unicode character.
|
||||
/// </summary>
|
||||
[Obsolete]
|
||||
Unity = 0xF949,
|
||||
|
||||
/// <summary>
|
||||
/// The Font Awesome "universal-access" icon unicode character.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -172,6 +172,42 @@ internal class GameFontManager : IServiceType
|
|||
fontPtr.BuildLookupTable();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a glyph range for use with ImGui AddFont.
|
||||
/// </summary>
|
||||
/// <param name="family">Font family and size.</param>
|
||||
/// <param name="mergeDistance">Merge two ranges into one if distance is below the value specified in this parameter.</param>
|
||||
/// <returns>Glyph ranges.</returns>
|
||||
public GCHandle ToGlyphRanges(GameFontFamilyAndSize family, int mergeDistance = 8)
|
||||
{
|
||||
var fdt = this.fdts[(int)family]!;
|
||||
var ranges = new List<ushort>(fdt.Glyphs.Count)
|
||||
{
|
||||
checked((ushort)fdt.Glyphs[0].CharInt),
|
||||
checked((ushort)fdt.Glyphs[0].CharInt),
|
||||
};
|
||||
|
||||
foreach (var glyph in fdt.Glyphs.Skip(1))
|
||||
{
|
||||
var c32 = glyph.CharInt;
|
||||
if (c32 >= 0x10000)
|
||||
break;
|
||||
|
||||
var c16 = unchecked((ushort)c32);
|
||||
if (ranges[^1] + mergeDistance >= c16 && c16 > ranges[^1])
|
||||
{
|
||||
ranges[^1] = c16;
|
||||
}
|
||||
else if (ranges[^1] + 1 < c16)
|
||||
{
|
||||
ranges.Add(c16);
|
||||
ranges.Add(c16);
|
||||
}
|
||||
}
|
||||
|
||||
return GCHandle.Alloc(ranges.ToArray(), GCHandleType.Pinned);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new GameFontHandle, and increases internal font reference counter, and if it's first time use, then the font will be loaded on next font building process.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -525,7 +525,8 @@ internal class DalamudInterface : IDisposable, IServiceType
|
|||
|
||||
private void DrawCreditsDarkeningAnimation()
|
||||
{
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.WindowRounding, 0f);
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.WindowRounding | ImGuiStyleVar.WindowBorderSize, 0f);
|
||||
using var color = ImRaii.PushColor(ImGuiCol.WindowBg, new Vector4(0, 0, 0, 0));
|
||||
|
||||
ImGui.SetNextWindowPos(new Vector2(0, 0));
|
||||
ImGui.SetNextWindowSize(ImGuiHelpers.MainViewport.Size);
|
||||
|
|
|
|||
|
|
@ -791,10 +791,10 @@ internal class InterfaceManager : IDisposable, IServiceType
|
|||
}
|
||||
else
|
||||
{
|
||||
var japaneseRangeHandle = GCHandle.Alloc(GlyphRangesJapanese.GlyphRanges, GCHandleType.Pinned);
|
||||
garbageList.Add(japaneseRangeHandle);
|
||||
var rangeHandle = gameFontManager.ToGlyphRanges(GameFontFamilyAndSize.Axis12);
|
||||
garbageList.Add(rangeHandle);
|
||||
|
||||
fontConfig.GlyphRanges = japaneseRangeHandle.AddrOfPinnedObject();
|
||||
fontConfig.GlyphRanges = rangeHandle.AddrOfPinnedObject();
|
||||
fontConfig.PixelSnapH = true;
|
||||
DefaultFont = ioFonts.AddFontFromFileTTF(fontPathJp, fontConfig.SizePixels, fontConfig);
|
||||
this.loadedFontInfo[DefaultFont] = fontInfo;
|
||||
|
|
@ -851,22 +851,19 @@ internal class InterfaceManager : IDisposable, IServiceType
|
|||
|
||||
foreach (var (fontSize, requests) in extraFontRequests)
|
||||
{
|
||||
List<Tuple<ushort, ushort>> codepointRanges = new();
|
||||
codepointRanges.Add(Tuple.Create(Fallback1Codepoint, Fallback1Codepoint));
|
||||
codepointRanges.Add(Tuple.Create(Fallback2Codepoint, Fallback2Codepoint));
|
||||
|
||||
// ImGui default ellipsis characters
|
||||
codepointRanges.Add(Tuple.Create<ushort, ushort>(0x2026, 0x2026));
|
||||
codepointRanges.Add(Tuple.Create<ushort, ushort>(0x0085, 0x0085));
|
||||
List<(ushort, ushort)> codepointRanges = new(4 + requests.Sum(x => x.CodepointRanges.Count))
|
||||
{
|
||||
new(Fallback1Codepoint, Fallback1Codepoint),
|
||||
new(Fallback2Codepoint, Fallback2Codepoint),
|
||||
// ImGui default ellipsis characters
|
||||
new(0x2026, 0x2026),
|
||||
new(0x0085, 0x0085),
|
||||
};
|
||||
|
||||
foreach (var request in requests)
|
||||
{
|
||||
foreach (var range in request.CodepointRanges)
|
||||
codepointRanges.Add(range);
|
||||
}
|
||||
|
||||
codepointRanges.Sort((x, y) => (x.Item1 == y.Item1 ? (x.Item2 < y.Item2 ? -1 : (x.Item2 == y.Item2 ? 0 : 1)) : (x.Item1 < y.Item1 ? -1 : 1)));
|
||||
codepointRanges.AddRange(request.CodepointRanges.Select(x => (From: x.Item1, To: x.Item2)));
|
||||
|
||||
codepointRanges.Sort();
|
||||
List<ushort> flattenedRanges = new();
|
||||
foreach (var range in codepointRanges)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -203,7 +203,9 @@ internal class TextureManager : IDisposable, IServiceType, ITextureSubstitutionP
|
|||
/// </summary>
|
||||
/// <param name="file">The texture to obtain a handle to.</param>
|
||||
/// <returns>A texture wrap that can be used to render the texture.</returns>
|
||||
public IDalamudTextureWrap? GetTexture(TexFile file)
|
||||
/// <exception cref="InvalidOperationException">Thrown when the graphics system is not available yet. Relevant for plugins when LoadRequiredState is set to 0 or 1.</exception>
|
||||
/// <exception cref="NotSupportedException">Thrown when the given <see cref="TexFile"/> is not supported. Most likely is that the file is corrupt.</exception>
|
||||
public IDalamudTextureWrap GetTexture(TexFile file)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(file);
|
||||
|
||||
|
|
@ -229,6 +231,40 @@ internal class TextureManager : IDisposable, IServiceType, ITextureSubstitutionP
|
|||
return this.im.LoadImageFromDxgiFormat(buffer.RawData, pitch, buffer.Width, buffer.Height, (Format)dxgiFormat);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string GetSubstitutedPath(string originalPath)
|
||||
{
|
||||
if (this.InterceptTexDataLoad == null)
|
||||
return originalPath;
|
||||
|
||||
string? interceptPath = null;
|
||||
this.InterceptTexDataLoad.Invoke(originalPath, ref interceptPath);
|
||||
|
||||
if (interceptPath != null)
|
||||
{
|
||||
Log.Verbose("Intercept: {OriginalPath} => {ReplacePath}", originalPath, interceptPath);
|
||||
return interceptPath;
|
||||
}
|
||||
|
||||
return originalPath;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void InvalidatePaths(IEnumerable<string> paths)
|
||||
{
|
||||
lock (this.activeTextures)
|
||||
{
|
||||
foreach (var path in paths)
|
||||
{
|
||||
if (!this.activeTextures.TryGetValue(path, out var info) || info == null)
|
||||
continue;
|
||||
|
||||
info.Wrap?.Dispose();
|
||||
info.Wrap = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
|
|
@ -249,110 +285,102 @@ internal class TextureManager : IDisposable, IServiceType, ITextureSubstitutionP
|
|||
/// Get texture info.
|
||||
/// </summary>
|
||||
/// <param name="path">Path to the texture.</param>
|
||||
/// <param name="refresh">Whether or not the texture should be reloaded if it was unloaded.</param>
|
||||
/// <param name="rethrow">
|
||||
/// If true, exceptions caused by texture load will not be caught.
|
||||
/// If false, exceptions will be caught and a dummy texture will be returned to prevent plugins from using invalid texture handles.
|
||||
/// </param>
|
||||
/// <returns>Info object storing texture metadata.</returns>
|
||||
internal TextureInfo GetInfo(string path, bool refresh = true, bool rethrow = false)
|
||||
internal TextureInfo GetInfo(string path, bool rethrow = false)
|
||||
{
|
||||
TextureInfo? info;
|
||||
lock (this.activeTextures)
|
||||
{
|
||||
this.activeTextures.TryGetValue(path, out info);
|
||||
if (!this.activeTextures.TryGetValue(path, out info))
|
||||
{
|
||||
Debug.Assert(rethrow, "This should never run when getting outside of creator");
|
||||
|
||||
info = new TextureInfo();
|
||||
this.activeTextures.Add(path, info);
|
||||
}
|
||||
|
||||
if (info == null)
|
||||
throw new Exception("null info in activeTextures");
|
||||
}
|
||||
|
||||
if (info == null)
|
||||
{
|
||||
info = new TextureInfo();
|
||||
lock (this.activeTextures)
|
||||
{
|
||||
if (!this.activeTextures.TryAdd(path, info))
|
||||
Log.Warning("Texture {Path} tracked twice", path);
|
||||
}
|
||||
}
|
||||
|
||||
if (refresh && info.KeepAliveCount == 0)
|
||||
if (info.KeepAliveCount == 0)
|
||||
info.LastAccess = DateTime.UtcNow;
|
||||
|
||||
if (info is { Wrap: not null })
|
||||
return info;
|
||||
|
||||
if (refresh)
|
||||
{
|
||||
if (!this.im.IsReady)
|
||||
if (!this.im.IsReady)
|
||||
throw new InvalidOperationException("Cannot create textures before scene is ready");
|
||||
|
||||
string? interceptPath = null;
|
||||
this.InterceptTexDataLoad?.Invoke(path, ref interceptPath);
|
||||
|
||||
if (interceptPath != null)
|
||||
// Substitute the path here for loading, instead of when getting the respective TextureInfo
|
||||
path = this.GetSubstitutedPath(path);
|
||||
|
||||
TextureWrap? wrap;
|
||||
try
|
||||
{
|
||||
// We want to load this from the disk, probably, if the path has a root
|
||||
// Not sure if this can cause issues with e.g. network drives, might have to rethink
|
||||
// and add a flag instead if it does.
|
||||
if (Path.IsPathRooted(path))
|
||||
{
|
||||
Log.Verbose("Intercept: {OriginalPath} => {ReplacePath}", path, interceptPath);
|
||||
path = interceptPath;
|
||||
}
|
||||
|
||||
TextureWrap? wrap;
|
||||
try
|
||||
{
|
||||
// We want to load this from the disk, probably, if the path has a root
|
||||
// Not sure if this can cause issues with e.g. network drives, might have to rethink
|
||||
// and add a flag instead if it does.
|
||||
if (Path.IsPathRooted(path))
|
||||
if (Path.GetExtension(path) == ".tex")
|
||||
{
|
||||
if (Path.GetExtension(path) == ".tex")
|
||||
{
|
||||
// Attempt to load via Lumina
|
||||
var file = this.dataManager.GameData.GetFileFromDisk<TexFile>(path);
|
||||
wrap = this.GetTexture(file);
|
||||
Log.Verbose("Texture {Path} loaded FS via Lumina", path);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Attempt to load image
|
||||
wrap = this.im.LoadImage(path);
|
||||
Log.Verbose("Texture {Path} loaded FS via LoadImage", path);
|
||||
}
|
||||
// Attempt to load via Lumina
|
||||
var file = this.dataManager.GameData.GetFileFromDisk<TexFile>(path);
|
||||
wrap = this.GetTexture(file);
|
||||
Log.Verbose("Texture {Path} loaded FS via Lumina", path);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Load regularly from dats
|
||||
var file = this.dataManager.GetFile<TexFile>(path);
|
||||
if (file == null)
|
||||
throw new Exception("Could not load TexFile from dat.");
|
||||
|
||||
wrap = this.GetTexture(file);
|
||||
Log.Verbose("Texture {Path} loaded from SqPack", path);
|
||||
// Attempt to load image
|
||||
wrap = this.im.LoadImage(path);
|
||||
Log.Verbose("Texture {Path} loaded FS via LoadImage", path);
|
||||
}
|
||||
|
||||
if (wrap == null)
|
||||
throw new Exception("Could not create texture");
|
||||
|
||||
// TODO: We could support this, but I don't think it's worth it at the moment.
|
||||
var extents = new Vector2(wrap.Width, wrap.Height);
|
||||
if (info.Extents != Vector2.Zero && info.Extents != extents)
|
||||
Log.Warning("Texture at {Path} changed size between reloads, this is currently not supported.", path);
|
||||
|
||||
info.Extents = extents;
|
||||
}
|
||||
catch (Exception e)
|
||||
else
|
||||
{
|
||||
Log.Error(e, "Could not load texture from {Path}", path);
|
||||
|
||||
// When creating the texture initially, we want to be able to pass errors back to the plugin
|
||||
if (rethrow)
|
||||
throw;
|
||||
|
||||
// This means that the load failed due to circumstances outside of our control,
|
||||
// and we can't do anything about it. Return a dummy texture so that the plugin still
|
||||
// has something to draw.
|
||||
wrap = this.fallbackTextureWrap;
|
||||
// Load regularly from dats
|
||||
var file = this.dataManager.GetFile<TexFile>(path);
|
||||
if (file == null)
|
||||
throw new Exception("Could not load TexFile from dat.");
|
||||
|
||||
wrap = this.GetTexture(file);
|
||||
Log.Verbose("Texture {Path} loaded from SqPack", path);
|
||||
}
|
||||
|
||||
if (wrap == null)
|
||||
throw new Exception("Could not create texture");
|
||||
|
||||
info.Wrap = wrap;
|
||||
// TODO: We could support this, but I don't think it's worth it at the moment.
|
||||
var extents = new Vector2(wrap.Width, wrap.Height);
|
||||
if (info.Extents != Vector2.Zero && info.Extents != extents)
|
||||
Log.Warning("Texture at {Path} changed size between reloads, this is currently not supported.", path);
|
||||
|
||||
info.Extents = extents;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Could not load texture from {Path}", path);
|
||||
|
||||
// When creating the texture initially, we want to be able to pass errors back to the plugin
|
||||
if (rethrow)
|
||||
throw;
|
||||
|
||||
// This means that the load failed due to circumstances outside of our control,
|
||||
// and we can't do anything about it. Return a dummy texture so that the plugin still
|
||||
// has something to draw.
|
||||
wrap = this.fallbackTextureWrap;
|
||||
|
||||
// Prevent divide-by-zero
|
||||
if (info.Extents == Vector2.Zero)
|
||||
info.Extents = Vector2.One;
|
||||
}
|
||||
|
||||
info.Wrap = wrap;
|
||||
return info;
|
||||
}
|
||||
|
||||
|
|
@ -364,15 +392,23 @@ internal class TextureManager : IDisposable, IServiceType, ITextureSubstitutionP
|
|||
/// <param name="keepAlive">Whether or not this handle was created in keep-alive mode.</param>
|
||||
internal void NotifyTextureDisposed(string path, bool keepAlive)
|
||||
{
|
||||
var info = this.GetInfo(path, false);
|
||||
info.RefCount--;
|
||||
lock (this.activeTextures)
|
||||
{
|
||||
if (!this.activeTextures.TryGetValue(path, out var info))
|
||||
{
|
||||
Log.Warning("Disposing texture that didn't exist: {Path}", path);
|
||||
return;
|
||||
}
|
||||
|
||||
info.RefCount--;
|
||||
|
||||
if (keepAlive)
|
||||
info.KeepAliveCount--;
|
||||
if (keepAlive)
|
||||
info.KeepAliveCount--;
|
||||
|
||||
// Clean it up by the next update. If it's re-requested in-between, we don't reload it.
|
||||
if (info.RefCount <= 0)
|
||||
info.LastAccess = default;
|
||||
// Clean it up by the next update. If it's re-requested in-between, we don't reload it.
|
||||
if (info.RefCount <= 0)
|
||||
info.LastAccess = default;
|
||||
}
|
||||
}
|
||||
|
||||
private static string FormatIconPath(uint iconId, string? type, bool highResolution)
|
||||
|
|
@ -388,18 +424,25 @@ internal class TextureManager : IDisposable, IServiceType, ITextureSubstitutionP
|
|||
|
||||
private TextureManagerTextureWrap? CreateWrap(string path, bool keepAlive)
|
||||
{
|
||||
// This will create the texture.
|
||||
// That's fine, it's probably used immediately and this will let the plugin catch load errors.
|
||||
var info = this.GetInfo(path, rethrow: true);
|
||||
info.RefCount++;
|
||||
lock (this.activeTextures)
|
||||
{
|
||||
// This will create the texture.
|
||||
// That's fine, it's probably used immediately and this will let the plugin catch load errors.
|
||||
var info = this.GetInfo(path, rethrow: true);
|
||||
|
||||
if (keepAlive)
|
||||
info.KeepAliveCount++;
|
||||
// We need to increase the refcounts here while locking the collection!
|
||||
// Otherwise, if this is loaded from a task, cleanup might already try to delete it
|
||||
// before it can be increased.
|
||||
info.RefCount++;
|
||||
|
||||
return new TextureManagerTextureWrap(path, info.Extents, keepAlive, this);
|
||||
if (keepAlive)
|
||||
info.KeepAliveCount++;
|
||||
|
||||
return new TextureManagerTextureWrap(path, info.Extents, keepAlive, this);
|
||||
}
|
||||
}
|
||||
|
||||
private void FrameworkOnUpdate(Framework fw)
|
||||
private void FrameworkOnUpdate(IFramework fw)
|
||||
{
|
||||
lock (this.activeTextures)
|
||||
{
|
||||
|
|
@ -586,7 +629,9 @@ internal class TextureManagerTextureWrap : IDalamudTextureWrap
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IntPtr ImGuiHandle => this.manager.GetInfo(this.path).Wrap!.ImGuiHandle;
|
||||
public IntPtr ImGuiHandle => !this.IsDisposed ?
|
||||
this.manager.GetInfo(this.path).Wrap!.ImGuiHandle :
|
||||
throw new InvalidOperationException("Texture already disposed. You may not render it.");
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int Width { get; private set; }
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using Dalamud.Game;
|
||||
|
|
@ -416,7 +417,7 @@ internal unsafe class UiDebug
|
|||
$"MultiplyRGB: {node->MultiplyRed} {node->MultiplyGreen} {node->MultiplyBlue}");
|
||||
}
|
||||
|
||||
private bool DrawUnitListHeader(int index, uint count, ulong ptr, bool highlight)
|
||||
private bool DrawUnitListHeader(int index, ushort count, ulong ptr, bool highlight)
|
||||
{
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, highlight ? 0xFFAAAA00 : 0xFFFFFFFF);
|
||||
if (!string.IsNullOrEmpty(this.searchInput) && !this.doingSearch)
|
||||
|
|
@ -455,8 +456,6 @@ internal unsafe class UiDebug
|
|||
this.selectedInList[i] = false;
|
||||
var unitManager = &unitManagers[i];
|
||||
|
||||
var unitBaseArray = &unitManager->AtkUnitEntries;
|
||||
|
||||
var headerOpen = true;
|
||||
|
||||
if (!searching)
|
||||
|
|
@ -468,7 +467,7 @@ internal unsafe class UiDebug
|
|||
|
||||
for (var j = 0; j < unitManager->Count && headerOpen; j++)
|
||||
{
|
||||
var unitBase = unitBaseArray[j];
|
||||
var unitBase = *(AtkUnitBase**)Unsafe.AsPointer(ref unitManager->EntriesSpan[j]);
|
||||
if (this.selectedUnitBase != null && unitBase == this.selectedUnitBase)
|
||||
{
|
||||
this.selectedInList[i] = true;
|
||||
|
|
@ -513,7 +512,8 @@ internal unsafe class UiDebug
|
|||
{
|
||||
for (var j = 0; j < unitManager->Count; j++)
|
||||
{
|
||||
if (this.selectedUnitBase == null || unitBaseArray[j] != this.selectedUnitBase) continue;
|
||||
var unitBase = *(AtkUnitBase**)Unsafe.AsPointer(ref unitManager->EntriesSpan[j]);
|
||||
if (this.selectedUnitBase == null || unitBase != this.selectedUnitBase) continue;
|
||||
this.selectedInList[i] = true;
|
||||
foundSelected = true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using System.Linq;
|
|||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
using Dalamud.Configuration.Internal;
|
||||
using Dalamud.Game.Command;
|
||||
|
|
@ -14,6 +15,7 @@ using Dalamud.Interface.Utility;
|
|||
using Dalamud.Interface.Windowing;
|
||||
using Dalamud.Logging.Internal;
|
||||
using Dalamud.Plugin.Internal;
|
||||
using Dalamud.Utility;
|
||||
using ImGuiNET;
|
||||
using Serilog;
|
||||
using Serilog.Events;
|
||||
|
|
@ -28,26 +30,26 @@ internal class ConsoleWindow : Window, IDisposable
|
|||
private readonly List<LogEntry> logText = new();
|
||||
private readonly object renderLock = new();
|
||||
|
||||
private readonly string[] logLevelStrings = new[] { "Verbose", "Debug", "Information", "Warning", "Error", "Fatal" };
|
||||
|
||||
private List<LogEntry> filteredLogText = new();
|
||||
private bool autoScroll;
|
||||
private bool openAtStartup;
|
||||
private readonly List<string> history = new();
|
||||
private readonly List<PluginFilterEntry> pluginFilters = new();
|
||||
|
||||
private bool? lastCmdSuccess;
|
||||
|
||||
private string commandText = string.Empty;
|
||||
|
||||
private string textFilter = string.Empty;
|
||||
private int levelFilter;
|
||||
private List<string> sourceFilters = new();
|
||||
private bool filterShowUncaughtExceptions = false;
|
||||
private bool isFiltered = false;
|
||||
private string selectedSource = "DalamudInternal";
|
||||
|
||||
private bool filterShowUncaughtExceptions;
|
||||
private bool showFilterToolbar;
|
||||
private bool clearLog;
|
||||
private bool copyLog;
|
||||
private bool copyMode;
|
||||
private bool killGameArmed;
|
||||
private bool autoScroll;
|
||||
private bool autoOpen;
|
||||
|
||||
private int historyPos;
|
||||
private List<string> history = new();
|
||||
|
||||
private bool killGameArmed = false;
|
||||
private int copyStart = -1;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConsoleWindow"/> class.
|
||||
|
|
@ -58,16 +60,22 @@ internal class ConsoleWindow : Window, IDisposable
|
|||
var configuration = Service<DalamudConfiguration>.Get();
|
||||
|
||||
this.autoScroll = configuration.LogAutoScroll;
|
||||
this.openAtStartup = configuration.LogOpenAtStartup;
|
||||
this.autoOpen = configuration.LogOpenAtStartup;
|
||||
SerilogEventSink.Instance.LogLine += this.OnLogLine;
|
||||
|
||||
this.Size = new Vector2(500, 400);
|
||||
this.SizeCondition = ImGuiCond.FirstUseEver;
|
||||
|
||||
this.SizeConstraints = new WindowSizeConstraints
|
||||
{
|
||||
MinimumSize = new Vector2(600.0f, 200.0f),
|
||||
MaximumSize = new Vector2(9999.0f, 9999.0f),
|
||||
};
|
||||
|
||||
this.RespectCloseHotkey = false;
|
||||
}
|
||||
|
||||
private List<LogEntry> LogEntries => this.isFiltered ? this.filteredLogText : this.logText;
|
||||
private List<LogEntry> FilteredLogEntries { get; set; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void OnOpen()
|
||||
|
|
@ -92,10 +100,20 @@ internal class ConsoleWindow : Window, IDisposable
|
|||
lock (this.renderLock)
|
||||
{
|
||||
this.logText.Clear();
|
||||
this.filteredLogText.Clear();
|
||||
this.FilteredLogEntries.Clear();
|
||||
this.clearLog = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the entire log contents to clipboard.
|
||||
/// </summary>
|
||||
public void CopyLog()
|
||||
{
|
||||
ImGui.LogToClipboard();
|
||||
this.copyLog = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a single log line to the display.
|
||||
/// </summary>
|
||||
|
|
@ -123,157 +141,15 @@ internal class ConsoleWindow : Window, IDisposable
|
|||
/// <inheritdoc/>
|
||||
public override void Draw()
|
||||
{
|
||||
// Options menu
|
||||
if (ImGui.BeginPopup("Options"))
|
||||
{
|
||||
var configuration = Service<DalamudConfiguration>.Get();
|
||||
this.DrawOptionsToolbar();
|
||||
|
||||
if (ImGui.Checkbox("Auto-scroll", ref this.autoScroll))
|
||||
{
|
||||
configuration.LogAutoScroll = this.autoScroll;
|
||||
configuration.QueueSave();
|
||||
}
|
||||
|
||||
if (ImGui.Checkbox("Open at startup", ref this.openAtStartup))
|
||||
{
|
||||
configuration.LogOpenAtStartup = this.openAtStartup;
|
||||
configuration.QueueSave();
|
||||
}
|
||||
|
||||
var prevLevel = (int)EntryPoint.LogLevelSwitch.MinimumLevel;
|
||||
if (ImGui.Combo("Log Level", ref prevLevel, Enum.GetValues(typeof(LogEventLevel)).Cast<LogEventLevel>().Select(x => x.ToString()).ToArray(), 6))
|
||||
{
|
||||
EntryPoint.LogLevelSwitch.MinimumLevel = (LogEventLevel)prevLevel;
|
||||
configuration.LogLevel = (LogEventLevel)prevLevel;
|
||||
configuration.QueueSave();
|
||||
}
|
||||
|
||||
ImGui.EndPopup();
|
||||
}
|
||||
|
||||
// Filter menu
|
||||
if (ImGui.BeginPopup("Filters"))
|
||||
{
|
||||
if (ImGui.Checkbox("Enabled", ref this.isFiltered))
|
||||
{
|
||||
this.Refilter();
|
||||
}
|
||||
|
||||
if (ImGui.InputTextWithHint("##filterText", "Text Filter", ref this.textFilter, 255, ImGuiInputTextFlags.EnterReturnsTrue))
|
||||
{
|
||||
this.Refilter();
|
||||
}
|
||||
|
||||
ImGui.TextColored(ImGuiColors.DalamudGrey, "Enter to confirm.");
|
||||
|
||||
if (ImGui.BeginCombo("Levels", this.levelFilter == 0 ? "All Levels..." : "Selected Levels..."))
|
||||
{
|
||||
for (var i = 0; i < this.logLevelStrings.Length; i++)
|
||||
{
|
||||
if (ImGui.Selectable(this.logLevelStrings[i], ((this.levelFilter >> i) & 1) == 1))
|
||||
{
|
||||
this.levelFilter ^= 1 << i;
|
||||
this.Refilter();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.EndCombo();
|
||||
}
|
||||
|
||||
// Filter by specific plugin(s)
|
||||
var pluginInternalNames = Service<PluginManager>.Get().InstalledPlugins
|
||||
.Select(p => p.Manifest.InternalName)
|
||||
.OrderBy(s => s).ToList();
|
||||
var sourcePreviewVal = this.sourceFilters.Count switch
|
||||
{
|
||||
0 => "All plugins...",
|
||||
1 => "1 plugin...",
|
||||
_ => $"{this.sourceFilters.Count} plugins...",
|
||||
};
|
||||
var sourceSelectables = pluginInternalNames.Union(this.sourceFilters).ToList();
|
||||
if (ImGui.BeginCombo("Plugins", sourcePreviewVal))
|
||||
{
|
||||
foreach (var selectable in sourceSelectables)
|
||||
{
|
||||
if (ImGui.Selectable(selectable, this.sourceFilters.Contains(selectable)))
|
||||
{
|
||||
if (!this.sourceFilters.Contains(selectable))
|
||||
{
|
||||
this.sourceFilters.Add(selectable);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.sourceFilters.Remove(selectable);
|
||||
}
|
||||
|
||||
this.Refilter();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.EndCombo();
|
||||
}
|
||||
|
||||
if (ImGui.Checkbox("Always Show Uncaught Exceptions", ref this.filterShowUncaughtExceptions))
|
||||
{
|
||||
this.Refilter();
|
||||
}
|
||||
|
||||
ImGui.EndPopup();
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
if (ImGuiComponents.IconButton(FontAwesomeIcon.Cog))
|
||||
ImGui.OpenPopup("Options");
|
||||
|
||||
if (ImGui.IsItemHovered())
|
||||
ImGui.SetTooltip("Options");
|
||||
|
||||
ImGui.SameLine();
|
||||
if (ImGuiComponents.IconButton(FontAwesomeIcon.Search))
|
||||
ImGui.OpenPopup("Filters");
|
||||
|
||||
if (ImGui.IsItemHovered())
|
||||
ImGui.SetTooltip("Filters");
|
||||
|
||||
ImGui.SameLine();
|
||||
var clear = ImGuiComponents.IconButton(FontAwesomeIcon.Trash);
|
||||
|
||||
if (ImGui.IsItemHovered())
|
||||
ImGui.SetTooltip("Clear Log");
|
||||
|
||||
ImGui.SameLine();
|
||||
var copy = ImGuiComponents.IconButton(FontAwesomeIcon.Copy);
|
||||
|
||||
if (ImGui.IsItemHovered())
|
||||
ImGui.SetTooltip("Copy Log");
|
||||
|
||||
ImGui.SameLine();
|
||||
if (this.killGameArmed)
|
||||
{
|
||||
if (ImGuiComponents.IconButton(FontAwesomeIcon.Flushed))
|
||||
Process.GetCurrentProcess().Kill();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ImGuiComponents.IconButton(FontAwesomeIcon.Skull))
|
||||
this.killGameArmed = true;
|
||||
}
|
||||
|
||||
if (ImGui.IsItemHovered())
|
||||
ImGui.SetTooltip("Kill game");
|
||||
this.DrawFilterToolbar();
|
||||
|
||||
ImGui.BeginChild("scrolling", new Vector2(0, ImGui.GetFrameHeightWithSpacing() - 55), false, ImGuiWindowFlags.AlwaysHorizontalScrollbar | ImGuiWindowFlags.AlwaysVerticalScrollbar);
|
||||
|
||||
if (clear)
|
||||
{
|
||||
this.Clear();
|
||||
}
|
||||
if (this.clearLog) this.Clear();
|
||||
|
||||
if (copy)
|
||||
{
|
||||
ImGui.LogToClipboard();
|
||||
}
|
||||
if (this.copyLog) this.CopyLog();
|
||||
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
|
||||
|
||||
|
|
@ -289,27 +165,40 @@ internal class ConsoleWindow : Window, IDisposable
|
|||
var childDrawList = ImGui.GetWindowDrawList();
|
||||
var childSize = ImGui.GetWindowSize();
|
||||
|
||||
var cursorDiv = ImGuiHelpers.GlobalScale * 92;
|
||||
var cursorDiv = ImGuiHelpers.GlobalScale * 93;
|
||||
var cursorLogLevel = ImGuiHelpers.GlobalScale * 100;
|
||||
var cursorLogLine = ImGuiHelpers.GlobalScale * 135;
|
||||
|
||||
lock (this.renderLock)
|
||||
{
|
||||
clipper.Begin(this.LogEntries.Count);
|
||||
clipper.Begin(this.FilteredLogEntries.Count);
|
||||
while (clipper.Step())
|
||||
{
|
||||
for (var i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)
|
||||
{
|
||||
var line = this.LogEntries[i];
|
||||
var line = this.FilteredLogEntries[i];
|
||||
|
||||
if (!line.IsMultiline && !copy)
|
||||
if (!line.IsMultiline && !this.copyLog)
|
||||
ImGui.Separator();
|
||||
|
||||
if (line.SelectedForCopy)
|
||||
{
|
||||
ImGui.PushStyleColor(ImGuiCol.Header, ImGuiColors.ParsedGrey);
|
||||
ImGui.PushStyleColor(ImGuiCol.HeaderActive, ImGuiColors.ParsedGrey);
|
||||
ImGui.PushStyleColor(ImGuiCol.HeaderHovered, ImGuiColors.ParsedGrey);
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui.PushStyleColor(ImGuiCol.Header, this.GetColorForLogEventLevel(line.Level));
|
||||
ImGui.PushStyleColor(ImGuiCol.HeaderActive, this.GetColorForLogEventLevel(line.Level));
|
||||
ImGui.PushStyleColor(ImGuiCol.HeaderHovered, this.GetColorForLogEventLevel(line.Level));
|
||||
}
|
||||
|
||||
ImGui.PushStyleColor(ImGuiCol.Header, this.GetColorForLogEventLevel(line.Level));
|
||||
ImGui.PushStyleColor(ImGuiCol.HeaderActive, this.GetColorForLogEventLevel(line.Level));
|
||||
ImGui.PushStyleColor(ImGuiCol.HeaderHovered, this.GetColorForLogEventLevel(line.Level));
|
||||
ImGui.Selectable("###console_null", true, ImGuiSelectableFlags.AllowItemOverlap | ImGuiSelectableFlags.SpanAllColumns);
|
||||
|
||||
ImGui.Selectable("###consolenull", true, ImGuiSelectableFlags.AllowItemOverlap | ImGuiSelectableFlags.SpanAllColumns);
|
||||
// This must be after ImGui.Selectable, it uses ImGui.IsItem... functions
|
||||
this.HandleCopyMode(i, line);
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
ImGui.PopStyleColor(3);
|
||||
|
|
@ -364,12 +253,12 @@ internal class ConsoleWindow : Window, IDisposable
|
|||
}
|
||||
}
|
||||
|
||||
ImGui.SetNextItemWidth(ImGui.GetWindowSize().X - 80);
|
||||
ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X - (80.0f * ImGuiHelpers.GlobalScale) - (ImGui.GetStyle().ItemSpacing.X * ImGuiHelpers.GlobalScale));
|
||||
|
||||
var getFocus = false;
|
||||
unsafe
|
||||
{
|
||||
if (ImGui.InputText("##commandbox", ref this.commandText, 255, ImGuiInputTextFlags.EnterReturnsTrue | ImGuiInputTextFlags.CallbackCompletion | ImGuiInputTextFlags.CallbackHistory, this.CommandInputCallback))
|
||||
if (ImGui.InputText("##command_box", ref this.commandText, 255, ImGuiInputTextFlags.EnterReturnsTrue | ImGuiInputTextFlags.CallbackCompletion | ImGuiInputTextFlags.CallbackHistory, this.CommandInputCallback))
|
||||
{
|
||||
this.ProcessCommand();
|
||||
getFocus = true;
|
||||
|
|
@ -385,16 +274,279 @@ internal class ConsoleWindow : Window, IDisposable
|
|||
if (hadColor)
|
||||
ImGui.PopStyleColor();
|
||||
|
||||
if (ImGui.Button("Send"))
|
||||
if (ImGui.Button("Send", ImGuiHelpers.ScaledVector2(80.0f, 23.0f)))
|
||||
{
|
||||
this.ProcessCommand();
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleCopyMode(int i, LogEntry line)
|
||||
{
|
||||
var selectionChanged = false;
|
||||
|
||||
// If copyStart is -1, it means a drag has not been started yet, let's start one, and select the starting spot.
|
||||
if (this.copyMode && this.copyStart == -1 && ImGui.IsItemClicked())
|
||||
{
|
||||
this.copyStart = i;
|
||||
line.SelectedForCopy = !line.SelectedForCopy;
|
||||
|
||||
selectionChanged = true;
|
||||
}
|
||||
|
||||
// Update the selected range when dragging over entries
|
||||
if (this.copyMode && this.copyStart != -1 && ImGui.IsItemHovered() && ImGui.IsMouseDragging(ImGuiMouseButton.Left))
|
||||
{
|
||||
if (!line.SelectedForCopy)
|
||||
{
|
||||
foreach (var index in Enumerable.Range(0, this.FilteredLogEntries.Count))
|
||||
{
|
||||
if (this.copyStart < i)
|
||||
{
|
||||
this.FilteredLogEntries[index].SelectedForCopy = index >= this.copyStart && index <= i;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.FilteredLogEntries[index].SelectedForCopy = index >= i && index <= this.copyStart;
|
||||
}
|
||||
}
|
||||
|
||||
selectionChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Finish the drag, we should have already marked all dragged entries as selected by now.
|
||||
if (this.copyMode && this.copyStart != -1 && ImGui.IsItemHovered() && ImGui.IsMouseReleased(ImGuiMouseButton.Left))
|
||||
{
|
||||
this.copyStart = -1;
|
||||
}
|
||||
|
||||
if (selectionChanged)
|
||||
{
|
||||
var allSelectedLines = this.FilteredLogEntries
|
||||
.Where(entry => entry.SelectedForCopy)
|
||||
.Select(entry => $"{line.TimeStamp:HH:mm:ss.fff} {this.GetTextForLogEventLevel(entry.Level)} | {entry.Line}");
|
||||
|
||||
ImGui.SetClipboardText(string.Join("\n", allSelectedLines));
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawOptionsToolbar()
|
||||
{
|
||||
var configuration = Service<DalamudConfiguration>.Get();
|
||||
|
||||
ImGui.PushItemWidth(150.0f * ImGuiHelpers.GlobalScale);
|
||||
if (ImGui.BeginCombo("##log_level", $"{EntryPoint.LogLevelSwitch.MinimumLevel}+"))
|
||||
{
|
||||
foreach (var value in Enum.GetValues<LogEventLevel>())
|
||||
{
|
||||
if (ImGui.Selectable(value.ToString(), value == EntryPoint.LogLevelSwitch.MinimumLevel))
|
||||
{
|
||||
EntryPoint.LogLevelSwitch.MinimumLevel = value;
|
||||
configuration.LogLevel = value;
|
||||
configuration.QueueSave();
|
||||
this.Refilter();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.EndCombo();
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
this.autoScroll = configuration.LogAutoScroll;
|
||||
if (this.DrawToggleButtonWithTooltip("auto_scroll", "Auto-scroll", FontAwesomeIcon.Sync, ref this.autoScroll))
|
||||
{
|
||||
configuration.LogAutoScroll = !configuration.LogAutoScroll;
|
||||
configuration.QueueSave();
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
this.autoOpen = configuration.LogOpenAtStartup;
|
||||
if (this.DrawToggleButtonWithTooltip("auto_open", "Open at startup", FontAwesomeIcon.WindowRestore, ref this.autoOpen))
|
||||
{
|
||||
configuration.LogOpenAtStartup = !configuration.LogOpenAtStartup;
|
||||
configuration.QueueSave();
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
if (this.DrawToggleButtonWithTooltip("show_filters", "Show filter toolbar", FontAwesomeIcon.Search, ref this.showFilterToolbar))
|
||||
{
|
||||
this.showFilterToolbar = !this.showFilterToolbar;
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
if (this.DrawToggleButtonWithTooltip("show_uncaught_exceptions", "Show uncaught exception while filtering", FontAwesomeIcon.Bug, ref this.filterShowUncaughtExceptions))
|
||||
{
|
||||
this.filterShowUncaughtExceptions = !this.filterShowUncaughtExceptions;
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
if (ImGuiComponents.IconButton("clear_log", FontAwesomeIcon.Trash))
|
||||
{
|
||||
this.clearLog = true;
|
||||
}
|
||||
|
||||
if (ImGui.IsItemHovered()) ImGui.SetTooltip("Clear Log");
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
if (this.DrawToggleButtonWithTooltip("copy_mode", "Enable Copy Mode\nRight-click to copy entire log", FontAwesomeIcon.Copy, ref this.copyMode))
|
||||
{
|
||||
this.copyMode = !this.copyMode;
|
||||
|
||||
if (!this.copyMode)
|
||||
{
|
||||
foreach (var entry in this.FilteredLogEntries)
|
||||
{
|
||||
entry.SelectedForCopy = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) this.copyLog = true;
|
||||
|
||||
ImGui.SameLine();
|
||||
if (this.killGameArmed)
|
||||
{
|
||||
if (ImGuiComponents.IconButton(FontAwesomeIcon.ExclamationTriangle))
|
||||
Process.GetCurrentProcess().Kill();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ImGuiComponents.IconButton(FontAwesomeIcon.Stop))
|
||||
this.killGameArmed = true;
|
||||
}
|
||||
|
||||
if (ImGui.IsItemHovered()) ImGui.SetTooltip("Kill game");
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGui.SetCursorPosX(ImGui.GetContentRegionMax().X - (200.0f * ImGuiHelpers.GlobalScale));
|
||||
ImGui.PushItemWidth(200.0f * ImGuiHelpers.GlobalScale);
|
||||
if (ImGui.InputTextWithHint("##global_filter", "regex global filter", ref this.textFilter, 2048, ImGuiInputTextFlags.EnterReturnsTrue | ImGuiInputTextFlags.AutoSelectAll))
|
||||
{
|
||||
this.Refilter();
|
||||
}
|
||||
|
||||
if (ImGui.IsItemDeactivatedAfterEdit())
|
||||
{
|
||||
this.Refilter();
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawFilterToolbar()
|
||||
{
|
||||
if (!this.showFilterToolbar) return;
|
||||
|
||||
PluginFilterEntry? removalEntry = null;
|
||||
if (ImGui.BeginTable("plugin_filter_entries", 4, ImGuiTableFlags.Resizable | ImGuiTableFlags.BordersInnerV))
|
||||
{
|
||||
ImGui.TableSetupColumn("##remove_button", ImGuiTableColumnFlags.WidthFixed, 25.0f * ImGuiHelpers.GlobalScale);
|
||||
ImGui.TableSetupColumn("##source_name", ImGuiTableColumnFlags.WidthFixed, 150.0f * ImGuiHelpers.GlobalScale);
|
||||
ImGui.TableSetupColumn("##log_level", ImGuiTableColumnFlags.WidthFixed, 150.0f * ImGuiHelpers.GlobalScale);
|
||||
ImGui.TableSetupColumn("##filter_text", ImGuiTableColumnFlags.WidthStretch);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
if (ImGuiComponents.IconButton("add_entry", FontAwesomeIcon.Plus))
|
||||
{
|
||||
if (this.pluginFilters.All(entry => entry.Source != this.selectedSource))
|
||||
{
|
||||
this.pluginFilters.Add(new PluginFilterEntry
|
||||
{
|
||||
Source = this.selectedSource,
|
||||
Filter = string.Empty,
|
||||
Level = LogEventLevel.Debug,
|
||||
});
|
||||
}
|
||||
|
||||
this.Refilter();
|
||||
}
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.PushItemWidth(ImGui.GetContentRegionAvail().X);
|
||||
if (ImGui.BeginCombo("##Sources", this.selectedSource))
|
||||
{
|
||||
var sourceNames = Service<PluginManager>.Get().InstalledPlugins
|
||||
.Select(p => p.Manifest.InternalName)
|
||||
.OrderBy(s => s)
|
||||
.Prepend("DalamudInternal")
|
||||
.ToList();
|
||||
|
||||
foreach (var selectable in sourceNames)
|
||||
{
|
||||
if (ImGui.Selectable(selectable, this.selectedSource == selectable))
|
||||
{
|
||||
this.selectedSource = selectable;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.EndCombo();
|
||||
}
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TableNextColumn();
|
||||
|
||||
foreach (var entry in this.pluginFilters)
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
if (ImGuiComponents.IconButton($"remove{entry.Source}", FontAwesomeIcon.Trash))
|
||||
{
|
||||
removalEntry = entry;
|
||||
}
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text(entry.Source);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X);
|
||||
if (ImGui.BeginCombo($"##levels{entry.Source}", $"{entry.Level}+"))
|
||||
{
|
||||
foreach (var value in Enum.GetValues<LogEventLevel>())
|
||||
{
|
||||
if (ImGui.Selectable(value.ToString(), value == entry.Level))
|
||||
{
|
||||
entry.Level = value;
|
||||
this.Refilter();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.EndCombo();
|
||||
}
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X);
|
||||
var entryFilter = entry.Filter;
|
||||
if (ImGui.InputTextWithHint($"##filter{entry.Source}", $"{entry.Source} regex filter", ref entryFilter, 2048, ImGuiInputTextFlags.EnterReturnsTrue | ImGuiInputTextFlags.AutoSelectAll))
|
||||
{
|
||||
entry.Filter = entryFilter;
|
||||
this.Refilter();
|
||||
}
|
||||
|
||||
if (ImGui.IsItemDeactivatedAfterEdit()) this.Refilter();
|
||||
}
|
||||
|
||||
ImGui.EndTable();
|
||||
}
|
||||
|
||||
if (removalEntry is { } toRemove)
|
||||
{
|
||||
this.pluginFilters.Remove(toRemove);
|
||||
this.Refilter();
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessCommand()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (this.commandText is['/', ..])
|
||||
{
|
||||
this.commandText = this.commandText[1..];
|
||||
}
|
||||
|
||||
this.historyPos = -1;
|
||||
for (var i = this.history.Count - 1; i >= 0; i--)
|
||||
{
|
||||
|
|
@ -407,7 +559,7 @@ internal class ConsoleWindow : Window, IDisposable
|
|||
|
||||
this.history.Add(this.commandText);
|
||||
|
||||
if (this.commandText == "clear" || this.commandText == "cls")
|
||||
if (this.commandText is "clear" or "cls")
|
||||
{
|
||||
this.Clear();
|
||||
return;
|
||||
|
|
@ -444,7 +596,9 @@ internal class ConsoleWindow : Window, IDisposable
|
|||
|
||||
// TODO: Improve this, add partial completion
|
||||
// https://github.com/ocornut/imgui/blob/master/imgui_demo.cpp#L6443-L6484
|
||||
var candidates = Service<CommandManager>.Get().Commands.Where(x => x.Key.Contains("/" + words[0])).ToList();
|
||||
var candidates = Service<CommandManager>.Get().Commands
|
||||
.Where(x => x.Key.Contains("/" + words[0]))
|
||||
.ToList();
|
||||
if (candidates.Count > 0)
|
||||
{
|
||||
ptr.DeleteChars(0, ptr.BufTextLen);
|
||||
|
|
@ -452,6 +606,7 @@ internal class ConsoleWindow : Window, IDisposable
|
|||
}
|
||||
|
||||
break;
|
||||
|
||||
case ImGuiInputTextFlags.CallbackHistory:
|
||||
var prevPos = this.historyPos;
|
||||
|
||||
|
|
@ -501,45 +656,63 @@ internal class ConsoleWindow : Window, IDisposable
|
|||
HasException = logEvent.Exception != null,
|
||||
};
|
||||
|
||||
if (logEvent.Properties.TryGetValue("SourceContext", out var sourceProp) &&
|
||||
sourceProp is ScalarValue { Value: string value })
|
||||
// TODO (v9): Remove SourceContext property check.
|
||||
if (logEvent.Properties.ContainsKey("Dalamud.ModuleName"))
|
||||
{
|
||||
entry.Source = value;
|
||||
entry.Source = "DalamudInternal";
|
||||
}
|
||||
else if ((logEvent.Properties.TryGetValue("Dalamud.PluginName", out var sourceProp) ||
|
||||
logEvent.Properties.TryGetValue("SourceContext", out sourceProp)) &&
|
||||
sourceProp is ScalarValue { Value: string sourceValue })
|
||||
{
|
||||
entry.Source = sourceValue;
|
||||
}
|
||||
|
||||
this.logText.Add(entry);
|
||||
|
||||
if (!this.isFiltered)
|
||||
return;
|
||||
|
||||
if (this.IsFilterApplicable(entry))
|
||||
this.filteredLogText.Add(entry);
|
||||
this.FilteredLogEntries.Add(entry);
|
||||
}
|
||||
|
||||
private bool IsFilterApplicable(LogEntry entry)
|
||||
{
|
||||
if (this.levelFilter > 0 && ((this.levelFilter >> (int)entry.Level) & 1) == 0)
|
||||
// If this entry is below a newly set minimum level, fail it
|
||||
if (EntryPoint.LogLevelSwitch.MinimumLevel > entry.Level)
|
||||
return false;
|
||||
|
||||
|
||||
// Show exceptions that weren't properly tagged with a Source (generally meaning they were uncaught)
|
||||
// After log levels because uncaught exceptions should *never* fall below Error.
|
||||
if (this.filterShowUncaughtExceptions && entry.HasException && entry.Source == null)
|
||||
return true;
|
||||
|
||||
if (this.sourceFilters.Count > 0 && !this.sourceFilters.Contains(entry.Source))
|
||||
return false;
|
||||
// If we have a global filter, check that first
|
||||
if (!this.textFilter.IsNullOrEmpty())
|
||||
{
|
||||
// Someone will definitely try to just text filter a source without using the actual filters, should allow that.
|
||||
var matchesSource = entry.Source is not null && Regex.IsMatch(entry.Source, this.textFilter, RegexOptions.IgnoreCase);
|
||||
var matchesContent = Regex.IsMatch(entry.Line, this.textFilter, RegexOptions.IgnoreCase);
|
||||
|
||||
if (!string.IsNullOrEmpty(this.textFilter) && !entry.Line.Contains(this.textFilter))
|
||||
return false;
|
||||
return matchesSource || matchesContent;
|
||||
}
|
||||
|
||||
return true;
|
||||
// If this entry has a filter, check the filter
|
||||
if (this.pluginFilters.FirstOrDefault(filter => string.Equals(filter.Source, entry.Source, StringComparison.InvariantCultureIgnoreCase)) is { } filterEntry)
|
||||
{
|
||||
var allowedLevel = filterEntry.Level <= entry.Level;
|
||||
var matchesContent = filterEntry.Filter.IsNullOrEmpty() || Regex.IsMatch(entry.Line, filterEntry.Filter, RegexOptions.IgnoreCase);
|
||||
|
||||
return allowedLevel && matchesContent;
|
||||
}
|
||||
|
||||
// else we couldn't find a filter for this entry, if we have any filters, we need to block this entry.
|
||||
return !this.pluginFilters.Any();
|
||||
}
|
||||
|
||||
private void Refilter()
|
||||
{
|
||||
lock (this.renderLock)
|
||||
{
|
||||
this.filteredLogText = this.logText.Where(this.IsFilterApplicable).ToList();
|
||||
this.FilteredLogEntries = this.logText.Where(this.IsFilterApplicable).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -570,18 +743,51 @@ internal class ConsoleWindow : Window, IDisposable
|
|||
this.HandleLogLine(logEvent.Line, logEvent.LogEvent);
|
||||
}
|
||||
|
||||
private bool DrawToggleButtonWithTooltip(string buttonId, string tooltip, FontAwesomeIcon icon, ref bool enabledState)
|
||||
{
|
||||
var result = false;
|
||||
|
||||
var buttonEnabled = enabledState;
|
||||
if (buttonEnabled) ImGui.PushStyleColor(ImGuiCol.Button, ImGuiColors.HealerGreen with { W = 0.25f });
|
||||
if (ImGuiComponents.IconButton(buttonId, icon))
|
||||
{
|
||||
result = true;
|
||||
}
|
||||
|
||||
if (ImGui.IsItemHovered()) ImGui.SetTooltip(tooltip);
|
||||
|
||||
if (buttonEnabled) ImGui.PopStyleColor();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private class LogEntry
|
||||
{
|
||||
public string Line { get; set; }
|
||||
public string Line { get; init; } = string.Empty;
|
||||
|
||||
public LogEventLevel Level { get; set; }
|
||||
public LogEventLevel Level { get; init; }
|
||||
|
||||
public DateTimeOffset TimeStamp { get; set; }
|
||||
public DateTimeOffset TimeStamp { get; init; }
|
||||
|
||||
public bool IsMultiline { get; set; }
|
||||
public bool IsMultiline { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the system responsible for generating this log entry. Generally will be a plugin's
|
||||
/// InternalName.
|
||||
/// </summary>
|
||||
public string? Source { get; set; }
|
||||
|
||||
public bool SelectedForCopy { get; set; }
|
||||
|
||||
public bool HasException { get; set; }
|
||||
public bool HasException { get; init; }
|
||||
}
|
||||
|
||||
private class PluginFilterEntry
|
||||
{
|
||||
public string Source { get; init; } = string.Empty;
|
||||
|
||||
public string Filter { get; set; } = string.Empty;
|
||||
|
||||
public LogEventLevel Level { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,163 +0,0 @@
|
|||
// ReSharper disable InconsistentNaming // Naming is suppressed so we can replace '_' with ' '
|
||||
namespace Dalamud.Interface.Internal.Windows;
|
||||
|
||||
/// <summary>
|
||||
/// Enum representing a DataKind for the Data Window.
|
||||
/// </summary>
|
||||
internal enum DataKind
|
||||
{
|
||||
/// <summary>
|
||||
/// Server Opcode Display.
|
||||
/// </summary>
|
||||
Server_OpCode,
|
||||
|
||||
/// <summary>
|
||||
/// Address.
|
||||
/// </summary>
|
||||
Address,
|
||||
|
||||
/// <summary>
|
||||
/// Object Table.
|
||||
/// </summary>
|
||||
Object_Table,
|
||||
|
||||
/// <summary>
|
||||
/// Fate Table.
|
||||
/// </summary>
|
||||
Fate_Table,
|
||||
|
||||
/// <summary>
|
||||
/// SE Font Test.
|
||||
/// </summary>
|
||||
SE_Font_Test,
|
||||
|
||||
/// <summary>
|
||||
/// FontAwesome Test.
|
||||
/// </summary>
|
||||
FontAwesome_Test,
|
||||
|
||||
/// <summary>
|
||||
/// Party List.
|
||||
/// </summary>
|
||||
Party_List,
|
||||
|
||||
/// <summary>
|
||||
/// Buddy List.
|
||||
/// </summary>
|
||||
Buddy_List,
|
||||
|
||||
/// <summary>
|
||||
/// Plugin IPC Test.
|
||||
/// </summary>
|
||||
Plugin_IPC,
|
||||
|
||||
/// <summary>
|
||||
/// Player Condition.
|
||||
/// </summary>
|
||||
Condition,
|
||||
|
||||
/// <summary>
|
||||
/// Gauge.
|
||||
/// </summary>
|
||||
Gauge,
|
||||
|
||||
/// <summary>
|
||||
/// Command.
|
||||
/// </summary>
|
||||
Command,
|
||||
|
||||
/// <summary>
|
||||
/// Addon.
|
||||
/// </summary>
|
||||
Addon,
|
||||
|
||||
/// <summary>
|
||||
/// Addon Inspector.
|
||||
/// </summary>
|
||||
Addon_Inspector,
|
||||
|
||||
/// <summary>
|
||||
/// AtkArrayData Browser.
|
||||
/// </summary>
|
||||
AtkArrayData_Browser,
|
||||
|
||||
/// <summary>
|
||||
/// StartInfo.
|
||||
/// </summary>
|
||||
StartInfo,
|
||||
|
||||
/// <summary>
|
||||
/// Target.
|
||||
/// </summary>
|
||||
Target,
|
||||
|
||||
/// <summary>
|
||||
/// Toast.
|
||||
/// </summary>
|
||||
Toast,
|
||||
|
||||
/// <summary>
|
||||
/// Fly Text.
|
||||
/// </summary>
|
||||
FlyText,
|
||||
|
||||
/// <summary>
|
||||
/// ImGui.
|
||||
/// </summary>
|
||||
ImGui,
|
||||
|
||||
/// <summary>
|
||||
/// Tex.
|
||||
/// </summary>
|
||||
Tex,
|
||||
|
||||
/// <summary>
|
||||
/// KeyState.
|
||||
/// </summary>
|
||||
KeyState,
|
||||
|
||||
/// <summary>
|
||||
/// GamePad.
|
||||
/// </summary>
|
||||
Gamepad,
|
||||
|
||||
/// <summary>
|
||||
/// Configuration.
|
||||
/// </summary>
|
||||
Configuration,
|
||||
|
||||
/// <summary>
|
||||
/// Task Scheduler.
|
||||
/// </summary>
|
||||
TaskSched,
|
||||
|
||||
/// <summary>
|
||||
/// Hook.
|
||||
/// </summary>
|
||||
Hook,
|
||||
|
||||
/// <summary>
|
||||
/// Aetherytes.
|
||||
/// </summary>
|
||||
Aetherytes,
|
||||
|
||||
/// <summary>
|
||||
/// DTR Bar.
|
||||
/// </summary>
|
||||
Dtr_Bar,
|
||||
|
||||
/// <summary>
|
||||
/// UIColor.
|
||||
/// </summary>
|
||||
UIColor,
|
||||
|
||||
/// <summary>
|
||||
/// Data Share.
|
||||
/// </summary>
|
||||
Data_Share,
|
||||
|
||||
/// <summary>
|
||||
/// Network Monitor.
|
||||
/// </summary>
|
||||
Network_Monitor,
|
||||
}
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
|
||||
|
|
@ -20,7 +19,7 @@ internal class DataWindow : Window
|
|||
{
|
||||
private readonly IDataWindowWidget[] modules =
|
||||
{
|
||||
new ServerOpcodeWidget(),
|
||||
new ServicesWidget(),
|
||||
new AddressesWidget(),
|
||||
new ObjectTableWidget(),
|
||||
new FateTableWidget(),
|
||||
|
|
@ -53,26 +52,24 @@ internal class DataWindow : Window
|
|||
new NetworkMonitorWidget(),
|
||||
};
|
||||
|
||||
private readonly Dictionary<DataKind, string> dataKindNames = new();
|
||||
private readonly IOrderedEnumerable<IDataWindowWidget> orderedModules;
|
||||
|
||||
private bool isExcept;
|
||||
private DataKind currentKind;
|
||||
|
||||
private bool selectionCollapsed;
|
||||
private IDataWindowWidget currentWidget;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DataWindow"/> class.
|
||||
/// </summary>
|
||||
public DataWindow()
|
||||
: base("Dalamud Data")
|
||||
: base("Dalamud Data", ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse)
|
||||
{
|
||||
this.Size = new Vector2(500, 500);
|
||||
this.Size = new Vector2(400, 300);
|
||||
this.SizeCondition = ImGuiCond.FirstUseEver;
|
||||
|
||||
this.RespectCloseHotkey = false;
|
||||
|
||||
foreach (var dataKind in Enum.GetValues<DataKind>())
|
||||
{
|
||||
this.dataKindNames[dataKind] = dataKind.ToString().Replace("_", " ");
|
||||
}
|
||||
this.orderedModules = this.modules.OrderBy(module => module.DisplayName);
|
||||
this.currentWidget = this.orderedModules.First();
|
||||
|
||||
this.Load();
|
||||
}
|
||||
|
|
@ -96,24 +93,9 @@ internal class DataWindow : Window
|
|||
if (string.IsNullOrEmpty(dataKind))
|
||||
return;
|
||||
|
||||
dataKind = dataKind switch
|
||||
if (this.modules.FirstOrDefault(module => module.IsWidgetCommand(dataKind)) is { } targetModule)
|
||||
{
|
||||
"ai" => "Addon Inspector",
|
||||
"at" => "Object Table", // Actor Table
|
||||
"ot" => "Object Table",
|
||||
"uic" => "UIColor",
|
||||
_ => dataKind,
|
||||
};
|
||||
|
||||
dataKind = dataKind.Replace(" ", string.Empty).ToLower();
|
||||
|
||||
var matched = Enum
|
||||
.GetValues<DataKind>()
|
||||
.FirstOrDefault(kind => Enum.GetName(kind)?.Replace("_", string.Empty).ToLower() == dataKind);
|
||||
|
||||
if (matched != default)
|
||||
{
|
||||
this.currentKind = matched;
|
||||
this.currentWidget = targetModule;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -126,59 +108,113 @@ internal class DataWindow : Window
|
|||
/// </summary>
|
||||
public override void Draw()
|
||||
{
|
||||
if (ImGuiComponents.IconButton("forceReload", FontAwesomeIcon.Sync)) this.Load();
|
||||
if (ImGui.IsItemHovered()) ImGui.SetTooltip("Force Reload");
|
||||
ImGui.SameLine();
|
||||
var copy = ImGuiComponents.IconButton("copyAll", FontAwesomeIcon.ClipboardList);
|
||||
if (ImGui.IsItemHovered()) ImGui.SetTooltip("Copy All");
|
||||
ImGui.SameLine();
|
||||
|
||||
ImGui.SetNextItemWidth(275.0f * ImGuiHelpers.GlobalScale);
|
||||
if (ImGui.BeginCombo("Data Kind", this.dataKindNames[this.currentKind]))
|
||||
// Only draw the widget contents if the selection pane is collapsed.
|
||||
if (this.selectionCollapsed)
|
||||
{
|
||||
foreach (var module in this.modules.OrderBy(module => this.dataKindNames[module.DataKind]))
|
||||
this.DrawContents();
|
||||
return;
|
||||
}
|
||||
|
||||
if (ImGui.BeginTable("XlData_Table", 2, ImGuiTableFlags.BordersInnerV | ImGuiTableFlags.Resizable))
|
||||
{
|
||||
ImGui.TableSetupColumn("##SelectionColumn", ImGuiTableColumnFlags.WidthFixed, 200.0f * ImGuiHelpers.GlobalScale);
|
||||
ImGui.TableSetupColumn("##ContentsColumn", ImGuiTableColumnFlags.WidthStretch);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
this.DrawSelection();
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
this.DrawContents();
|
||||
|
||||
ImGui.EndTable();
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawSelection()
|
||||
{
|
||||
if (ImGui.BeginChild("XlData_SelectionPane", ImGui.GetContentRegionAvail()))
|
||||
{
|
||||
if (ImGui.BeginListBox("WidgetSelectionListbox", ImGui.GetContentRegionAvail()))
|
||||
{
|
||||
if (ImGui.Selectable(this.dataKindNames[module.DataKind], this.currentKind == module.DataKind))
|
||||
foreach (var widget in this.orderedModules)
|
||||
{
|
||||
this.currentKind = module.DataKind;
|
||||
if (ImGui.Selectable(widget.DisplayName, this.currentWidget == widget))
|
||||
{
|
||||
this.currentWidget = widget;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.EndListBox();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.EndChild();
|
||||
}
|
||||
|
||||
private void DrawContents()
|
||||
{
|
||||
if (ImGui.BeginChild("XlData_ContentsPane", ImGui.GetContentRegionAvail()))
|
||||
{
|
||||
if (ImGuiComponents.IconButton("collapse-expand", this.selectionCollapsed ? FontAwesomeIcon.ArrowRight : FontAwesomeIcon.ArrowLeft))
|
||||
{
|
||||
this.selectionCollapsed = !this.selectionCollapsed;
|
||||
}
|
||||
|
||||
if (ImGui.IsItemHovered())
|
||||
{
|
||||
ImGui.SetTooltip($"{(this.selectionCollapsed ? "Expand" : "Collapse")} selection pane");
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
if (ImGuiComponents.IconButton("forceReload", FontAwesomeIcon.Sync))
|
||||
{
|
||||
this.Load();
|
||||
}
|
||||
|
||||
if (ImGui.IsItemHovered())
|
||||
{
|
||||
ImGui.SetTooltip("Force Reload");
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
var copy = ImGuiComponents.IconButton("copyAll", FontAwesomeIcon.ClipboardList);
|
||||
|
||||
ImGuiHelpers.ScaledDummy(10.0f);
|
||||
|
||||
if (ImGui.BeginChild("XlData_WidgetContents", ImGui.GetContentRegionAvail()))
|
||||
{
|
||||
if (copy)
|
||||
ImGui.LogToClipboard();
|
||||
|
||||
try
|
||||
{
|
||||
if (this.currentWidget is { Ready: true })
|
||||
{
|
||||
this.currentWidget.Draw();
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui.TextUnformatted("Data not ready.");
|
||||
}
|
||||
|
||||
this.isExcept = false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (!this.isExcept)
|
||||
{
|
||||
Log.Error(ex, "Could not draw data");
|
||||
}
|
||||
|
||||
this.isExcept = true;
|
||||
|
||||
ImGui.TextUnformatted(ex.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.EndCombo();
|
||||
}
|
||||
|
||||
ImGuiHelpers.ScaledDummy(10.0f);
|
||||
|
||||
ImGui.BeginChild("scrolling", Vector2.Zero, false, ImGuiWindowFlags.HorizontalScrollbar);
|
||||
|
||||
if (copy)
|
||||
ImGui.LogToClipboard();
|
||||
|
||||
try
|
||||
{
|
||||
var selectedWidget = this.modules.FirstOrDefault(dataWindowWidget => dataWindowWidget.DataKind == this.currentKind);
|
||||
|
||||
if (selectedWidget is { Ready: true })
|
||||
{
|
||||
selectedWidget.Draw();
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui.TextUnformatted("Data not ready.");
|
||||
}
|
||||
|
||||
this.isExcept = false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (!this.isExcept)
|
||||
{
|
||||
Log.Error(ex, "Could not draw data");
|
||||
}
|
||||
|
||||
this.isExcept = true;
|
||||
|
||||
ImGui.TextUnformatted(ex.ToString());
|
||||
ImGui.EndChild();
|
||||
}
|
||||
|
||||
ImGui.EndChild();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
namespace Dalamud.Interface.Internal.Windows;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Dalamud.Interface.Internal.Windows;
|
||||
|
||||
/// <summary>
|
||||
/// Class representing a date window entry.
|
||||
|
|
@ -6,9 +9,14 @@
|
|||
internal interface IDataWindowWidget
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the Data Kind for this data window module.
|
||||
/// Gets the command strings that can be used to open the data window directly to this module.
|
||||
/// </summary>
|
||||
DataKind DataKind { get; init; }
|
||||
string[]? CommandShortcuts { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the display name for this module.
|
||||
/// </summary>
|
||||
string DisplayName { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this data window module is ready.
|
||||
|
|
@ -24,4 +32,11 @@ internal interface IDataWindowWidget
|
|||
/// Draws this data window module.
|
||||
/// </summary>
|
||||
void Draw();
|
||||
|
||||
/// <summary>
|
||||
/// Helper method to check if this widget should be activated by the input command.
|
||||
/// </summary>
|
||||
/// <param name="command">The command being run.</param>
|
||||
/// <returns>true if this module should be activated by the input command.</returns>
|
||||
bool IsWidgetCommand(string command) => this.CommandShortcuts?.Any(shortcut => string.Equals(shortcut, command, StringComparison.InvariantCultureIgnoreCase)) ?? false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,10 @@ internal class AddonInspectorWidget : IDataWindowWidget
|
|||
private UiDebug? addonInspector;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public DataKind DataKind { get; init; } = DataKind.Addon_Inspector;
|
||||
public string[]? CommandShortcuts { get; init; } = { "ai", "addoninspector" };
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string DisplayName { get; init; } = "Addon Inspector";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Ready { get; set; }
|
||||
|
|
|
|||
|
|
@ -15,7 +15,10 @@ internal unsafe class AddonWidget : IDataWindowWidget
|
|||
private nint findAgentInterfacePtr;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public DataKind DataKind { get; init; } = DataKind.Addon;
|
||||
public string DisplayName { get; init; } = "Addon";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string[]? CommandShortcuts { get; init; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Ready { get; set; }
|
||||
|
|
|
|||
|
|
@ -14,7 +14,10 @@ internal class AddressesWidget : IDataWindowWidget
|
|||
private nint sigResult = nint.Zero;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public DataKind DataKind { get; init; } = DataKind.Address;
|
||||
public string[]? CommandShortcuts { get; init; } = { "address" };
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string DisplayName { get; init; } = "Addresses";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Ready { get; set; }
|
||||
|
|
|
|||
|
|
@ -9,10 +9,13 @@ namespace Dalamud.Interface.Internal.Windows.Data.Widgets;
|
|||
internal class AetherytesWidget : IDataWindowWidget
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public DataKind DataKind { get; init; } = DataKind.Aetherytes;
|
||||
public bool Ready { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Ready { get; set; }
|
||||
public string[]? CommandShortcuts { get; init; } = { "aetherytes" };
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string DisplayName { get; init; } = "Aetherytes";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Load()
|
||||
|
|
|
|||
|
|
@ -11,10 +11,13 @@ namespace Dalamud.Interface.Internal.Windows.Data.Widgets;
|
|||
internal unsafe class AtkArrayDataBrowserWidget : IDataWindowWidget
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public DataKind DataKind { get; init; } = DataKind.AtkArrayData_Browser;
|
||||
public bool Ready { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Ready { get; set; }
|
||||
public string[]? CommandShortcuts { get; init; } = { "atkarray" };
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string DisplayName { get; init; } = "Atk Array Data";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Load()
|
||||
|
|
|
|||
|
|
@ -12,10 +12,13 @@ internal class BuddyListWidget : IDataWindowWidget
|
|||
private bool resolveGameData;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public DataKind DataKind { get; init; } = DataKind.Buddy_List;
|
||||
public bool Ready { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Ready { get; set; }
|
||||
public string[]? CommandShortcuts { get; init; } = { "buddy", "buddylist" };
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string DisplayName { get; init; } = "Buddy List";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Load()
|
||||
|
|
|
|||
|
|
@ -9,7 +9,10 @@ namespace Dalamud.Interface.Internal.Windows.Data.Widgets;
|
|||
internal class CommandWidget : IDataWindowWidget
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public DataKind DataKind { get; init; } = DataKind.Command;
|
||||
public string[]? CommandShortcuts { get; init; } = { "command" };
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string DisplayName { get; init; } = "Command";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Ready { get; set; }
|
||||
|
|
|
|||
|
|
@ -9,10 +9,13 @@ namespace Dalamud.Interface.Internal.Windows.Data.Widgets;
|
|||
internal class ConditionWidget : IDataWindowWidget
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public DataKind DataKind { get; init; } = DataKind.Condition;
|
||||
public bool Ready { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Ready { get; set; }
|
||||
public string[]? CommandShortcuts { get; init; } = { "condition" };
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string DisplayName { get; init; } = "Condition";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Load()
|
||||
|
|
|
|||
|
|
@ -9,7 +9,10 @@ namespace Dalamud.Interface.Internal.Windows.Data.Widgets;
|
|||
internal class ConfigurationWidget : IDataWindowWidget
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public DataKind DataKind { get; init; } = DataKind.Configuration;
|
||||
public string[]? CommandShortcuts { get; init; } = { "config", "configuration" };
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string DisplayName { get; init; } = "Configuration";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Ready { get; set; }
|
||||
|
|
|
|||
|
|
@ -10,7 +10,10 @@ namespace Dalamud.Interface.Internal.Windows.Data.Widgets;
|
|||
internal class DataShareWidget : IDataWindowWidget
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public DataKind DataKind { get; init; } = DataKind.Data_Share;
|
||||
public string[]? CommandShortcuts { get; init; } = { "datashare" };
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string DisplayName { get; init; } = "Data Share";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Ready { get; set; }
|
||||
|
|
|
|||
|
|
@ -14,7 +14,10 @@ internal class DtrBarWidget : IDataWindowWidget
|
|||
private DtrBarEntry? dtrTest3;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public DataKind DataKind { get; init; } = DataKind.Dtr_Bar;
|
||||
public string[]? CommandShortcuts { get; init; } = { "dtr", "dtrbar" };
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string DisplayName { get; init; } = "DTR Bar";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Ready { get; set; }
|
||||
|
|
|
|||
|
|
@ -11,7 +11,10 @@ internal class FateTableWidget : IDataWindowWidget
|
|||
private bool resolveGameData;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public DataKind DataKind { get; init; } = DataKind.Fate_Table;
|
||||
public string[]? CommandShortcuts { get; init; } = { "fate", "fatetable" };
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string DisplayName { get; init; } = "Fate Table";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Ready { get; set; }
|
||||
|
|
|
|||
|
|
@ -21,7 +21,10 @@ internal class FlyTextWidget : IDataWindowWidget
|
|||
private Vector4 flyColor = new(1, 0, 0, 1);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public DataKind DataKind { get; init; } = DataKind.FlyText;
|
||||
public string[]? CommandShortcuts { get; init; } = { "flytext" };
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string DisplayName { get; init; } = "Fly Text";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Ready { get; set; }
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue