mirror of
https://github.com/goatcorp/Dalamud.git
synced 2026-02-25 22:21:49 +01:00
commit
725a2efa8e
30 changed files with 890 additions and 161 deletions
|
|
@ -61,6 +61,11 @@ public enum AddonEventType : byte
|
||||||
/// </summary>
|
/// </summary>
|
||||||
InputBaseInputReceived = 15,
|
InputBaseInputReceived = 15,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fired at the very beginning of AtkInputManager.HandleInput on AtkStage.ViewportEventManager. Used in LovmMiniMap.
|
||||||
|
/// </summary>
|
||||||
|
RawInputData = 16,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Focus Start.
|
/// Focus Start.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -107,7 +112,12 @@ public enum AddonEventType : byte
|
||||||
SliderReleased = 30,
|
SliderReleased = 30,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// AtkComponentList RollOver.
|
/// AtkComponentList Button Press.
|
||||||
|
/// </summary>
|
||||||
|
ListButtonPress = 31,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// AtkComponentList Roll Over.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ListItemRollOver = 33,
|
ListItemRollOver = 33,
|
||||||
|
|
||||||
|
|
@ -126,11 +136,31 @@ public enum AddonEventType : byte
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ListItemDoubleClick = 36,
|
ListItemDoubleClick = 36,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// AtkComponentList Highlight.
|
||||||
|
/// </summary>
|
||||||
|
ListItemHighlight = 37,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// AtkComponentList Select.
|
/// AtkComponentList Select.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ListItemSelect = 38,
|
ListItemSelect = 38,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// AtkComponentList Pad Drag Drop Begin.
|
||||||
|
/// </summary>
|
||||||
|
ListItemPadDragDropBegin = 40,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// AtkComponentList Pad Drag Drop End.
|
||||||
|
/// </summary>
|
||||||
|
ListItemPadDragDropEnd = 41,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// AtkComponentList Pad Drag Drop Insert.
|
||||||
|
/// </summary>
|
||||||
|
ListItemPadDragDropInsert = 42,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// AtkComponentDragDrop Begin.
|
/// AtkComponentDragDrop Begin.
|
||||||
/// Sent on MouseDown over a draggable icon (will NOT send for a locked icon).
|
/// Sent on MouseDown over a draggable icon (will NOT send for a locked icon).
|
||||||
|
|
@ -142,12 +172,22 @@ public enum AddonEventType : byte
|
||||||
/// </summary>
|
/// </summary>
|
||||||
DragDropEnd = 51,
|
DragDropEnd = 51,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// AtkComponentDragDrop Insert Attempt.
|
||||||
|
/// </summary>
|
||||||
|
DragDropInsertAttempt = 52,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// AtkComponentDragDrop Insert.
|
/// AtkComponentDragDrop Insert.
|
||||||
/// Sent when dropping an icon into a hotbar/inventory slot or similar.
|
/// Sent when dropping an icon into a hotbar/inventory slot or similar.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
DragDropInsert = 53,
|
DragDropInsert = 53,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// AtkComponentDragDrop Can Accept Check.
|
||||||
|
/// </summary>
|
||||||
|
DragDropCanAcceptCheck = 54,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// AtkComponentDragDrop Roll Over.
|
/// AtkComponentDragDrop Roll Over.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -165,23 +205,18 @@ public enum AddonEventType : byte
|
||||||
DragDropDiscard = 57,
|
DragDropDiscard = 57,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Drag Drop Unknown.
|
/// AtkComponentDragDrop Click.
|
||||||
|
/// Sent on MouseUp if the cursor has not moved since DragDropBegin, OR on MouseDown over a locked icon.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Obsolete("Use DragDropDiscard", true)]
|
DragDropClick = 58,
|
||||||
DragDropUnk54 = 54,
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// AtkComponentDragDrop Cancel.
|
/// AtkComponentDragDrop Cancel.
|
||||||
/// Sent on MouseUp if the cursor has not moved since DragDropBegin, OR on MouseDown over a locked icon.
|
/// Sent on MouseUp if the cursor has not moved since DragDropBegin, OR on MouseDown over a locked icon.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Obsolete("Renamed to DragDropClick")]
|
||||||
DragDropCancel = 58,
|
DragDropCancel = 58,
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Drag Drop Unknown.
|
|
||||||
/// </summary>
|
|
||||||
[Obsolete("Use DragDropCancel", true)]
|
|
||||||
DragDropUnk55 = 55,
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// AtkComponentIconText Roll Over.
|
/// AtkComponentIconText Roll Over.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -217,6 +252,11 @@ public enum AddonEventType : byte
|
||||||
/// </summary>
|
/// </summary>
|
||||||
TimerEnd = 65,
|
TimerEnd = 65,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// AtkTimer Start.
|
||||||
|
/// </summary>
|
||||||
|
TimerStart = 66,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// AtkSimpleTween Progress.
|
/// AtkSimpleTween Progress.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -247,6 +287,11 @@ public enum AddonEventType : byte
|
||||||
/// </summary>
|
/// </summary>
|
||||||
WindowChangeScale = 72,
|
WindowChangeScale = 72,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// AtkTimeline Active Label Changed.
|
||||||
|
/// </summary>
|
||||||
|
TimelineActiveLabelChanged = 75,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// AtkTextNode Link Mouse Click.
|
/// AtkTextNode Link Mouse Click.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,11 @@ internal unsafe class AddonLifecycle : IInternalDisposableService
|
||||||
|
|
||||||
private static readonly ModuleLog Log = ModuleLog.Create<AddonLifecycle>();
|
private static readonly ModuleLog Log = ModuleLog.Create<AddonLifecycle>();
|
||||||
|
|
||||||
|
[ServiceManager.ServiceDependency]
|
||||||
|
private readonly Framework framework = Service<Framework>.Get();
|
||||||
|
|
||||||
private Hook<AtkUnitBase.Delegates.Initialize>? onInitializeAddonHook;
|
private Hook<AtkUnitBase.Delegates.Initialize>? onInitializeAddonHook;
|
||||||
|
private bool isInvokingListeners = false;
|
||||||
|
|
||||||
[ServiceManager.ServiceConstructor]
|
[ServiceManager.ServiceConstructor]
|
||||||
private AddonLifecycle()
|
private AddonLifecycle()
|
||||||
|
|
@ -58,20 +62,23 @@ internal unsafe class AddonLifecycle : IInternalDisposableService
|
||||||
/// <param name="listener">The listener to register.</param>
|
/// <param name="listener">The listener to register.</param>
|
||||||
internal void RegisterListener(AddonLifecycleEventListener listener)
|
internal void RegisterListener(AddonLifecycleEventListener listener)
|
||||||
{
|
{
|
||||||
if (!this.EventListeners.ContainsKey(listener.EventType))
|
this.framework.RunOnTick(() =>
|
||||||
{
|
{
|
||||||
if (!this.EventListeners.TryAdd(listener.EventType, []))
|
if (!this.EventListeners.ContainsKey(listener.EventType))
|
||||||
return;
|
{
|
||||||
}
|
if (!this.EventListeners.TryAdd(listener.EventType, []))
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Note: string.Empty is a valid addon name, as that will trigger on any addon for this event type
|
// Note: string.Empty is a valid addon name, as that will trigger on any addon for this event type
|
||||||
if (!this.EventListeners[listener.EventType].ContainsKey(listener.AddonName))
|
if (!this.EventListeners[listener.EventType].ContainsKey(listener.AddonName))
|
||||||
{
|
{
|
||||||
if (!this.EventListeners[listener.EventType].TryAdd(listener.AddonName, []))
|
if (!this.EventListeners[listener.EventType].TryAdd(listener.AddonName, []))
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.EventListeners[listener.EventType][listener.AddonName].Add(listener);
|
this.EventListeners[listener.EventType][listener.AddonName].Add(listener);
|
||||||
|
}, delayTicks: this.isInvokingListeners ? 1 : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -80,13 +87,16 @@ internal unsafe class AddonLifecycle : IInternalDisposableService
|
||||||
/// <param name="listener">The listener to unregister.</param>
|
/// <param name="listener">The listener to unregister.</param>
|
||||||
internal void UnregisterListener(AddonLifecycleEventListener listener)
|
internal void UnregisterListener(AddonLifecycleEventListener listener)
|
||||||
{
|
{
|
||||||
if (this.EventListeners.TryGetValue(listener.EventType, out var addonListeners))
|
this.framework.RunOnTick(() =>
|
||||||
{
|
{
|
||||||
if (addonListeners.TryGetValue(listener.AddonName, out var addonListener))
|
if (this.EventListeners.TryGetValue(listener.EventType, out var addonListeners))
|
||||||
{
|
{
|
||||||
addonListener.Remove(listener);
|
if (addonListeners.TryGetValue(listener.AddonName, out var addonListener))
|
||||||
|
{
|
||||||
|
addonListener.Remove(listener);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}, delayTicks: this.isInvokingListeners ? 1 : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -97,6 +107,8 @@ internal unsafe class AddonLifecycle : IInternalDisposableService
|
||||||
/// <param name="blame">What to blame on errors.</param>
|
/// <param name="blame">What to blame on errors.</param>
|
||||||
internal void InvokeListenersSafely(AddonEvent eventType, AddonArgs args, [CallerMemberName] string blame = "")
|
internal void InvokeListenersSafely(AddonEvent eventType, AddonArgs args, [CallerMemberName] string blame = "")
|
||||||
{
|
{
|
||||||
|
this.isInvokingListeners = true;
|
||||||
|
|
||||||
// Early return if we don't have any listeners of this type
|
// Early return if we don't have any listeners of this type
|
||||||
if (!this.EventListeners.TryGetValue(eventType, out var addonListeners)) return;
|
if (!this.EventListeners.TryGetValue(eventType, out var addonListeners)) return;
|
||||||
|
|
||||||
|
|
@ -131,6 +143,8 @@ internal unsafe class AddonLifecycle : IInternalDisposableService
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.isInvokingListeners = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
221
Dalamud/Game/Chat/LogMessage.cs
Normal file
221
Dalamud/Game/Chat/LogMessage.cs
Normal file
|
|
@ -0,0 +1,221 @@
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
|
using Dalamud.Data;
|
||||||
|
using Dalamud.Utility;
|
||||||
|
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.System.String;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
|
||||||
|
using FFXIVClientStructs.FFXIV.Component.Text;
|
||||||
|
using FFXIVClientStructs.Interop;
|
||||||
|
|
||||||
|
using Lumina.Excel;
|
||||||
|
using Lumina.Text.ReadOnly;
|
||||||
|
|
||||||
|
namespace Dalamud.Game.Chat;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Interface representing a log message.
|
||||||
|
/// </summary>
|
||||||
|
public interface ILogMessage : IEquatable<ILogMessage>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of the log message in memory.
|
||||||
|
/// </summary>
|
||||||
|
nint Address { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the ID of this log message.
|
||||||
|
/// </summary>
|
||||||
|
uint LogMessageId { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the GameData associated with this log message.
|
||||||
|
/// </summary>
|
||||||
|
RowRef<Lumina.Excel.Sheets.LogMessage> GameData { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the entity that is the source of this log message, if any.
|
||||||
|
/// </summary>
|
||||||
|
ILogMessageEntity? SourceEntity { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the entity that is the target of this log message, if any.
|
||||||
|
/// </summary>
|
||||||
|
ILogMessageEntity? TargetEntity { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the number of parameters.
|
||||||
|
/// </summary>
|
||||||
|
int ParameterCount { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves the value of a parameter for the log message if it is an int.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index">The index of the parameter to retrieve.</param>
|
||||||
|
/// <param name="value">The value of the parameter.</param>
|
||||||
|
/// <returns><see langword="true"/> if the parameter was retrieved successfully.</returns>
|
||||||
|
bool TryGetIntParameter(int index, out int value);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves the value of a parameter for the log message if it is a string.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index">The index of the parameter to retrieve.</param>
|
||||||
|
/// <param name="value">The value of the parameter.</param>
|
||||||
|
/// <returns><see langword="true"/> if the parameter was retrieved successfully.</returns>
|
||||||
|
bool TryGetStringParameter(int index, out ReadOnlySeString value);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Formats this log message into an approximation of the string that will eventually be shown in the log.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>This can cause side effects such as playing sound effects and thus should only be used for debugging.</remarks>
|
||||||
|
/// <returns>The formatted string.</returns>
|
||||||
|
ReadOnlySeString FormatLogMessageForDebugging();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This struct represents log message in the queue to be added to the chat.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ptr">A pointer to the log message.</param>
|
||||||
|
internal unsafe readonly struct LogMessage(LogMessageQueueItem* ptr) : ILogMessage
|
||||||
|
{
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public nint Address => (nint)ptr;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public uint LogMessageId => ptr->LogMessageId;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public RowRef<Lumina.Excel.Sheets.LogMessage> GameData => LuminaUtils.CreateRef<Lumina.Excel.Sheets.LogMessage>(ptr->LogMessageId);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
ILogMessageEntity? ILogMessage.SourceEntity => ptr->SourceKind == EntityRelationKind.None ? null : this.SourceEntity;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
ILogMessageEntity? ILogMessage.TargetEntity => ptr->TargetKind == EntityRelationKind.None ? null : this.TargetEntity;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public int ParameterCount => ptr->Parameters.Count;
|
||||||
|
|
||||||
|
private LogMessageEntity SourceEntity => new(ptr, true);
|
||||||
|
|
||||||
|
private LogMessageEntity TargetEntity => new(ptr, false);
|
||||||
|
|
||||||
|
public static bool operator ==(LogMessage x, LogMessage y) => x.Equals(y);
|
||||||
|
|
||||||
|
public static bool operator !=(LogMessage x, LogMessage y) => !(x == y);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool Equals(ILogMessage? other)
|
||||||
|
{
|
||||||
|
return other is LogMessage logMessage && this.Equals(logMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override bool Equals([NotNullWhen(true)] object? obj)
|
||||||
|
{
|
||||||
|
return obj is LogMessage logMessage && this.Equals(logMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return HashCode.Combine(this.LogMessageId, this.SourceEntity, this.TargetEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool TryGetIntParameter(int index, out int value)
|
||||||
|
{
|
||||||
|
value = 0;
|
||||||
|
if (!this.TryGetParameter(index, out var parameter)) return false;
|
||||||
|
if (parameter.Type != TextParameterType.Integer) return false;
|
||||||
|
value = parameter.IntValue;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool TryGetStringParameter(int index, out ReadOnlySeString value)
|
||||||
|
{
|
||||||
|
value = default;
|
||||||
|
if (!this.TryGetParameter(index, out var parameter)) return false;
|
||||||
|
if (parameter.Type == TextParameterType.String)
|
||||||
|
{
|
||||||
|
value = new(parameter.StringValue.AsSpan());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parameter.Type == TextParameterType.ReferencedUtf8String)
|
||||||
|
{
|
||||||
|
value = new(parameter.ReferencedUtf8StringValue->Utf8String.AsSpan());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public ReadOnlySeString FormatLogMessageForDebugging()
|
||||||
|
{
|
||||||
|
var logModule = RaptureLogModule.Instance();
|
||||||
|
|
||||||
|
// the formatting logic is taken from RaptureLogModule_Update
|
||||||
|
|
||||||
|
using var utf8 = new Utf8String();
|
||||||
|
SetName(logModule, this.SourceEntity);
|
||||||
|
SetName(logModule, this.TargetEntity);
|
||||||
|
|
||||||
|
using var rssb = new RentedSeStringBuilder();
|
||||||
|
logModule->RaptureTextModule->FormatString(rssb.Builder.Append(this.GameData.Value.Text).GetViewAsSpan(), &ptr->Parameters, &utf8);
|
||||||
|
|
||||||
|
return new ReadOnlySeString(utf8.AsSpan());
|
||||||
|
|
||||||
|
static void SetName(RaptureLogModule* self, LogMessageEntity item)
|
||||||
|
{
|
||||||
|
var name = item.NameSpan.GetPointer(0);
|
||||||
|
|
||||||
|
if (item.IsPlayer)
|
||||||
|
{
|
||||||
|
var str = self->TempParseMessage.GetPointer(item.IsSourceEntity ? 8 : 9);
|
||||||
|
self->FormatPlayerLink(name, str, null, 0, item.Kind != 1 /* LocalPlayer */, item.HomeWorldId, false, null, false);
|
||||||
|
|
||||||
|
if (item.HomeWorldId != 0 && item.HomeWorldId != AgentLobby.Instance()->LobbyData.HomeWorldId)
|
||||||
|
{
|
||||||
|
var crossWorldSymbol = self->RaptureTextModule->UnkStrings0.GetPointer(3);
|
||||||
|
if (!crossWorldSymbol->StringPtr.HasValue)
|
||||||
|
self->RaptureTextModule->ProcessMacroCode(crossWorldSymbol, "<icon(88)>\0"u8);
|
||||||
|
str->Append(crossWorldSymbol);
|
||||||
|
if (self->UIModule->GetWorldHelper()->AllWorlds.TryGetValuePointer(item.HomeWorldId, out var world))
|
||||||
|
str->ConcatCStr(world->Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
name = str->StringPtr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.IsSourceEntity)
|
||||||
|
{
|
||||||
|
self->RaptureTextModule->SetGlobalTempEntity1(name, item.Sex, item.ObjStrId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
self->RaptureTextModule->SetGlobalTempEntity2(name, item.Sex, item.ObjStrId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryGetParameter(int index, out TextParameter value)
|
||||||
|
{
|
||||||
|
if (index < 0 || index >= ptr->Parameters.Count)
|
||||||
|
{
|
||||||
|
value = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = ptr->Parameters[index];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool Equals(LogMessage other)
|
||||||
|
{
|
||||||
|
return this.LogMessageId == other.LogMessageId && this.SourceEntity == other.SourceEntity && this.TargetEntity == other.TargetEntity;
|
||||||
|
}
|
||||||
|
}
|
||||||
113
Dalamud/Game/Chat/LogMessageEntity.cs
Normal file
113
Dalamud/Game/Chat/LogMessageEntity.cs
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
|
using Dalamud.Data;
|
||||||
|
using Dalamud.Plugin.Services;
|
||||||
|
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
|
||||||
|
|
||||||
|
using Lumina.Excel;
|
||||||
|
using Lumina.Excel.Sheets;
|
||||||
|
using Lumina.Text.ReadOnly;
|
||||||
|
|
||||||
|
namespace Dalamud.Game.Chat;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Interface representing an entity related to a log message.
|
||||||
|
/// </summary>
|
||||||
|
public interface ILogMessageEntity : IEquatable<ILogMessageEntity>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the name of this entity.
|
||||||
|
/// </summary>
|
||||||
|
ReadOnlySeString Name { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the ID of the homeworld of this entity, if it is a player.
|
||||||
|
/// </summary>
|
||||||
|
ushort HomeWorldId { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the homeworld of this entity, if it is a player.
|
||||||
|
/// </summary>
|
||||||
|
RowRef<World> HomeWorld { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the ObjStr ID of this entity, if not a player. See <seealso cref="ISeStringEvaluator.EvaluateObjStr"/>.
|
||||||
|
/// </summary>
|
||||||
|
uint ObjStrId { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether this entity is a player.
|
||||||
|
/// </summary>
|
||||||
|
bool IsPlayer { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This struct represents an entity related to a log message.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ptr">A pointer to the log message item.</param>
|
||||||
|
/// <param name="source">If <see langword="true"/> represents the source entity of the log message, otherwise represents the target entity.</param>
|
||||||
|
internal unsafe readonly struct LogMessageEntity(LogMessageQueueItem* ptr, bool source) : ILogMessageEntity
|
||||||
|
{
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public ReadOnlySeString Name => new(this.NameSpan[..this.NameSpan.IndexOf((byte)0)]);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public ushort HomeWorldId => source ? ptr->SourceHomeWorld : ptr->TargetHomeWorld;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public RowRef<World> HomeWorld => LuminaUtils.CreateRef<World>(this.HomeWorldId);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public uint ObjStrId => source ? ptr->SourceObjStrId : ptr->TargetObjStrId;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool IsPlayer => source ? ptr->SourceIsPlayer : ptr->TargetIsPlayer;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the Span containing the raw name of this entity.
|
||||||
|
/// </summary>
|
||||||
|
internal Span<byte> NameSpan => source ? ptr->SourceName : ptr->TargetName;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the kind of the entity.
|
||||||
|
/// </summary>
|
||||||
|
internal byte Kind => source ? (byte)ptr->SourceKind : (byte)ptr->TargetKind;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the Sex of this entity.
|
||||||
|
/// </summary>
|
||||||
|
internal byte Sex => source ? ptr->SourceSex : ptr->TargetSex;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether this entity is the source entity of a log message.
|
||||||
|
/// </summary>
|
||||||
|
internal bool IsSourceEntity => source;
|
||||||
|
|
||||||
|
public static bool operator ==(LogMessageEntity x, LogMessageEntity y) => x.Equals(y);
|
||||||
|
|
||||||
|
public static bool operator !=(LogMessageEntity x, LogMessageEntity y) => !(x == y);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool Equals(ILogMessageEntity other)
|
||||||
|
{
|
||||||
|
return other is LogMessageEntity entity && this.Equals(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override bool Equals([NotNullWhen(true)] object? obj)
|
||||||
|
{
|
||||||
|
return obj is LogMessageEntity entity && this.Equals(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return HashCode.Combine(this.Name, this.HomeWorldId, this.ObjStrId, this.Sex, this.IsPlayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool Equals(LogMessageEntity other)
|
||||||
|
{
|
||||||
|
return this.Name == other.Name && this.HomeWorldId == other.HomeWorldId && this.ObjStrId == other.ObjStrId && this.Kind == other.Kind && this.Sex == other.Sex && this.IsPlayer == other.IsPlayer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
using Dalamud.Configuration.Internal;
|
using Dalamud.Configuration.Internal;
|
||||||
|
|
@ -41,10 +42,12 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
|
||||||
|
|
||||||
private readonly Queue<XivChatEntry> chatQueue = new();
|
private readonly Queue<XivChatEntry> chatQueue = new();
|
||||||
private readonly Dictionary<(string PluginName, uint CommandId), Action<uint, SeString>> dalamudLinkHandlers = [];
|
private readonly Dictionary<(string PluginName, uint CommandId), Action<uint, SeString>> dalamudLinkHandlers = [];
|
||||||
|
private readonly List<nint> seenLogMessageObjects = [];
|
||||||
|
|
||||||
private readonly Hook<PrintMessageDelegate> printMessageHook;
|
private readonly Hook<PrintMessageDelegate> printMessageHook;
|
||||||
private readonly Hook<InventoryItem.Delegates.Copy> inventoryItemCopyHook;
|
private readonly Hook<InventoryItem.Delegates.Copy> inventoryItemCopyHook;
|
||||||
private readonly Hook<LogViewer.Delegates.HandleLinkClick> handleLinkClickHook;
|
private readonly Hook<LogViewer.Delegates.HandleLinkClick> handleLinkClickHook;
|
||||||
|
private readonly Hook<RaptureLogModule.Delegates.Update> handleLogModuleUpdate;
|
||||||
|
|
||||||
[ServiceManager.ServiceDependency]
|
[ServiceManager.ServiceDependency]
|
||||||
private readonly DalamudConfiguration configuration = Service<DalamudConfiguration>.Get();
|
private readonly DalamudConfiguration configuration = Service<DalamudConfiguration>.Get();
|
||||||
|
|
@ -58,10 +61,12 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
|
||||||
this.printMessageHook = Hook<PrintMessageDelegate>.FromAddress(RaptureLogModule.Addresses.PrintMessage.Value, this.HandlePrintMessageDetour);
|
this.printMessageHook = Hook<PrintMessageDelegate>.FromAddress(RaptureLogModule.Addresses.PrintMessage.Value, this.HandlePrintMessageDetour);
|
||||||
this.inventoryItemCopyHook = Hook<InventoryItem.Delegates.Copy>.FromAddress((nint)InventoryItem.StaticVirtualTablePointer->Copy, this.InventoryItemCopyDetour);
|
this.inventoryItemCopyHook = Hook<InventoryItem.Delegates.Copy>.FromAddress((nint)InventoryItem.StaticVirtualTablePointer->Copy, this.InventoryItemCopyDetour);
|
||||||
this.handleLinkClickHook = Hook<LogViewer.Delegates.HandleLinkClick>.FromAddress(LogViewer.Addresses.HandleLinkClick.Value, this.HandleLinkClickDetour);
|
this.handleLinkClickHook = Hook<LogViewer.Delegates.HandleLinkClick>.FromAddress(LogViewer.Addresses.HandleLinkClick.Value, this.HandleLinkClickDetour);
|
||||||
|
this.handleLogModuleUpdate = Hook<RaptureLogModule.Delegates.Update>.FromAddress(RaptureLogModule.Addresses.Update.Value, this.UpdateDetour);
|
||||||
|
|
||||||
this.printMessageHook.Enable();
|
this.printMessageHook.Enable();
|
||||||
this.inventoryItemCopyHook.Enable();
|
this.inventoryItemCopyHook.Enable();
|
||||||
this.handleLinkClickHook.Enable();
|
this.handleLinkClickHook.Enable();
|
||||||
|
this.handleLogModuleUpdate.Enable();
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||||
|
|
@ -79,6 +84,9 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public event IChatGui.OnMessageUnhandledDelegate? ChatMessageUnhandled;
|
public event IChatGui.OnMessageUnhandledDelegate? ChatMessageUnhandled;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public event IChatGui.OnLogMessageDelegate? LogMessage;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public uint LastLinkedItemId { get; private set; }
|
public uint LastLinkedItemId { get; private set; }
|
||||||
|
|
||||||
|
|
@ -110,6 +118,7 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
|
||||||
this.printMessageHook.Dispose();
|
this.printMessageHook.Dispose();
|
||||||
this.inventoryItemCopyHook.Dispose();
|
this.inventoryItemCopyHook.Dispose();
|
||||||
this.handleLinkClickHook.Dispose();
|
this.handleLinkClickHook.Dispose();
|
||||||
|
this.handleLogModuleUpdate.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
#region DalamudSeString
|
#region DalamudSeString
|
||||||
|
|
@ -493,6 +502,46 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
|
||||||
Log.Error(ex, "Exception in HandleLinkClickDetour");
|
Log.Error(ex, "Exception in HandleLinkClickDetour");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void UpdateDetour(RaptureLogModule* thisPtr)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
foreach (ref var item in thisPtr->LogMessageQueue)
|
||||||
|
{
|
||||||
|
var logMessage = new Chat.LogMessage((LogMessageQueueItem*)Unsafe.AsPointer(ref item));
|
||||||
|
|
||||||
|
// skip any entries that survived the previous Update call as the event was already called for them
|
||||||
|
if (this.seenLogMessageObjects.Contains(logMessage.Address))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
foreach (var action in Delegate.EnumerateInvocationList(this.LogMessage))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
action(logMessage);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Could not invoke registered OnLogMessageDelegate for {Name}", action.Method);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.handleLogModuleUpdate.Original(thisPtr);
|
||||||
|
|
||||||
|
// record the log messages for that we already called the event, but are still in the queue
|
||||||
|
this.seenLogMessageObjects.Clear();
|
||||||
|
foreach (ref var item in thisPtr->LogMessageQueue)
|
||||||
|
{
|
||||||
|
this.seenLogMessageObjects.Add((nint)Unsafe.AsPointer(ref item));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, "Exception in UpdateDetour");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -521,6 +570,7 @@ internal class ChatGuiPluginScoped : IInternalDisposableService, IChatGui
|
||||||
this.chatGuiService.CheckMessageHandled += this.OnCheckMessageForward;
|
this.chatGuiService.CheckMessageHandled += this.OnCheckMessageForward;
|
||||||
this.chatGuiService.ChatMessageHandled += this.OnMessageHandledForward;
|
this.chatGuiService.ChatMessageHandled += this.OnMessageHandledForward;
|
||||||
this.chatGuiService.ChatMessageUnhandled += this.OnMessageUnhandledForward;
|
this.chatGuiService.ChatMessageUnhandled += this.OnMessageUnhandledForward;
|
||||||
|
this.chatGuiService.LogMessage += this.OnLogMessageForward;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|
@ -535,6 +585,9 @@ internal class ChatGuiPluginScoped : IInternalDisposableService, IChatGui
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public event IChatGui.OnMessageUnhandledDelegate? ChatMessageUnhandled;
|
public event IChatGui.OnMessageUnhandledDelegate? ChatMessageUnhandled;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public event IChatGui.OnLogMessageDelegate? LogMessage;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public uint LastLinkedItemId => this.chatGuiService.LastLinkedItemId;
|
public uint LastLinkedItemId => this.chatGuiService.LastLinkedItemId;
|
||||||
|
|
||||||
|
|
@ -551,11 +604,13 @@ internal class ChatGuiPluginScoped : IInternalDisposableService, IChatGui
|
||||||
this.chatGuiService.CheckMessageHandled -= this.OnCheckMessageForward;
|
this.chatGuiService.CheckMessageHandled -= this.OnCheckMessageForward;
|
||||||
this.chatGuiService.ChatMessageHandled -= this.OnMessageHandledForward;
|
this.chatGuiService.ChatMessageHandled -= this.OnMessageHandledForward;
|
||||||
this.chatGuiService.ChatMessageUnhandled -= this.OnMessageUnhandledForward;
|
this.chatGuiService.ChatMessageUnhandled -= this.OnMessageUnhandledForward;
|
||||||
|
this.chatGuiService.LogMessage -= this.OnLogMessageForward;
|
||||||
|
|
||||||
this.ChatMessage = null;
|
this.ChatMessage = null;
|
||||||
this.CheckMessageHandled = null;
|
this.CheckMessageHandled = null;
|
||||||
this.ChatMessageHandled = null;
|
this.ChatMessageHandled = null;
|
||||||
this.ChatMessageUnhandled = null;
|
this.ChatMessageUnhandled = null;
|
||||||
|
this.LogMessage = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|
@ -609,4 +664,7 @@ internal class ChatGuiPluginScoped : IInternalDisposableService, IChatGui
|
||||||
|
|
||||||
private void OnMessageUnhandledForward(XivChatType type, int timestamp, SeString sender, SeString message)
|
private void OnMessageUnhandledForward(XivChatType type, int timestamp, SeString sender, SeString message)
|
||||||
=> this.ChatMessageUnhandled?.Invoke(type, timestamp, sender, message);
|
=> this.ChatMessageUnhandled?.Invoke(type, timestamp, sender, message);
|
||||||
|
|
||||||
|
private void OnLogMessageForward(Chat.ILogMessage message)
|
||||||
|
=> this.LogMessage?.Invoke(message);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -845,7 +845,7 @@ internal class SeStringEvaluator : IServiceType, ISeStringEvaluator
|
||||||
using var rssb = new RentedSeStringBuilder();
|
using var rssb = new RentedSeStringBuilder();
|
||||||
var sb = rssb.Builder;
|
var sb = rssb.Builder;
|
||||||
|
|
||||||
sb.Append(this.EvaluateFromAddon(6, [rarity], context.Language));
|
sb.Append(this.EvaluateFromAddon(6, [rarity], context.Language)); // appends colortype and edgecolortype
|
||||||
|
|
||||||
if (!skipLink)
|
if (!skipLink)
|
||||||
sb.PushLink(LinkMacroPayloadType.Item, itemId, rarity, 0u); // arg3 = some LogMessage flag based on LogKind RowId? => "89 5C 24 20 E8 ?? ?? ?? ?? 48 8B 1F"
|
sb.PushLink(LinkMacroPayloadType.Item, itemId, rarity, 0u); // arg3 = some LogMessage flag based on LogKind RowId? => "89 5C 24 20 E8 ?? ?? ?? ?? 48 8B 1F"
|
||||||
|
|
@ -868,6 +868,9 @@ internal class SeStringEvaluator : IServiceType, ISeStringEvaluator
|
||||||
if (!skipLink)
|
if (!skipLink)
|
||||||
sb.PopLink();
|
sb.PopLink();
|
||||||
|
|
||||||
|
sb.PopEdgeColorType();
|
||||||
|
sb.PopColorType();
|
||||||
|
|
||||||
text = sb.ToReadOnlySeString();
|
text = sb.ToReadOnlySeString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ using Dalamud.Interface.Windowing;
|
||||||
using Dalamud.Interface.Windowing.Persistence;
|
using Dalamud.Interface.Windowing.Persistence;
|
||||||
using Dalamud.IoC.Internal;
|
using Dalamud.IoC.Internal;
|
||||||
using Dalamud.Logging.Internal;
|
using Dalamud.Logging.Internal;
|
||||||
|
using Dalamud.Memory;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
using Dalamud.Utility.Timing;
|
using Dalamud.Utility.Timing;
|
||||||
|
|
@ -502,6 +503,34 @@ internal partial class InterfaceManager : IInternalDisposableService
|
||||||
ImGuiHelpers.ClearStacksOnContext();
|
ImGuiHelpers.ClearStacksOnContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Applies immersive dark mode to the game window based on the current system theme setting.
|
||||||
|
/// </summary>
|
||||||
|
internal void SetImmersiveModeFromSystemTheme()
|
||||||
|
{
|
||||||
|
bool useDark = this.IsSystemInDarkMode();
|
||||||
|
this.SetImmersiveMode(useDark);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks whether the system use dark mode.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Returns true if dark mode is preferred.</returns>
|
||||||
|
internal bool IsSystemInDarkMode()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var key = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(
|
||||||
|
@"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize");
|
||||||
|
var value = key?.GetValue("AppsUseLightTheme") as int?;
|
||||||
|
return value != 1;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Toggle Windows 11 immersive mode on the game window.
|
/// Toggle Windows 11 immersive mode on the game window.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -745,6 +774,18 @@ internal partial class InterfaceManager : IInternalDisposableService
|
||||||
|
|
||||||
private void WndProcHookManagerOnPreWndProc(WndProcEventArgs args)
|
private void WndProcHookManagerOnPreWndProc(WndProcEventArgs args)
|
||||||
{
|
{
|
||||||
|
if (args.Message == WM.WM_SETTINGCHANGE)
|
||||||
|
{
|
||||||
|
if (this.dalamudConfiguration.WindowIsImmersive)
|
||||||
|
{
|
||||||
|
if (MemoryHelper.EqualsZeroTerminatedWideString("ImmersiveColorSet", args.LParam) ||
|
||||||
|
MemoryHelper.EqualsZeroTerminatedWideString("VisualStyleChanged", args.LParam))
|
||||||
|
{
|
||||||
|
this.SetImmersiveModeFromSystemTheme();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var r = this.backend?.ProcessWndProcW(args.Hwnd, args.Message, args.WParam, args.LParam);
|
var r = this.backend?.ProcessWndProcW(args.Hwnd, args.Message, args.WParam, args.LParam);
|
||||||
if (r is not null)
|
if (r is not null)
|
||||||
args.SuppressWithValue(r.Value);
|
args.SuppressWithValue(r.Value);
|
||||||
|
|
@ -859,7 +900,7 @@ internal partial class InterfaceManager : IInternalDisposableService
|
||||||
{
|
{
|
||||||
// Requires that game window to be there, which will be the case once game swap chain is initialized.
|
// Requires that game window to be there, which will be the case once game swap chain is initialized.
|
||||||
if (Service<DalamudConfiguration>.Get().WindowIsImmersive)
|
if (Service<DalamudConfiguration>.Get().WindowIsImmersive)
|
||||||
this.SetImmersiveMode(true);
|
this.SetImmersiveModeFromSystemTheme();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,6 @@ public unsafe partial class AddonTree
|
||||||
if (tree.Success)
|
if (tree.Success)
|
||||||
{
|
{
|
||||||
using var tbl = ImRaii.Table("atkUnitBase_atkValueTable"u8, 3, ImGuiTableFlags.Borders | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg);
|
using var tbl = ImRaii.Table("atkUnitBase_atkValueTable"u8, 3, ImGuiTableFlags.Borders | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg);
|
||||||
|
|
||||||
if (tbl.Success)
|
if (tbl.Success)
|
||||||
{
|
{
|
||||||
ImGui.TableSetupColumn("Index"u8);
|
ImGui.TableSetupColumn("Index"u8);
|
||||||
|
|
|
||||||
|
|
@ -29,11 +29,9 @@ public static class Events
|
||||||
}
|
}
|
||||||
|
|
||||||
using var tree = ImRaii.TreeNode($"Events##{(nint)node:X}eventTree");
|
using var tree = ImRaii.TreeNode($"Events##{(nint)node:X}eventTree");
|
||||||
|
|
||||||
if (tree.Success)
|
if (tree.Success)
|
||||||
{
|
{
|
||||||
using var tbl = ImRaii.Table($"##{(nint)node:X}eventTable", 7, Resizable | SizingFixedFit | Borders | RowBg);
|
using var tbl = ImRaii.Table($"##{(nint)node:X}eventTable", 7, Resizable | SizingFixedFit | Borders | RowBg);
|
||||||
|
|
||||||
if (tbl.Success)
|
if (tbl.Success)
|
||||||
{
|
{
|
||||||
ImGui.TableSetupColumn("#"u8, WidthFixed);
|
ImGui.TableSetupColumn("#"u8, WidthFixed);
|
||||||
|
|
@ -51,18 +49,25 @@ public static class Events
|
||||||
{
|
{
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.Text($"{i++}");
|
ImGui.Text($"{i++}");
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.Text($"{evt->State.EventType}");
|
ImGui.Text($"{evt->State.EventType}");
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.Text($"{evt->Param}");
|
ImGui.Text($"{evt->Param}");
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.Text($"{evt->State.StateFlags}");
|
ImGui.Text($"{evt->State.StateFlags}");
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.Text($"{evt->State.ReturnFlags}");
|
ImGui.Text($"{evt->State.ReturnFlags}");
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGuiHelpers.ClickToCopyText($"{(nint)evt->Target:X}", default, new Vector4(0.6f, 0.6f, 0.6f, 1));
|
ImGuiHelpers.ClickToCopyText($"{(nint)evt->Target:X}", default, new Vector4(0.6f, 0.6f, 0.6f, 1));
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGuiHelpers.ClickToCopyText($"{(nint)evt->Listener:X}", default, new Vector4(0.6f, 0.6f, 0.6f, 1));
|
ImGuiHelpers.ClickToCopyText($"{(nint)evt->Listener:X}", default, new Vector4(0.6f, 0.6f, 0.6f, 1));
|
||||||
|
|
||||||
evt = evt->NextEvent;
|
evt = evt->NextEvent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ using Dalamud.Bindings.ImGui;
|
||||||
|
|
||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
|
|
||||||
|
using Lumina.Text.ReadOnly;
|
||||||
|
|
||||||
using static Dalamud.Interface.Internal.UiDebug2.Utility.Gui;
|
using static Dalamud.Interface.Internal.UiDebug2.Utility.Gui;
|
||||||
using static Dalamud.Utility.Util;
|
using static Dalamud.Utility.Util;
|
||||||
using static FFXIVClientStructs.FFXIV.Component.GUI.ComponentType;
|
using static FFXIVClientStructs.FFXIV.Component.GUI.ComponentType;
|
||||||
|
|
@ -90,14 +92,14 @@ internal unsafe class ComponentNodeTree : ResNodeTree
|
||||||
{
|
{
|
||||||
case TextInput:
|
case TextInput:
|
||||||
var textInputComponent = (AtkComponentTextInput*)this.Component;
|
var textInputComponent = (AtkComponentTextInput*)this.Component;
|
||||||
ImGui.Text($"InputBase Text1: {Marshal.PtrToStringAnsi(new(textInputComponent->AtkComponentInputBase.EvaluatedString.StringPtr))}");
|
ImGui.Text($"InputBase Text1 (Lumina): {new ReadOnlySeStringSpan(textInputComponent->AtkComponentInputBase.EvaluatedString.AsSpan()).ToMacroString()}");
|
||||||
ImGui.Text($"InputBase Text2: {Marshal.PtrToStringAnsi(new(textInputComponent->AtkComponentInputBase.RawString.StringPtr))}");
|
ImGui.Text($"InputBase Text2 (Lumina): {new ReadOnlySeStringSpan(textInputComponent->AtkComponentInputBase.RawString.AsSpan()).ToMacroString()}");
|
||||||
// TODO: Reenable when unknowns have been unprivated / named
|
// TODO: Reenable when unknowns have been unprivated / named
|
||||||
// ImGui.Text($"Text1: {Marshal.PtrToStringAnsi(new(textInputComponent->UnkText01.StringPtr))}");
|
// ImGui.Text($"Text1: {new ReadOnlySeStringSpan(textInputComponent->UnkText01.AsSpan()).ToMacroString()}");
|
||||||
// ImGui.Text($"Text2: {Marshal.PtrToStringAnsi(new(textInputComponent->UnkText02.StringPtr))}");
|
// ImGui.Text($"Text2: {new ReadOnlySeStringSpan(textInputComponent->UnkText02.AsSpan()).ToMacroString()}");
|
||||||
ImGui.Text($"AvailableLines: {Marshal.PtrToStringAnsi(new(textInputComponent->AvailableLines.StringPtr))}");
|
ImGui.Text($"AvailableLines: {new ReadOnlySeStringSpan(textInputComponent->AvailableLines.AsSpan()).ToMacroString()}");
|
||||||
ImGui.Text($"HighlightedAutoTranslateOptionColorPrefix: {Marshal.PtrToStringAnsi(new(textInputComponent->HighlightedAutoTranslateOptionColorPrefix.StringPtr))}");
|
ImGui.Text($"HighlightedAutoTranslateOptionColorPrefix: {new ReadOnlySeStringSpan(textInputComponent->HighlightedAutoTranslateOptionColorPrefix.AsSpan()).ToMacroString()}");
|
||||||
ImGui.Text($"HighlightedAutoTranslateOptionColorSuffix: {Marshal.PtrToStringAnsi(new(textInputComponent->HighlightedAutoTranslateOptionColorSuffix.StringPtr))}");
|
ImGui.Text($"HighlightedAutoTranslateOptionColorSuffix: {new ReadOnlySeStringSpan(textInputComponent->HighlightedAutoTranslateOptionColorSuffix.AsSpan()).ToMacroString()}");
|
||||||
break;
|
break;
|
||||||
case List:
|
case List:
|
||||||
case TreeList:
|
case TreeList:
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
|
|
||||||
|
using Lumina.Text.ReadOnly;
|
||||||
|
|
||||||
using static Dalamud.Interface.Internal.UiDebug2.Utility.Gui;
|
using static Dalamud.Interface.Internal.UiDebug2.Utility.Gui;
|
||||||
using static Dalamud.Utility.Util;
|
using static Dalamud.Utility.Util;
|
||||||
|
|
||||||
|
|
@ -30,7 +32,7 @@ internal unsafe partial class CounterNodeTree : ResNodeTree
|
||||||
{
|
{
|
||||||
if (!isEditorOpen)
|
if (!isEditorOpen)
|
||||||
{
|
{
|
||||||
PrintFieldValuePairs(("Text", ((AtkCounterNode*)this.Node)->NodeText.ToString()));
|
PrintFieldValuePairs(("Text", new ReadOnlySeStringSpan(((AtkCounterNode*)this.Node)->NodeText.AsSpan()).ToMacroString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@ using Dalamud.Interface.Utility.Raii;
|
||||||
|
|
||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
|
|
||||||
|
using Lumina.Text.ReadOnly;
|
||||||
|
|
||||||
using static Dalamud.Bindings.ImGui.ImGuiColorEditFlags;
|
using static Dalamud.Bindings.ImGui.ImGuiColorEditFlags;
|
||||||
using static Dalamud.Bindings.ImGui.ImGuiInputTextFlags;
|
using static Dalamud.Bindings.ImGui.ImGuiInputTextFlags;
|
||||||
using static Dalamud.Bindings.ImGui.ImGuiTableColumnFlags;
|
using static Dalamud.Bindings.ImGui.ImGuiTableColumnFlags;
|
||||||
|
|
@ -28,10 +30,10 @@ internal unsafe partial class ResNodeTree
|
||||||
private protected void DrawNodeEditorTable()
|
private protected void DrawNodeEditorTable()
|
||||||
{
|
{
|
||||||
using var tbl = ImRaii.Table($"###Editor{(nint)this.Node}", 2, SizingStretchProp | NoHostExtendX);
|
using var tbl = ImRaii.Table($"###Editor{(nint)this.Node}", 2, SizingStretchProp | NoHostExtendX);
|
||||||
if (tbl.Success)
|
if (!tbl.Success)
|
||||||
{
|
return;
|
||||||
this.DrawEditorRows();
|
|
||||||
}
|
this.DrawEditorRows();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -60,7 +62,7 @@ internal unsafe partial class ResNodeTree
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.SetNextItemWidth(150);
|
ImGui.SetNextItemWidth(150);
|
||||||
if (ImGui.DragFloat2($"##{(nint)this.Node:X}position", ref pos, 1, default, default, "%.0f"))
|
if (ImGui.DragFloat2($"##{(nint)this.Node:X}position", ref pos, 1, 0, 0, "%.0f"))
|
||||||
{
|
{
|
||||||
this.Node->X = pos.X;
|
this.Node->X = pos.X;
|
||||||
this.Node->Y = pos.Y;
|
this.Node->Y = pos.Y;
|
||||||
|
|
@ -74,7 +76,7 @@ internal unsafe partial class ResNodeTree
|
||||||
ImGui.Text("Size:"u8);
|
ImGui.Text("Size:"u8);
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.SetNextItemWidth(150);
|
ImGui.SetNextItemWidth(150);
|
||||||
if (ImGui.DragFloat2($"##{(nint)this.Node:X}size", ref size, 1, 0, default, "%.0f"))
|
if (ImGui.DragFloat2($"##{(nint)this.Node:X}size", ref size, 1, 0, 0, "%.0f"))
|
||||||
{
|
{
|
||||||
this.Node->Width = (ushort)Math.Max(size.X, 0);
|
this.Node->Width = (ushort)Math.Max(size.X, 0);
|
||||||
this.Node->Height = (ushort)Math.Max(size.Y, 0);
|
this.Node->Height = (ushort)Math.Max(size.Y, 0);
|
||||||
|
|
@ -102,7 +104,7 @@ internal unsafe partial class ResNodeTree
|
||||||
ImGui.Text("Origin:"u8);
|
ImGui.Text("Origin:"u8);
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.SetNextItemWidth(150);
|
ImGui.SetNextItemWidth(150);
|
||||||
if (ImGui.DragFloat2($"##{(nint)this.Node:X}origin", ref origin, 1, default, default, "%.0f"))
|
if (ImGui.DragFloat2($"##{(nint)this.Node:X}origin", ref origin, 1, 0, 0, "%.0f"))
|
||||||
{
|
{
|
||||||
this.Node->OriginX = origin.X;
|
this.Node->OriginX = origin.X;
|
||||||
this.Node->OriginY = origin.Y;
|
this.Node->OriginY = origin.Y;
|
||||||
|
|
@ -121,7 +123,7 @@ internal unsafe partial class ResNodeTree
|
||||||
angle -= 360;
|
angle -= 360;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui.DragFloat($"##{(nint)this.Node:X}rotation", ref angle, 0.05f, default, default, "%.2f°"))
|
if (ImGui.DragFloat($"##{(nint)this.Node:X}rotation", ref angle, 0.05f, 0, 0, "%.2f°"))
|
||||||
{
|
{
|
||||||
this.Node->Rotation = (float)(angle / (180 / Math.PI));
|
this.Node->Rotation = (float)(angle / (180 / Math.PI));
|
||||||
this.Node->DrawFlags |= 0xD;
|
this.Node->DrawFlags |= 0xD;
|
||||||
|
|
@ -169,7 +171,6 @@ internal unsafe partial class ResNodeTree
|
||||||
ImGui.Text("Add:"u8);
|
ImGui.Text("Add:"u8);
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.SetNextItemWidth(124);
|
ImGui.SetNextItemWidth(124);
|
||||||
|
|
||||||
if (ImGui.DragFloat3($"##{(nint)this.Node:X}addRGB", ref add, 1, -255, 255, "%.0f"))
|
if (ImGui.DragFloat3($"##{(nint)this.Node:X}addRGB", ref add, 1, -255, 255, "%.0f"))
|
||||||
{
|
{
|
||||||
this.Node->AddRed = (short)add.X;
|
this.Node->AddRed = (short)add.X;
|
||||||
|
|
@ -200,7 +201,7 @@ internal unsafe partial class CounterNodeTree
|
||||||
{
|
{
|
||||||
base.DrawEditorRows();
|
base.DrawEditorRows();
|
||||||
|
|
||||||
var str = this.CntNode->NodeText.ToString();
|
var str = new ReadOnlySeStringSpan(this.CntNode->NodeText.AsSpan()).ToMacroString();
|
||||||
|
|
||||||
ImGui.TableNextRow();
|
ImGui.TableNextRow();
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
|
|
@ -300,7 +301,7 @@ internal unsafe partial class TextNodeTree
|
||||||
{
|
{
|
||||||
base.DrawEditorRows();
|
base.DrawEditorRows();
|
||||||
|
|
||||||
var text = this.TxtNode->NodeText.ToString();
|
var text = new ReadOnlySeStringSpan(this.TxtNode->NodeText.AsSpan()).ToMacroString();
|
||||||
var fontIndex = FontList.IndexOf(this.TxtNode->FontType);
|
var fontIndex = FontList.IndexOf(this.TxtNode->FontType);
|
||||||
int fontSize = this.TxtNode->FontSize;
|
int fontSize = this.TxtNode->FontSize;
|
||||||
var alignment = this.TxtNode->AlignmentType;
|
var alignment = this.TxtNode->AlignmentType;
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,6 @@ internal unsafe partial class ImageNodeTree : ResNodeTree
|
||||||
}
|
}
|
||||||
|
|
||||||
using var tree = ImRaii.TreeNode($"Texture##texture{(nint)this.TexData.Texture->D3D11ShaderResourceView:X}", SpanFullWidth);
|
using var tree = ImRaii.TreeNode($"Texture##texture{(nint)this.TexData.Texture->D3D11ShaderResourceView:X}", SpanFullWidth);
|
||||||
|
|
||||||
if (tree.Success)
|
if (tree.Success)
|
||||||
{
|
{
|
||||||
PrintFieldValuePairs(
|
PrintFieldValuePairs(
|
||||||
|
|
|
||||||
|
|
@ -60,10 +60,10 @@ internal unsafe partial class NineGridNodeTree : ImageNodeTree
|
||||||
|
|
||||||
var ngCol = RgbaVector4ToUint(col with { W = 0.75f * col.W });
|
var ngCol = RgbaVector4ToUint(col with { W = 0.75f * col.W });
|
||||||
|
|
||||||
ImGui.GetWindowDrawList()
|
var windowDrawList = ImGui.GetWindowDrawList();
|
||||||
.AddRect(partBegin, partEnd, RgbaVector4ToUint(col));
|
windowDrawList.AddRect(partBegin, partEnd, RgbaVector4ToUint(col));
|
||||||
ImGui.GetWindowDrawList().AddRect(ngBegin1, ngEnd1, ngCol);
|
windowDrawList.AddRect(ngBegin1, ngEnd1, ngCol);
|
||||||
ImGui.GetWindowDrawList().AddRect(ngBegin2, ngEnd2, ngCol);
|
windowDrawList.AddRect(ngBegin2, ngEnd2, ngCol);
|
||||||
|
|
||||||
ImGui.SetCursorPos(cursorLocalPos + uv + new Vector2(0, -20));
|
ImGui.SetCursorPos(cursorLocalPos + uv + new Vector2(0, -20));
|
||||||
ImGui.TextColored(col, $"[#{partId}]\t{part.U}, {part.V}\t{part.Width}x{part.Height}");
|
ImGui.TextColored(col, $"[#{partId}]\t{part.U}, {part.V}\t{part.Width}x{part.Height}");
|
||||||
|
|
|
||||||
|
|
@ -138,7 +138,6 @@ internal unsafe partial class ResNodeTree : IDisposable
|
||||||
PrintNodeList(nodeList, count, addonTree);
|
PrintNodeList(nodeList, count, addonTree);
|
||||||
|
|
||||||
var lineEnd = lineStart with { Y = ImGui.GetCursorScreenPos().Y - 7 };
|
var lineEnd = lineStart with { Y = ImGui.GetCursorScreenPos().Y - 7 };
|
||||||
|
|
||||||
if (lineStart.Y < lineEnd.Y)
|
if (lineStart.Y < lineEnd.Y)
|
||||||
{
|
{
|
||||||
ImGui.GetWindowDrawList().AddLine(lineStart, lineEnd, RgbaVector4ToUint(color), 1);
|
ImGui.GetWindowDrawList().AddLine(lineStart, lineEnd, RgbaVector4ToUint(color), 1);
|
||||||
|
|
|
||||||
|
|
@ -2,16 +2,15 @@ using System.Numerics;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
using Dalamud.Bindings.ImGui;
|
using Dalamud.Bindings.ImGui;
|
||||||
using Dalamud.Game.Text.SeStringHandling;
|
|
||||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
|
||||||
using Dalamud.Interface.ImGuiSeStringRenderer;
|
using Dalamud.Interface.ImGuiSeStringRenderer;
|
||||||
using Dalamud.Interface.Internal.UiDebug2.Utility;
|
|
||||||
using Dalamud.Interface.Utility;
|
using Dalamud.Interface.Utility;
|
||||||
using Dalamud.Interface.Utility.Raii;
|
using Dalamud.Interface.Utility.Raii;
|
||||||
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.System.String;
|
using FFXIVClientStructs.FFXIV.Client.System.String;
|
||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
|
|
||||||
|
using Lumina.Text.ReadOnly;
|
||||||
|
|
||||||
using static Dalamud.Interface.ColorHelpers;
|
using static Dalamud.Interface.ColorHelpers;
|
||||||
using static Dalamud.Interface.Internal.UiDebug2.Utility.Gui;
|
using static Dalamud.Interface.Internal.UiDebug2.Utility.Gui;
|
||||||
using static Dalamud.Utility.Util;
|
using static Dalamud.Utility.Util;
|
||||||
|
|
@ -65,7 +64,7 @@ internal unsafe partial class TextNodeTree : ResNodeTree
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
ImGui.Text(Marshal.PtrToStringAnsi(new(this.NodeText.StringPtr)) ?? string.Empty);
|
ImGui.Text(new ReadOnlySeStringSpan(this.NodeText.AsSpan()).ToMacroString());
|
||||||
}
|
}
|
||||||
|
|
||||||
PrintFieldValuePairs(
|
PrintFieldValuePairs(
|
||||||
|
|
@ -83,36 +82,24 @@ internal unsafe partial class TextNodeTree : ResNodeTree
|
||||||
private void PrintPayloads()
|
private void PrintPayloads()
|
||||||
{
|
{
|
||||||
using var tree = ImRaii.TreeNode($"Text Payloads##{(nint)this.Node:X}");
|
using var tree = ImRaii.TreeNode($"Text Payloads##{(nint)this.Node:X}");
|
||||||
|
|
||||||
if (tree.Success)
|
if (tree.Success)
|
||||||
{
|
{
|
||||||
var utf8String = this.NodeText;
|
var idx = 0;
|
||||||
var seStringBytes = new byte[utf8String.BufUsed];
|
foreach (var payload in new ReadOnlySeString(this.NodeText.AsSpan()))
|
||||||
for (var i = 0L; i < utf8String.BufUsed; i++)
|
|
||||||
{
|
{
|
||||||
seStringBytes[i] = utf8String.StringPtr.Value[i];
|
ImGui.Text($"[{idx}]");
|
||||||
}
|
|
||||||
|
|
||||||
var seString = SeString.Parse(seStringBytes);
|
|
||||||
for (var i = 0; i < seString.Payloads.Count; i++)
|
|
||||||
{
|
|
||||||
var payload = seString.Payloads[i];
|
|
||||||
ImGui.Text($"[{i}]");
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
switch (payload.Type)
|
switch (payload.Type)
|
||||||
{
|
{
|
||||||
case PayloadType.RawText when payload is TextPayload tp:
|
case ReadOnlySePayloadType.Text:
|
||||||
{
|
PrintFieldValuePair("Raw Text", payload.ToString());
|
||||||
Gui.PrintFieldValuePair("Raw Text", tp.Text ?? string.Empty);
|
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
{
|
|
||||||
ImGui.Text(payload.ToString());
|
ImGui.Text(payload.ToString());
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
idx++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,6 @@ public readonly unsafe partial struct TimelineTree
|
||||||
if (animationCount > 0)
|
if (animationCount > 0)
|
||||||
{
|
{
|
||||||
using var tree = ImRaii.TreeNode($"Timeline##{(nint)this.node:X}timeline", SpanFullWidth);
|
using var tree = ImRaii.TreeNode($"Timeline##{(nint)this.node:X}timeline", SpanFullWidth);
|
||||||
|
|
||||||
if (tree.Success)
|
if (tree.Success)
|
||||||
{
|
{
|
||||||
PrintFieldValuePair("Timeline", $"{(nint)this.NodeTimeline:X}");
|
PrintFieldValuePair("Timeline", $"{(nint)this.NodeTimeline:X}");
|
||||||
|
|
@ -90,7 +89,6 @@ public readonly unsafe partial struct TimelineTree
|
||||||
if (labelSetCount > 0 && this.Resource->LabelSets is not null)
|
if (labelSetCount > 0 && this.Resource->LabelSets is not null)
|
||||||
{
|
{
|
||||||
using var tree = ImRaii.TreeNode($"Timeline Label Sets##{(nint)this.node:X}LabelSets", SpanFullWidth);
|
using var tree = ImRaii.TreeNode($"Timeline Label Sets##{(nint)this.node:X}LabelSets", SpanFullWidth);
|
||||||
|
|
||||||
if (tree.Success)
|
if (tree.Success)
|
||||||
{
|
{
|
||||||
this.DrawLabelSets();
|
this.DrawLabelSets();
|
||||||
|
|
@ -325,7 +323,6 @@ public readonly unsafe partial struct TimelineTree
|
||||||
using (ImRaii.PushColor(ImGuiCol.Text, new Vector4(1, 0.65F, 0.4F, 1), isActive))
|
using (ImRaii.PushColor(ImGuiCol.Text, new Vector4(1, 0.65F, 0.4F, 1), isActive))
|
||||||
{
|
{
|
||||||
using var tree = ImRaii.TreeNode($"[#{a}] [Frames {animation.StartFrameIdx}-{animation.EndFrameIdx}] {(isActive ? " (Active)" : string.Empty)}###{(nint)this.node}animTree{a}");
|
using var tree = ImRaii.TreeNode($"[#{a}] [Frames {animation.StartFrameIdx}-{animation.EndFrameIdx}] {(isActive ? " (Active)" : string.Empty)}###{(nint)this.node}animTree{a}");
|
||||||
|
|
||||||
if (tree.Success)
|
if (tree.Success)
|
||||||
{
|
{
|
||||||
PrintFieldValuePair("Animation", $"{address:X}");
|
PrintFieldValuePair("Animation", $"{address:X}");
|
||||||
|
|
@ -335,7 +332,6 @@ public readonly unsafe partial struct TimelineTree
|
||||||
if (columns.Count > 0)
|
if (columns.Count > 0)
|
||||||
{
|
{
|
||||||
using var tbl = ImRaii.Table($"##{(nint)this.node}animTable{a}", columns.Count, Borders | SizingFixedFit | RowBg | NoHostExtendX);
|
using var tbl = ImRaii.Table($"##{(nint)this.node}animTable{a}", columns.Count, Borders | SizingFixedFit | RowBg | NoHostExtendX);
|
||||||
|
|
||||||
if (tbl.Success)
|
if (tbl.Success)
|
||||||
{
|
{
|
||||||
foreach (var c in columns)
|
foreach (var c in columns)
|
||||||
|
|
|
||||||
|
|
@ -160,65 +160,61 @@ internal unsafe class ElementSelector : IDisposable
|
||||||
if (ch.Success)
|
if (ch.Success)
|
||||||
{
|
{
|
||||||
using var gr = ImRaii.Group();
|
using var gr = ImRaii.Group();
|
||||||
if (gr.Success)
|
Gui.PrintFieldValuePair("Mouse Position", $"{mousePos.X}, {mousePos.Y}");
|
||||||
|
ImGui.Spacing();
|
||||||
|
ImGui.Text("RESULTS:\n"u8);
|
||||||
|
|
||||||
|
var i = 0;
|
||||||
|
foreach (var a in addonResults)
|
||||||
{
|
{
|
||||||
Gui.PrintFieldValuePair("Mouse Position", $"{mousePos.X}, {mousePos.Y}");
|
var name = a.Addon->NameString;
|
||||||
ImGui.Spacing();
|
ImGui.Text($"[Addon] {name}");
|
||||||
ImGui.Text("RESULTS:\n"u8);
|
|
||||||
|
|
||||||
var i = 0;
|
using var indent = ImRaii.PushIndent(15.0f);
|
||||||
foreach (var a in addonResults)
|
foreach (var n in a.Nodes)
|
||||||
{
|
{
|
||||||
var name = a.Addon->NameString;
|
var nSelected = i++ == this.index;
|
||||||
ImGui.Text($"[Addon] {name}");
|
|
||||||
ImGui.Indent(15);
|
PrintNodeHeaderOnly(n.Node, nSelected, a.Addon);
|
||||||
foreach (var n in a.Nodes)
|
|
||||||
|
if (nSelected && ImGui.IsMouseClicked(ImGuiMouseButton.Left))
|
||||||
{
|
{
|
||||||
var nSelected = i++ == this.index;
|
this.Active = false;
|
||||||
|
|
||||||
PrintNodeHeaderOnly(n.Node, nSelected, a.Addon);
|
this.uiDebug2.SelectedAddonName = a.Addon->NameString;
|
||||||
|
|
||||||
if (nSelected && ImGui.IsMouseClicked(ImGuiMouseButton.Left))
|
var ptrList = new List<nint> { (nint)n.Node };
|
||||||
|
|
||||||
|
var nextNode = n.Node->ParentNode;
|
||||||
|
while (nextNode != null)
|
||||||
{
|
{
|
||||||
this.Active = false;
|
ptrList.Add((nint)nextNode);
|
||||||
|
nextNode = nextNode->ParentNode;
|
||||||
this.uiDebug2.SelectedAddonName = a.Addon->NameString;
|
|
||||||
|
|
||||||
var ptrList = new List<nint> { (nint)n.Node };
|
|
||||||
|
|
||||||
var nextNode = n.Node->ParentNode;
|
|
||||||
while (nextNode != null)
|
|
||||||
{
|
|
||||||
ptrList.Add((nint)nextNode);
|
|
||||||
nextNode = nextNode->ParentNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
SearchResults = [.. ptrList];
|
|
||||||
Countdown = 100;
|
|
||||||
Scrolled = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nSelected)
|
SearchResults = [.. ptrList];
|
||||||
{
|
Countdown = 100;
|
||||||
n.NodeBounds.DrawFilled(new(1, 1, 0.2f, 1));
|
Scrolled = false;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.Indent(-15);
|
if (nSelected)
|
||||||
|
{
|
||||||
|
n.NodeBounds.DrawFilled(new(1, 1, 0.2f, 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i != 0)
|
||||||
|
{
|
||||||
|
this.index -= (int)ImGui.GetIO().MouseWheel;
|
||||||
|
while (this.index < 0)
|
||||||
|
{
|
||||||
|
this.index += i;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (i != 0)
|
while (this.index >= i)
|
||||||
{
|
{
|
||||||
this.index -= (int)ImGui.GetIO().MouseWheel;
|
this.index -= i;
|
||||||
while (this.index < 0)
|
|
||||||
{
|
|
||||||
this.index += i;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (this.index >= i)
|
|
||||||
{
|
|
||||||
this.index -= i;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ internal class AddonPopoutWindow : Window, IDisposable
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override void Draw()
|
public override void Draw()
|
||||||
{
|
{
|
||||||
using var ch = ImRaii.Child($"{this.WindowName}child", new(-1, -1), true);
|
using var ch = ImRaii.Child($"{this.WindowName}child", Vector2.Zero, true);
|
||||||
if (ch.Success)
|
if (ch.Success)
|
||||||
{
|
{
|
||||||
this.addonTree.Draw();
|
this.addonTree.Draw();
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ internal unsafe class NodePopoutWindow : Window, IDisposable
|
||||||
this.PositionCondition = ImGuiCond.Once;
|
this.PositionCondition = ImGuiCond.Once;
|
||||||
this.SizeCondition = ImGuiCond.Once;
|
this.SizeCondition = ImGuiCond.Once;
|
||||||
this.Size = new(700, 200);
|
this.Size = new(700, 200);
|
||||||
this.SizeConstraints = new() { MinimumSize = new(100, 100) };
|
this.SizeConstraints = new() { MinimumSize = new Vector2(100, 100) };
|
||||||
}
|
}
|
||||||
|
|
||||||
private AddonTree AddonTree => this.resNodeTree.AddonTree;
|
private AddonTree AddonTree => this.resNodeTree.AddonTree;
|
||||||
|
|
@ -51,7 +51,7 @@ internal unsafe class NodePopoutWindow : Window, IDisposable
|
||||||
{
|
{
|
||||||
if (this.Node != null && this.AddonTree.ContainsNode(this.Node))
|
if (this.Node != null && this.AddonTree.ContainsNode(this.Node))
|
||||||
{
|
{
|
||||||
using var ch = ImRaii.Child($"{(nint)this.Node:X}popoutChild", new(-1, -1), true);
|
using var ch = ImRaii.Child($"{(nint)this.Node:X}popoutChild", Vector2.Zero, true);
|
||||||
if (ch.Success)
|
if (ch.Success)
|
||||||
{
|
{
|
||||||
ResNodeTree.GetOrCreate(this.Node, this.AddonTree).Print(null, this.firstDraw);
|
ResNodeTree.GetOrCreate(this.Node, this.AddonTree).Print(null, this.firstDraw);
|
||||||
|
|
|
||||||
|
|
@ -105,12 +105,8 @@ internal static class Gui
|
||||||
|
|
||||||
var index = (int)Math.Floor(prog * tooltips.Length);
|
var index = (int)Math.Floor(prog * tooltips.Length);
|
||||||
|
|
||||||
using var tt = ImRaii.Tooltip();
|
using var tooltip = ImRaii.Tooltip();
|
||||||
|
ImGui.Text(tooltips[index]);
|
||||||
if (tt.Success)
|
|
||||||
{
|
|
||||||
ImGui.Text(tooltips[index]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -124,13 +120,14 @@ internal static class Gui
|
||||||
{
|
{
|
||||||
if ((mask & 0b10) > 0)
|
if ((mask & 0b10) > 0)
|
||||||
{
|
{
|
||||||
ImGui.Dummy(new(padding * ImGui.GetIO().FontGlobalScale));
|
ImGuiHelpers.ScaledDummy(padding);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
|
|
||||||
if ((mask & 0b01) > 0)
|
if ((mask & 0b01) > 0)
|
||||||
{
|
{
|
||||||
ImGui.Dummy(new(padding * ImGui.GetIO().FontGlobalScale));
|
ImGuiHelpers.ScaledDummy(padding);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -62,10 +62,11 @@ public unsafe struct NodeBounds
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var backgroundDrawList = ImGui.GetBackgroundDrawList();
|
||||||
if (this.Points.Count == 1)
|
if (this.Points.Count == 1)
|
||||||
{
|
{
|
||||||
ImGui.GetBackgroundDrawList().AddCircle(this.Points[0], 10, RgbaVector4ToUint(col with { W = col.W / 2 }), 12, thickness);
|
backgroundDrawList.AddCircle(this.Points[0], 10, RgbaVector4ToUint(col with { W = col.W / 2 }), 12, thickness);
|
||||||
ImGui.GetBackgroundDrawList().AddCircle(this.Points[0], thickness, RgbaVector4ToUint(col), 12, thickness + 1);
|
backgroundDrawList.AddCircle(this.Points[0], thickness, RgbaVector4ToUint(col), 12, thickness + 1);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -75,8 +76,7 @@ public unsafe struct NodeBounds
|
||||||
path.Add(p);
|
path.Add(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.GetBackgroundDrawList()
|
backgroundDrawList.AddPolyline(ref path[0], path.Length, RgbaVector4ToUint(col), ImDrawFlags.Closed, thickness);
|
||||||
.AddPolyline(ref path[0], path.Length, RgbaVector4ToUint(col), ImDrawFlags.Closed, thickness);
|
|
||||||
|
|
||||||
path.Dispose();
|
path.Dispose();
|
||||||
}
|
}
|
||||||
|
|
@ -94,11 +94,11 @@ public unsafe struct NodeBounds
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var backgroundDrawList = ImGui.GetBackgroundDrawList();
|
||||||
if (this.Points.Count == 1)
|
if (this.Points.Count == 1)
|
||||||
{
|
{
|
||||||
ImGui.GetBackgroundDrawList()
|
backgroundDrawList.AddCircleFilled(this.Points[0], 10, RgbaVector4ToUint(col with { W = col.W / 2 }), 12);
|
||||||
.AddCircleFilled(this.Points[0], 10, RgbaVector4ToUint(col with { W = col.W / 2 }), 12);
|
backgroundDrawList.AddCircle(this.Points[0], 10, RgbaVector4ToUint(col), 12, thickness);
|
||||||
ImGui.GetBackgroundDrawList().AddCircle(this.Points[0], 10, RgbaVector4ToUint(col), 12, thickness);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -108,10 +108,8 @@ public unsafe struct NodeBounds
|
||||||
path.Add(p);
|
path.Add(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.GetBackgroundDrawList()
|
backgroundDrawList.AddConvexPolyFilled(ref path[0], path.Length, RgbaVector4ToUint(col with { W = col.W / 2 }));
|
||||||
.AddConvexPolyFilled(ref path[0], path.Length, RgbaVector4ToUint(col with { W = col.W / 2 }));
|
backgroundDrawList.AddPolyline(ref path[0], path.Length, RgbaVector4ToUint(col), ImDrawFlags.Closed, thickness);
|
||||||
ImGui.GetBackgroundDrawList()
|
|
||||||
.AddPolyline(ref path[0], path.Length, RgbaVector4ToUint(col), ImDrawFlags.Closed, thickness);
|
|
||||||
|
|
||||||
path.Dispose();
|
path.Dispose();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@ internal class DataWindow : Window, IDisposable
|
||||||
new ImGuiWidget(),
|
new ImGuiWidget(),
|
||||||
new InventoryWidget(),
|
new InventoryWidget(),
|
||||||
new KeyStateWidget(),
|
new KeyStateWidget(),
|
||||||
|
new LogMessageMonitorWidget(),
|
||||||
new MarketBoardWidget(),
|
new MarketBoardWidget(),
|
||||||
new NetworkMonitorWidget(),
|
new NetworkMonitorWidget(),
|
||||||
new NounProcessorWidget(),
|
new NounProcessorWidget(),
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,156 @@
|
||||||
|
using System.Buffers;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
using Dalamud.Bindings.ImGui;
|
||||||
|
using Dalamud.Game.Chat;
|
||||||
|
using Dalamud.Game.Gui;
|
||||||
|
using Dalamud.Interface.Utility;
|
||||||
|
using Dalamud.Interface.Utility.Raii;
|
||||||
|
|
||||||
|
using Lumina.Text.ReadOnly;
|
||||||
|
|
||||||
|
using ImGuiTable = Dalamud.Interface.Utility.ImGuiTable;
|
||||||
|
|
||||||
|
namespace Dalamud.Interface.Internal.Windows.Data.Widgets;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Widget to display the LogMessages.
|
||||||
|
/// </summary>
|
||||||
|
internal class LogMessageMonitorWidget : IDataWindowWidget
|
||||||
|
{
|
||||||
|
private readonly ConcurrentQueue<LogMessageData> messages = new();
|
||||||
|
|
||||||
|
private bool trackMessages;
|
||||||
|
private int trackedMessages;
|
||||||
|
private Regex? filterRegex;
|
||||||
|
private string filterString = string.Empty;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public string[]? CommandShortcuts { get; init; } = ["logmessage"];
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public string DisplayName { get; init; } = "LogMessage Monitor";
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool Ready { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Load()
|
||||||
|
{
|
||||||
|
this.trackMessages = false;
|
||||||
|
this.trackedMessages = 20;
|
||||||
|
this.filterRegex = null;
|
||||||
|
this.filterString = string.Empty;
|
||||||
|
this.messages.Clear();
|
||||||
|
this.Ready = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Draw()
|
||||||
|
{
|
||||||
|
var network = Service<ChatGui>.Get();
|
||||||
|
if (ImGui.Checkbox("Track LogMessages"u8, ref this.trackMessages))
|
||||||
|
{
|
||||||
|
if (this.trackMessages)
|
||||||
|
{
|
||||||
|
network.LogMessage += this.OnLogMessage;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
network.LogMessage -= this.OnLogMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X / 2);
|
||||||
|
if (ImGui.DragInt("Stored Number of Messages"u8, ref this.trackedMessages, 0.1f, 1, 512))
|
||||||
|
{
|
||||||
|
this.trackedMessages = Math.Clamp(this.trackedMessages, 1, 512);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.Button("Clear Stored Messages"u8))
|
||||||
|
{
|
||||||
|
this.messages.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.DrawFilterInput();
|
||||||
|
|
||||||
|
ImGuiTable.DrawTable(string.Empty, this.messages.Where(m => this.filterRegex == null || this.filterRegex.IsMatch(m.Formatted.ExtractText())), this.DrawNetworkPacket, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingStretchProp, "LogMessageId", "Source", "Target", "Parameters", "Formatted");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawNetworkPacket(LogMessageData data)
|
||||||
|
{
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.Text(data.LogMessageId.ToString());
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGuiHelpers.SeStringWrapped(data.Source);
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGuiHelpers.SeStringWrapped(data.Target);
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.Text(data.Parameters);
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGuiHelpers.SeStringWrapped(data.Formatted);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawFilterInput()
|
||||||
|
{
|
||||||
|
var invalidRegEx = this.filterString.Length > 0 && this.filterRegex == null;
|
||||||
|
using var style = ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, 2 * ImGuiHelpers.GlobalScale, invalidRegEx);
|
||||||
|
using var color = ImRaii.PushColor(ImGuiCol.Border, 0xFF0000FF, invalidRegEx);
|
||||||
|
ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X);
|
||||||
|
if (!ImGui.InputTextWithHint("##Filter"u8, "Regex Filter..."u8, ref this.filterString, 1024))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.filterString.Length == 0)
|
||||||
|
{
|
||||||
|
this.filterRegex = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.filterRegex = new Regex(this.filterString, RegexOptions.Compiled | RegexOptions.ExplicitCapture);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
this.filterRegex = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnLogMessage(ILogMessage message)
|
||||||
|
{
|
||||||
|
var buffer = new ArrayBufferWriter<byte>();
|
||||||
|
var writer = new Utf8JsonWriter(buffer);
|
||||||
|
|
||||||
|
writer.WriteStartArray();
|
||||||
|
for (var i = 0; i < message.ParameterCount; i++)
|
||||||
|
{
|
||||||
|
if (message.TryGetStringParameter(i, out var str))
|
||||||
|
writer.WriteStringValue(str.ExtractText());
|
||||||
|
else if (message.TryGetIntParameter(i, out var num))
|
||||||
|
writer.WriteNumberValue(num);
|
||||||
|
else
|
||||||
|
writer.WriteNullValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.WriteEndArray();
|
||||||
|
writer.Flush();
|
||||||
|
|
||||||
|
this.messages.Enqueue(new LogMessageData(message.LogMessageId, message.SourceEntity?.Name ?? default, message.TargetEntity?.Name ?? default, buffer.WrittenMemory, message.FormatLogMessageForDebugging()));
|
||||||
|
while (this.messages.Count > this.trackedMessages)
|
||||||
|
{
|
||||||
|
this.messages.TryDequeue(out _);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly record struct LogMessageData(uint LogMessageId, ReadOnlySeString Source, ReadOnlySeString Target, ReadOnlyMemory<byte> Parameters, ReadOnlySeString Formatted);
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using Dalamud.Bindings.ImGui;
|
using Dalamud.Bindings.ImGui;
|
||||||
|
using Dalamud.Game.Chat;
|
||||||
using Dalamud.Game.Gui;
|
using Dalamud.Game.Gui;
|
||||||
using Dalamud.Game.Text;
|
using Dalamud.Game.Text;
|
||||||
using Dalamud.Game.Text.SeStringHandling;
|
using Dalamud.Game.Text.SeStringHandling;
|
||||||
|
|
@ -12,8 +13,12 @@ namespace Dalamud.Interface.Internal.Windows.SelfTest.Steps;
|
||||||
internal class ChatSelfTestStep : ISelfTestStep
|
internal class ChatSelfTestStep : ISelfTestStep
|
||||||
{
|
{
|
||||||
private int step = 0;
|
private int step = 0;
|
||||||
private bool subscribed = false;
|
private bool subscribedChatMessage = false;
|
||||||
private bool hasPassed = false;
|
private bool subscribedLogMessage = false;
|
||||||
|
private bool hasSeenEchoMessage = false;
|
||||||
|
private bool hasSeenActionMessage = false;
|
||||||
|
private string actionName = string.Empty;
|
||||||
|
private string actionUser = string.Empty;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public string Name => "Test Chat";
|
public string Name => "Test Chat";
|
||||||
|
|
@ -34,20 +39,55 @@ internal class ChatSelfTestStep : ISelfTestStep
|
||||||
case 1:
|
case 1:
|
||||||
ImGui.Text("Type \"/e DALAMUD\" in chat...");
|
ImGui.Text("Type \"/e DALAMUD\" in chat...");
|
||||||
|
|
||||||
if (!this.subscribed)
|
if (!this.subscribedChatMessage)
|
||||||
{
|
{
|
||||||
this.subscribed = true;
|
this.subscribedChatMessage = true;
|
||||||
chatGui.ChatMessage += this.ChatOnOnChatMessage;
|
chatGui.ChatMessage += this.ChatOnOnChatMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.hasPassed)
|
if (this.hasSeenEchoMessage)
|
||||||
{
|
{
|
||||||
chatGui.ChatMessage -= this.ChatOnOnChatMessage;
|
chatGui.ChatMessage -= this.ChatOnOnChatMessage;
|
||||||
this.subscribed = false;
|
this.subscribedChatMessage = false;
|
||||||
return SelfTestStepResult.Pass;
|
this.step++;
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
ImGui.Text("Use any action (for example Sprint) or be near a player using an action.");
|
||||||
|
|
||||||
|
if (!this.subscribedLogMessage)
|
||||||
|
{
|
||||||
|
this.subscribedLogMessage = true;
|
||||||
|
chatGui.LogMessage += this.ChatOnLogMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.hasSeenActionMessage)
|
||||||
|
{
|
||||||
|
ImGui.Text($"{this.actionUser} used {this.actionName}.");
|
||||||
|
ImGui.Text("Is this correct?");
|
||||||
|
|
||||||
|
if (ImGui.Button("Yes"))
|
||||||
|
{
|
||||||
|
chatGui.LogMessage -= this.ChatOnLogMessage;
|
||||||
|
this.subscribedLogMessage = false;
|
||||||
|
this.step++;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (ImGui.Button("No"))
|
||||||
|
{
|
||||||
|
chatGui.LogMessage -= this.ChatOnLogMessage;
|
||||||
|
this.subscribedLogMessage = false;
|
||||||
|
return SelfTestStepResult.Fail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return SelfTestStepResult.Pass;
|
||||||
}
|
}
|
||||||
|
|
||||||
return SelfTestStepResult.Waiting;
|
return SelfTestStepResult.Waiting;
|
||||||
|
|
@ -59,7 +99,9 @@ internal class ChatSelfTestStep : ISelfTestStep
|
||||||
var chatGui = Service<ChatGui>.Get();
|
var chatGui = Service<ChatGui>.Get();
|
||||||
|
|
||||||
chatGui.ChatMessage -= this.ChatOnOnChatMessage;
|
chatGui.ChatMessage -= this.ChatOnOnChatMessage;
|
||||||
this.subscribed = false;
|
chatGui.LogMessage -= this.ChatOnLogMessage;
|
||||||
|
this.subscribedChatMessage = false;
|
||||||
|
this.subscribedLogMessage = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ChatOnOnChatMessage(
|
private void ChatOnOnChatMessage(
|
||||||
|
|
@ -67,7 +109,17 @@ internal class ChatSelfTestStep : ISelfTestStep
|
||||||
{
|
{
|
||||||
if (type == XivChatType.Echo && message.TextValue == "DALAMUD")
|
if (type == XivChatType.Echo && message.TextValue == "DALAMUD")
|
||||||
{
|
{
|
||||||
this.hasPassed = true;
|
this.hasSeenEchoMessage = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ChatOnLogMessage(ILogMessage message)
|
||||||
|
{
|
||||||
|
if (message.LogMessageId == 533 && message.TryGetStringParameter(0, out var value))
|
||||||
|
{
|
||||||
|
this.hasSeenActionMessage = true;
|
||||||
|
this.actionUser = message.SourceEntity?.Name.ExtractText() ?? "<incorrect>";
|
||||||
|
this.actionName = value.ExtractText();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,14 @@ internal sealed class SettingsTabLook : SettingsTab
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Service<InterfaceManager>.GetNullable()?.SetImmersiveMode(b);
|
if (b)
|
||||||
|
{
|
||||||
|
Service<InterfaceManager>.GetNullable()?.SetImmersiveModeFromSystemTheme();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Service<InterfaceManager>.GetNullable()?.SetImmersiveMode(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -268,6 +268,31 @@ public static unsafe class MemoryHelper
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Compares a UTF-16 character span with a null-terminated UTF-16 string at <paramref name="memoryAddress"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="charSpan">UTF-16 character span (e.g., from a string literal).</param>
|
||||||
|
/// <param name="memoryAddress">Address of null-terminated UTF-16 (wide) string, as used by Windows APIs.</param>
|
||||||
|
/// <returns><see langword="true"/> if equal; otherwise, <see langword="false"/>.</returns>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static unsafe bool EqualsZeroTerminatedWideString(
|
||||||
|
scoped ReadOnlySpan<char> charSpan,
|
||||||
|
nint memoryAddress)
|
||||||
|
{
|
||||||
|
if (memoryAddress == 0)
|
||||||
|
return charSpan.Length == 0;
|
||||||
|
|
||||||
|
char* p = (char*)memoryAddress;
|
||||||
|
|
||||||
|
foreach (char c in charSpan)
|
||||||
|
{
|
||||||
|
if (*p++ != c)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return *p == '\0';
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Read a UTF-8 encoded string from a specified memory address.
|
/// Read a UTF-8 encoded string from a specified memory address.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
using Dalamud.Game.Chat;
|
||||||
using Dalamud.Game.Gui;
|
using Dalamud.Game.Gui;
|
||||||
using Dalamud.Game.Text;
|
using Dalamud.Game.Text;
|
||||||
using Dalamud.Game.Text.SeStringHandling;
|
using Dalamud.Game.Text.SeStringHandling;
|
||||||
|
|
@ -50,6 +51,12 @@ public interface IChatGui : IDalamudService
|
||||||
/// <param name="message">The message sent.</param>
|
/// <param name="message">The message sent.</param>
|
||||||
public delegate void OnMessageUnhandledDelegate(XivChatType type, int timestamp, SeString sender, SeString message);
|
public delegate void OnMessageUnhandledDelegate(XivChatType type, int timestamp, SeString sender, SeString message);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A delegate type used with the <see cref="IChatGui.LogMessage"/> event.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">The message sent.</param>
|
||||||
|
public delegate void OnLogMessageDelegate(ILogMessage message);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event that will be fired when a chat message is sent to chat by the game.
|
/// Event that will be fired when a chat message is sent to chat by the game.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -70,6 +77,11 @@ public interface IChatGui : IDalamudService
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event OnMessageUnhandledDelegate ChatMessageUnhandled;
|
public event OnMessageUnhandledDelegate ChatMessageUnhandled;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event that will be fired when a log message, that is a chat message based on entries in the LogMessage sheet, is sent.
|
||||||
|
/// </summary>
|
||||||
|
public event OnLogMessageDelegate LogMessage;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the ID of the last linked item.
|
/// Gets the ID of the last linked item.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit b6f886afc2b1a54d8fd76c37a260a05f214a559e
|
Subproject commit d83e0c13d3c802d4a483f373edcd129bc4802073
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit 52cb5e0a9a7a1138d8c2406c277307a6c9ad8898
|
Subproject commit 31e50c3f267dd845891b328140106a0cc3b1f35e
|
||||||
Loading…
Add table
Add a link
Reference in a new issue