mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 18:27:23 +01:00
Add NamePlateGui
This commit is contained in:
parent
fd48e7be62
commit
b2e30f7cc1
10 changed files with 1407 additions and 0 deletions
270
Dalamud/Game/Gui/NamePlate/NamePlateGui.cs
Normal file
270
Dalamud/Game/Gui/NamePlate/NamePlateGui.cs
Normal file
|
|
@ -0,0 +1,270 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using Dalamud.Game.Addon.Lifecycle;
|
||||
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||
using Dalamud.Game.ClientState.Objects;
|
||||
using Dalamud.IoC;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Plugin.Services;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||
|
||||
namespace Dalamud.Game.Gui.NamePlate;
|
||||
|
||||
/// <summary>
|
||||
/// Class used to modify the data used when rendering nameplates.
|
||||
/// </summary>
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
internal sealed class NamePlateGui : IInternalDisposableService, INamePlateGui
|
||||
{
|
||||
/// <summary>
|
||||
/// The index for the number array used by the NamePlate addon.
|
||||
/// </summary>
|
||||
public const int NumberArrayIndex = 5;
|
||||
|
||||
/// <summary>
|
||||
/// The index for the string array used by the NamePlate addon.
|
||||
/// </summary>
|
||||
public const int StringArrayIndex = 4;
|
||||
|
||||
/// <summary>
|
||||
/// The index for of the FullUpdate entry in the NamePlate number array.
|
||||
/// </summary>
|
||||
internal const int NumberArrayFullUpdateIndex = 4;
|
||||
|
||||
/// <summary>
|
||||
/// An empty null-terminated string pointer allocated in unmanaged memory, used to tag removed fields.
|
||||
/// </summary>
|
||||
internal static readonly nint EmptyStringPointer = CreateEmptyStringPointer();
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly AddonLifecycle addonLifecycle = Service<AddonLifecycle>.Get();
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly GameGui gameGui = Service<GameGui>.Get();
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly ObjectTable objectTable = Service<ObjectTable>.Get();
|
||||
|
||||
private readonly AddonLifecycleEventListener preRequestedUpdateListener;
|
||||
|
||||
private NamePlateUpdateContext? context;
|
||||
|
||||
private NamePlateUpdateHandler[] updateHandlers = [];
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private NamePlateGui()
|
||||
{
|
||||
this.preRequestedUpdateListener = new AddonLifecycleEventListener(
|
||||
AddonEvent.PreRequestedUpdate,
|
||||
"NamePlate",
|
||||
this.OnPreRequestedUpdate);
|
||||
|
||||
this.addonLifecycle.RegisterListener(this.preRequestedUpdateListener);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event INamePlateGui.OnPlateUpdateDelegate? OnNamePlateUpdate;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event INamePlateGui.OnPlateUpdateDelegate? OnDataUpdate;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public unsafe void RequestRedraw()
|
||||
{
|
||||
var addon = this.gameGui.GetAddonByName("NamePlate");
|
||||
if (addon != 0)
|
||||
{
|
||||
var raptureAtkModule = RaptureAtkModule.Instance();
|
||||
if (raptureAtkModule == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
((AddonNamePlate*)addon)->DoFullUpdate = 1;
|
||||
var namePlateNumberArrayData = raptureAtkModule->AtkArrayDataHolder.NumberArrays[NumberArrayIndex];
|
||||
namePlateNumberArrayData->SetValue(NumberArrayFullUpdateIndex, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
this.addonLifecycle.UnregisterListener(this.preRequestedUpdateListener);
|
||||
}
|
||||
|
||||
private static nint CreateEmptyStringPointer()
|
||||
{
|
||||
var pointer = Marshal.AllocHGlobal(1);
|
||||
Marshal.WriteByte(pointer, 0, 0);
|
||||
return pointer;
|
||||
}
|
||||
|
||||
private void CreateHandlers(NamePlateUpdateContext createdContext)
|
||||
{
|
||||
var handlers = new List<NamePlateUpdateHandler>();
|
||||
for (var i = 0; i < AddonNamePlate.NumNamePlateObjects; i++)
|
||||
{
|
||||
handlers.Add(new NamePlateUpdateHandler(createdContext, i));
|
||||
}
|
||||
|
||||
this.updateHandlers = handlers.ToArray();
|
||||
}
|
||||
|
||||
private void OnPreRequestedUpdate(AddonEvent type, AddonArgs args)
|
||||
{
|
||||
if (this.OnDataUpdate == null && this.OnNamePlateUpdate == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var reqArgs = (AddonRequestedUpdateArgs)args;
|
||||
if (this.context == null)
|
||||
{
|
||||
this.context = new NamePlateUpdateContext(this.objectTable, reqArgs);
|
||||
this.CreateHandlers(this.context);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.context.ResetState(reqArgs);
|
||||
}
|
||||
|
||||
var activeNamePlateCount = this.context.ActiveNamePlateCount;
|
||||
if (activeNamePlateCount == 0)
|
||||
return;
|
||||
|
||||
var activeHandlers = this.updateHandlers[..activeNamePlateCount];
|
||||
|
||||
if (this.context.IsFullUpdate)
|
||||
{
|
||||
foreach (var handler in activeHandlers)
|
||||
{
|
||||
handler.ResetState();
|
||||
}
|
||||
|
||||
this.OnDataUpdate?.Invoke(this.context, activeHandlers);
|
||||
this.OnNamePlateUpdate?.Invoke(this.context, activeHandlers);
|
||||
if (this.context.HasParts)
|
||||
this.ApplyBuilders(activeHandlers);
|
||||
}
|
||||
else
|
||||
{
|
||||
var udpatedHandlers = new List<NamePlateUpdateHandler>(activeNamePlateCount);
|
||||
foreach (var handler in activeHandlers)
|
||||
{
|
||||
handler.ResetState();
|
||||
if (handler.IsUpdating)
|
||||
udpatedHandlers.Add(handler);
|
||||
}
|
||||
|
||||
if (this.OnDataUpdate is not null)
|
||||
{
|
||||
this.OnDataUpdate?.Invoke(this.context, activeHandlers);
|
||||
this.OnNamePlateUpdate?.Invoke(this.context, udpatedHandlers);
|
||||
if (this.context.HasParts)
|
||||
this.ApplyBuilders(activeHandlers);
|
||||
}
|
||||
else if (udpatedHandlers.Count != 0)
|
||||
{
|
||||
var changedHandlersSpan = udpatedHandlers.ToArray().AsSpan();
|
||||
this.OnNamePlateUpdate?.Invoke(this.context, udpatedHandlers);
|
||||
if (this.context.HasParts)
|
||||
this.ApplyBuilders(changedHandlersSpan);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyBuilders(Span<NamePlateUpdateHandler> handlers)
|
||||
{
|
||||
foreach (var handler in handlers)
|
||||
{
|
||||
if (handler.PartsContainer is { } container)
|
||||
{
|
||||
container.ApplyBuilders(handler);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plugin-scoped version of a AddonEventManager service.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[ServiceManager.ScopedService]
|
||||
#pragma warning disable SA1015
|
||||
[ResolveVia<INamePlateGui>]
|
||||
#pragma warning restore SA1015
|
||||
internal class NamePlateGuiPluginScoped : IInternalDisposableService, INamePlateGui
|
||||
{
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly NamePlateGui parentService = Service<NamePlateGui>.Get();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event INamePlateGui.OnPlateUpdateDelegate? OnNamePlateUpdate
|
||||
{
|
||||
add
|
||||
{
|
||||
if (this.OnNamePlateUpdateScoped == null)
|
||||
this.parentService.OnNamePlateUpdate += this.OnNamePlateUpdateForward;
|
||||
this.OnNamePlateUpdateScoped += value;
|
||||
}
|
||||
|
||||
remove
|
||||
{
|
||||
this.OnNamePlateUpdateScoped -= value;
|
||||
if (this.OnNamePlateUpdateScoped == null)
|
||||
this.parentService.OnNamePlateUpdate -= this.OnNamePlateUpdateForward;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event INamePlateGui.OnPlateUpdateDelegate? OnDataUpdate
|
||||
{
|
||||
add
|
||||
{
|
||||
if (this.OnDataUpdateScoped == null)
|
||||
this.parentService.OnDataUpdate += this.OnDataUpdateForward;
|
||||
this.OnDataUpdateScoped += value;
|
||||
}
|
||||
|
||||
remove
|
||||
{
|
||||
this.OnDataUpdateScoped -= value;
|
||||
if (this.OnDataUpdateScoped == null)
|
||||
this.parentService.OnDataUpdate -= this.OnDataUpdateForward;
|
||||
}
|
||||
}
|
||||
|
||||
private event INamePlateGui.OnPlateUpdateDelegate? OnNamePlateUpdateScoped;
|
||||
|
||||
private event INamePlateGui.OnPlateUpdateDelegate? OnDataUpdateScoped;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void RequestRedraw()
|
||||
{
|
||||
this.parentService.RequestRedraw();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void DisposeService()
|
||||
{
|
||||
this.parentService.OnNamePlateUpdate -= this.OnNamePlateUpdateForward;
|
||||
this.OnNamePlateUpdateScoped = null;
|
||||
|
||||
this.parentService.OnDataUpdate -= this.OnDataUpdateForward;
|
||||
this.OnDataUpdateScoped = null;
|
||||
}
|
||||
|
||||
private void OnNamePlateUpdateForward(
|
||||
INamePlateUpdateContext context, IReadOnlyList<INamePlateUpdateHandler> handlers)
|
||||
{
|
||||
this.OnNamePlateUpdateScoped?.Invoke(context, handlers);
|
||||
}
|
||||
|
||||
private void OnDataUpdateForward(
|
||||
INamePlateUpdateContext context, IReadOnlyList<INamePlateUpdateHandler> handlers)
|
||||
{
|
||||
this.OnDataUpdateScoped?.Invoke(context, handlers);
|
||||
}
|
||||
}
|
||||
93
Dalamud/Game/Gui/NamePlate/NamePlateInfoView.cs
Normal file
93
Dalamud/Game/Gui/NamePlate/NamePlateInfoView.cs
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
using Dalamud.Game.Text.SeStringHandling;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||
|
||||
namespace Dalamud.Game.Gui.NamePlate;
|
||||
|
||||
/// <summary>
|
||||
/// Provides a read-only view of the nameplate info object data for a nameplate. Modifications to
|
||||
/// <see cref="NamePlateUpdateHandler"/> fields do not affect this data.
|
||||
/// </summary>
|
||||
public interface INamePlateInfoView
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the displayed name for this nameplate according to the nameplate info object.
|
||||
/// </summary>
|
||||
SeString Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the displayed free company tag for this nameplate according to the nameplate info object.
|
||||
/// </summary>
|
||||
SeString FreeCompanyTag { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the displayed title for this nameplate according to the nameplate info object. In this field, the quote
|
||||
/// characters which appear on either side of the title are NOT included.
|
||||
/// </summary>
|
||||
SeString Title { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the displayed title for this nameplate according to the nameplate info object. In this field, the quote
|
||||
/// characters which appear on either side of the title ARE included.
|
||||
/// </summary>
|
||||
SeString DisplayTitle { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the displayed level text for this nameplate according to the nameplate info object.
|
||||
/// </summary>
|
||||
SeString LevelText { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the flags for this nameplate according to the nameplate info object.
|
||||
/// </summary>
|
||||
int Flags { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this nameplate is considered 'dirty' or not according to the nameplate
|
||||
/// info object.
|
||||
/// </summary>
|
||||
bool IsDirty { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the title for this nameplate is a prefix title or not according to the nameplate
|
||||
/// info object. This value is derived from the <see cref="Flags"/> field.
|
||||
/// </summary>
|
||||
bool IsPrefixTitle { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides a read-only view of the nameplate info object data for a nameplate. Modifications to
|
||||
/// <see cref="NamePlateUpdateHandler"/> fields do not affect this data.
|
||||
/// </summary>
|
||||
internal unsafe class NamePlateInfoView(RaptureAtkModule.NamePlateInfo* info) : INamePlateInfoView
|
||||
{
|
||||
private SeString? name;
|
||||
private SeString? freeCompanyTag;
|
||||
private SeString? title;
|
||||
private SeString? displayTitle;
|
||||
private SeString? levelText;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public SeString Name => this.name ??= SeString.Parse(info->Name);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public SeString FreeCompanyTag => this.freeCompanyTag ??= SeString.Parse(info->FcName);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public SeString Title => this.title ??= SeString.Parse(info->Title);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public SeString DisplayTitle => this.displayTitle ??= SeString.Parse(info->DisplayTitle);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public SeString LevelText => this.levelText ??= SeString.Parse(info->LevelText);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int Flags => info->Flags;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsDirty => info->IsDirty;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsPrefixTitle => ((info->Flags >> (8 * 3)) & 0xFF) == 1;
|
||||
}
|
||||
57
Dalamud/Game/Gui/NamePlate/NamePlateKind.cs
Normal file
57
Dalamud/Game/Gui/NamePlate/NamePlateKind.cs
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
namespace Dalamud.Game.Gui.NamePlate;
|
||||
|
||||
/// <summary>
|
||||
/// An enum describing what kind of game object this nameplate represents.
|
||||
/// </summary>
|
||||
public enum NamePlateKind : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// A player character.
|
||||
/// </summary>
|
||||
PlayerCharacter = 0,
|
||||
|
||||
/// <summary>
|
||||
/// An event NPC or companion.
|
||||
/// </summary>
|
||||
EventNpcCompanion = 1,
|
||||
|
||||
/// <summary>
|
||||
/// A retainer.
|
||||
/// </summary>
|
||||
Retainer = 2,
|
||||
|
||||
/// <summary>
|
||||
/// An enemy battle NPC.
|
||||
/// </summary>
|
||||
BattleNpcEnemy = 3,
|
||||
|
||||
/// <summary>
|
||||
/// A friendly battle NPC.
|
||||
/// </summary>
|
||||
BattleNpcFriendly = 4,
|
||||
|
||||
/// <summary>
|
||||
/// An event object.
|
||||
/// </summary>
|
||||
EventObject = 5,
|
||||
|
||||
/// <summary>
|
||||
/// Treasure.
|
||||
/// </summary>
|
||||
Treasure = 6,
|
||||
|
||||
/// <summary>
|
||||
/// A gathering point.
|
||||
/// </summary>
|
||||
GatheringPoint = 7,
|
||||
|
||||
/// <summary>
|
||||
/// A battle NPC with subkind 6.
|
||||
/// </summary>
|
||||
BattleNpcSubkind6 = 8,
|
||||
|
||||
/// <summary>
|
||||
/// Something else.
|
||||
/// </summary>
|
||||
Other = 9,
|
||||
}
|
||||
46
Dalamud/Game/Gui/NamePlate/NamePlatePartsContainer.cs
Normal file
46
Dalamud/Game/Gui/NamePlate/NamePlatePartsContainer.cs
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
namespace Dalamud.Game.Gui.NamePlate;
|
||||
|
||||
/// <summary>
|
||||
/// A container for parts.
|
||||
/// </summary>
|
||||
internal class NamePlatePartsContainer
|
||||
{
|
||||
private NamePlateSimpleParts? nameParts;
|
||||
private NamePlateQuotedParts? titleParts;
|
||||
private NamePlateQuotedParts? freeCompanyTagParts;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NamePlatePartsContainer"/> class.
|
||||
/// </summary>
|
||||
/// <param name="context">The currently executing update context.</param>
|
||||
public NamePlatePartsContainer(NamePlateUpdateContext context)
|
||||
{
|
||||
context.HasParts = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a parts object for constructing a nameplate name.
|
||||
/// </summary>
|
||||
internal NamePlateSimpleParts Name => this.nameParts ??= new NamePlateSimpleParts(NamePlateStringField.Name);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a parts object for constructing a nameplate title.
|
||||
/// </summary>
|
||||
internal NamePlateQuotedParts Title => this.titleParts ??= new NamePlateQuotedParts(NamePlateStringField.Title, false);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a parts object for constructing a nameplate free company tag.
|
||||
/// </summary>
|
||||
internal NamePlateQuotedParts FreeCompanyTag => this.freeCompanyTagParts ??= new NamePlateQuotedParts(NamePlateStringField.FreeCompanyTag, true);
|
||||
|
||||
/// <summary>
|
||||
/// Applies all container parts.
|
||||
/// </summary>
|
||||
/// <param name="handler">The handler to apply the builders to.</param>
|
||||
internal void ApplyBuilders(NamePlateUpdateHandler handler)
|
||||
{
|
||||
this.nameParts?.Apply(handler);
|
||||
this.freeCompanyTagParts?.Apply(handler);
|
||||
this.titleParts?.Apply(handler);
|
||||
}
|
||||
}
|
||||
73
Dalamud/Game/Gui/NamePlate/NamePlateQuotedParts.cs
Normal file
73
Dalamud/Game/Gui/NamePlate/NamePlateQuotedParts.cs
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
using Dalamud.Game.Text.SeStringHandling;
|
||||
|
||||
namespace Dalamud.Game.Gui.NamePlate;
|
||||
|
||||
/// <summary>
|
||||
/// A part builder for constructing and setting quoted nameplate fields (i.e. free company tag and title).
|
||||
/// </summary>
|
||||
/// <param name="field">The field type which should be set.</param>
|
||||
public class NamePlateQuotedParts(NamePlateStringField field, bool isFreeCompany)
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the opening quote string which appears before the text and opening text-wrap.
|
||||
/// </summary>
|
||||
public SeString? LeftQuote { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the closing quote string which appears after the text and closing text-wrap.
|
||||
/// </summary>
|
||||
public SeString? RightQuote { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the opening and closing SeStrings which will wrap the text, which can be used to apply colors or
|
||||
/// styling to the field's text.
|
||||
/// </summary>
|
||||
public (SeString, SeString)? TextWrap { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets this field's text.
|
||||
/// </summary>
|
||||
public SeString? Text { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Applies the changes from this builder to the actual field.
|
||||
/// </summary>
|
||||
/// <param name="handler">The handler to perform the changes on.</param>
|
||||
internal unsafe void Apply(NamePlateUpdateHandler handler)
|
||||
{
|
||||
if ((nint)handler.GetFieldAsPointer(field) == NamePlateGui.EmptyStringPointer)
|
||||
return;
|
||||
|
||||
var sb = new SeStringBuilder();
|
||||
if (this.LeftQuote is not null)
|
||||
{
|
||||
sb.Append(this.LeftQuote);
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(isFreeCompany ? " «" : "《");
|
||||
}
|
||||
|
||||
if (this.TextWrap is { Item1: var left, Item2: var right })
|
||||
{
|
||||
sb.Append(left);
|
||||
sb.Append(this.Text ?? handler.GetFieldAsSeString(field));
|
||||
sb.Append(right);
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(this.Text ?? handler.GetFieldAsSeString(field));
|
||||
}
|
||||
|
||||
if (this.RightQuote is not null)
|
||||
{
|
||||
sb.Append(this.RightQuote);
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(isFreeCompany ? "»" : "》");
|
||||
}
|
||||
|
||||
handler.SetField(field, sb.Build());
|
||||
}
|
||||
}
|
||||
44
Dalamud/Game/Gui/NamePlate/NamePlateSimpleParts.cs
Normal file
44
Dalamud/Game/Gui/NamePlate/NamePlateSimpleParts.cs
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
using Dalamud.Game.Text.SeStringHandling;
|
||||
|
||||
namespace Dalamud.Game.Gui.NamePlate;
|
||||
|
||||
/// <summary>
|
||||
/// A part builder for constructing and setting a simple (unquoted) nameplate field.
|
||||
/// </summary>
|
||||
/// <param name="field">The field type which should be set.</param>
|
||||
public class NamePlateSimpleParts(NamePlateStringField field)
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the opening and closing SeStrings which will wrap the text, which can be used to apply colors or
|
||||
/// styling to the field's text.
|
||||
/// </summary>
|
||||
public (SeString, SeString)? TextWrap { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets this field's text.
|
||||
/// </summary>
|
||||
public SeString? Text { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Applies the changes from this builder to the actual field.
|
||||
/// </summary>
|
||||
/// <param name="handler">The handler to perform the changes on.</param>
|
||||
internal unsafe void Apply(NamePlateUpdateHandler handler)
|
||||
{
|
||||
if ((nint)handler.GetFieldAsPointer(field) == NamePlateGui.EmptyStringPointer)
|
||||
return;
|
||||
|
||||
if (this.TextWrap is { Item1: var left, Item2: var right })
|
||||
{
|
||||
var sb = new SeStringBuilder();
|
||||
sb.Append(left);
|
||||
sb.Append(this.Text ?? handler.GetFieldAsSeString(field));
|
||||
sb.Append(right);
|
||||
handler.SetField(field, sb.Build());
|
||||
}
|
||||
else if (this.Text is not null)
|
||||
{
|
||||
handler.SetField(field, this.Text);
|
||||
}
|
||||
}
|
||||
}
|
||||
38
Dalamud/Game/Gui/NamePlate/NamePlateStringField.cs
Normal file
38
Dalamud/Game/Gui/NamePlate/NamePlateStringField.cs
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
namespace Dalamud.Game.Gui.NamePlate;
|
||||
|
||||
/// <summary>
|
||||
/// An enum describing the string fields available in nameplate data. The <see cref="NamePlateKind"/> and various flags
|
||||
/// determine which fields will actually be rendered.
|
||||
/// </summary>
|
||||
public enum NamePlateStringField
|
||||
{
|
||||
/// <summary>
|
||||
/// The object's name.
|
||||
/// </summary>
|
||||
Name = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The object's title.
|
||||
/// </summary>
|
||||
Title = 50,
|
||||
|
||||
/// <summary>
|
||||
/// The object's free company tag.
|
||||
/// </summary>
|
||||
FreeCompanyTag = 100,
|
||||
|
||||
/// <summary>
|
||||
/// The object's status prefix.
|
||||
/// </summary>
|
||||
StatusPrefix = 150,
|
||||
|
||||
/// <summary>
|
||||
/// The object's target suffix.
|
||||
/// </summary>
|
||||
TargetSuffix = 200,
|
||||
|
||||
/// <summary>
|
||||
/// The object's level prefix.
|
||||
/// </summary>
|
||||
LevelPrefix = 250,
|
||||
}
|
||||
146
Dalamud/Game/Gui/NamePlate/NamePlateUpdateContext.cs
Normal file
146
Dalamud/Game/Gui/NamePlate/NamePlateUpdateContext.cs
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||
using Dalamud.Game.ClientState.Objects;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
|
||||
namespace Dalamud.Game.Gui.NamePlate;
|
||||
|
||||
/// <summary>
|
||||
/// Contains information related to the pending nameplate data update. This is only valid for a single frame and should
|
||||
/// not be kept across frames.
|
||||
/// </summary>
|
||||
public interface INamePlateUpdateContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the number of active nameplates. The actual number visible may be lower than this in cases where some
|
||||
/// nameplates are hidden by default (based on in-game "Display Name Settings" and so on).
|
||||
/// </summary>
|
||||
int ActiveNamePlateCount { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the game is currently performing a full update of all active nameplates.
|
||||
/// </summary>
|
||||
bool IsFullUpdate { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the NamePlate addon.
|
||||
/// </summary>
|
||||
nint AddonAddress { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the NamePlate addon's number array data container.
|
||||
/// </summary>
|
||||
nint NumberArrayDataAddress { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the NamePlate addon's string array data container.
|
||||
/// </summary>
|
||||
nint StringArrayDataAddress { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the first entry in the NamePlate addon's int array.
|
||||
/// </summary>
|
||||
nint NumberArrayDataEntryAddress { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains information related to the pending nameplate data update. This is only valid for a single frame and should
|
||||
/// not be kept across frames.
|
||||
/// </summary>
|
||||
internal unsafe class NamePlateUpdateContext : INamePlateUpdateContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NamePlateUpdateContext"/> class.
|
||||
/// </summary>
|
||||
/// <param name="objectTable">An object table.</param>
|
||||
/// <param name="args">The addon lifecycle arguments for the update request.</param>
|
||||
internal NamePlateUpdateContext(ObjectTable objectTable, AddonRequestedUpdateArgs args)
|
||||
{
|
||||
this.ObjectTable = objectTable;
|
||||
this.RaptureAtkModule = FFXIVClientStructs.FFXIV.Client.UI.RaptureAtkModule.Instance();
|
||||
this.ResetState(args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of active nameplates. The actual number visible may be lower than this in cases where some
|
||||
/// nameplates are hidden by default (based on in-game "Display Name Settings" and so on).
|
||||
/// </summary>
|
||||
public int ActiveNamePlateCount { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the game is currently performing a full update of all active nameplates.
|
||||
/// </summary>
|
||||
public bool IsFullUpdate { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the NamePlate addon.
|
||||
/// </summary>
|
||||
public nint AddonAddress => (nint)this.Addon;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the NamePlate addon's number array data container.
|
||||
/// </summary>
|
||||
public nint NumberArrayDataAddress => (nint)this.NumberData;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the NamePlate addon's string array data container.
|
||||
/// </summary>
|
||||
public nint StringArrayDataAddress => (nint)this.StringData;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the first entry in the NamePlate addon's int array.
|
||||
/// </summary>
|
||||
public nint NumberArrayDataEntryAddress => (nint)this.NumberStruct;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the RaptureAtkModule.
|
||||
/// </summary>
|
||||
internal RaptureAtkModule* RaptureAtkModule { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ObjectTable.
|
||||
/// </summary>
|
||||
internal ObjectTable ObjectTable { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a pointer to the NamePlate addon.
|
||||
/// </summary>
|
||||
internal AddonNamePlate* Addon { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a pointer to the NamePlate addon's number array data container.
|
||||
/// </summary>
|
||||
internal NumberArrayData* NumberData { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a pointer to the NamePlate addon's string array data container.
|
||||
/// </summary>
|
||||
internal StringArrayData* StringData { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a pointer to the NamePlate addon's number array entries as a struct.
|
||||
/// </summary>
|
||||
internal AddonNamePlate.NamePlateIntArrayData* NumberStruct { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether any handler in the current context has instantiated a part builder.
|
||||
/// </summary>
|
||||
internal bool HasParts { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Resets the state of the context based on the provided addon lifecycle arguments.
|
||||
/// </summary>
|
||||
/// <param name="args">The addon lifecycle arguments for the update request.</param>
|
||||
internal void ResetState(AddonRequestedUpdateArgs args)
|
||||
{
|
||||
this.Addon = (AddonNamePlate*)args.Addon;
|
||||
this.NumberData = ((NumberArrayData**)args.NumberArrayData)![NamePlateGui.NumberArrayIndex];
|
||||
this.NumberStruct = (AddonNamePlate.NamePlateIntArrayData*)this.NumberData->IntArray;
|
||||
this.StringData = ((StringArrayData**)args.StringArrayData)![NamePlateGui.StringArrayIndex];
|
||||
this.HasParts = false;
|
||||
|
||||
this.ActiveNamePlateCount = this.NumberStruct->ActiveNamePlateCount;
|
||||
this.IsFullUpdate = this.Addon->DoFullUpdate != 0;
|
||||
}
|
||||
}
|
||||
603
Dalamud/Game/Gui/NamePlate/NamePlateUpdateHandler.cs
Normal file
603
Dalamud/Game/Gui/NamePlate/NamePlateUpdateHandler.cs
Normal file
|
|
@ -0,0 +1,603 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||
using FFXIVClientStructs.Interop;
|
||||
|
||||
namespace Dalamud.Game.Gui.NamePlate;
|
||||
|
||||
/// <summary>
|
||||
/// A class representing a single nameplate. Provides mechanisms to look up the game object associated with the
|
||||
/// nameplate and allows for modification of various backing fields in number and string array data, which in turn
|
||||
/// affect aspects of the nameplate's appearance when drawn. Instances of this class are only valid for a single frame
|
||||
/// and should not be kept across frames.
|
||||
/// </summary>
|
||||
public interface INamePlateUpdateHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the GameObjectId of the game object associated with this nameplate.
|
||||
/// </summary>
|
||||
ulong GameObjectId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="IGameObject"/> associated with this nameplate, if possible. Performs an object table scan
|
||||
/// and caches the result if successful.
|
||||
/// </summary>
|
||||
IGameObject? GameObject { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a read-only view of the nameplate info object data for a nameplate. Modifications to
|
||||
/// <see cref="NamePlateUpdateHandler"/> fields do not affect fields in the returned view.
|
||||
/// </summary>
|
||||
INamePlateInfoView InfoView { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the index for this nameplate data in the backing number and string array data. This is not the same as the
|
||||
/// rendered or object index, which can be retrieved from <see cref="NamePlateIndex"/>.
|
||||
/// </summary>
|
||||
int ArrayIndex { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="IBattleChara"/> associated with this nameplate, if possible. Returns null if the nameplate
|
||||
/// has an associated <see cref="IGameObject"/>, but that object cannot be assigned to <see cref="IBattleChara"/>.
|
||||
/// </summary>
|
||||
IBattleChara? BattleChara { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="IPlayerCharacter"/> associated with this nameplate, if possible. Returns null if the
|
||||
/// nameplate has an associated <see cref="IGameObject"/>, but that object cannot be assigned to
|
||||
/// <see cref="IPlayerCharacter"/>.
|
||||
/// </summary>
|
||||
IPlayerCharacter? PlayerCharacter { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the nameplate info struct.
|
||||
/// </summary>
|
||||
nint NamePlateInfoAddress { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the first entry associated with this nameplate in the NamePlate addon's int array.
|
||||
/// </summary>
|
||||
nint NamePlateObjectAddress { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating what kind of nameplate this is, based on the kind of object it is associated with.
|
||||
/// </summary>
|
||||
NamePlateKind NamePlateKind { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the update flags for this nameplate.
|
||||
/// </summary>
|
||||
int UpdateFlags { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the overall text color for this nameplate. If this value is changed, the appropriate update flag
|
||||
/// will be set so that the game will reflect this change immediately.
|
||||
/// </summary>
|
||||
uint TextColor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the overall text edge color for this nameplate. If this value is changed, the appropriate update
|
||||
/// flag will be set so that the game will reflect this change immediately.
|
||||
/// </summary>
|
||||
uint EdgeColor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the icon ID for the nameplate's marker icon, which is the large icon used to indicate quest
|
||||
/// availability and so on. This value is read from and reset by the game every frame, not just when a nameplate
|
||||
/// changes.
|
||||
/// </summary>
|
||||
int MarkerIconId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the icon ID for the nameplate's name icon, which is the small icon shown to the left of the name.
|
||||
/// </summary>
|
||||
int NameIconId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the nameplate index, which is the index used for rendering and looking up entries in the object array. For
|
||||
/// number and string array data, <see cref="ArrayIndex"/> is used.
|
||||
/// </summary>
|
||||
int NamePlateIndex { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the draw flags for this nameplate.
|
||||
/// </summary>
|
||||
int DrawFlags { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the visibility flags for this nameplate.
|
||||
/// </summary>
|
||||
int VisibilityFlags { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this nameplate is undergoing a major update or not. This is usually true when a
|
||||
/// nameplate has just appeared or something meaningful about the entity has changed (e.g. its job or status). This
|
||||
/// flag is reset by the game during the update process (during requested update and before draw).
|
||||
/// </summary>
|
||||
bool IsUpdating { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the title (when visible) will be displayed above the object's name (a
|
||||
/// prefix title) instead of below the object's name (a suffix title).
|
||||
/// </summary>
|
||||
bool IsPrefixTitle { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the title should be displayed at all.
|
||||
/// </summary>
|
||||
bool DisplayTitle { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name for this nameplate.
|
||||
/// </summary>
|
||||
SeString Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a builder which can be used to help cooperatively build a new name for this nameplate even when other
|
||||
/// plugins modifying the name are present. Specifically, this builder allows setting text and text-wrapping
|
||||
/// payloads (e.g. for setting text color) separately.
|
||||
/// </summary>
|
||||
NamePlateSimpleParts NameParts { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the title for this nameplate.
|
||||
/// </summary>
|
||||
SeString Title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a builder which can be used to help cooperatively build a new title for this nameplate even when other
|
||||
/// plugins modifying the title are present. Specifically, this builder allows setting text, text-wrapping
|
||||
/// payloads (e.g. for setting text color), and opening and closing quote sequences separately.
|
||||
/// </summary>
|
||||
NamePlateQuotedParts TitleParts { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the free company tag for this nameplate.
|
||||
/// </summary>
|
||||
SeString FreeCompanyTag { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a builder which can be used to help cooperatively build a new FC tag for this nameplate even when other
|
||||
/// plugins modifying the FC tag are present. Specifically, this builder allows setting text, text-wrapping
|
||||
/// payloads (e.g. for setting text color), and opening and closing quote sequences separately.
|
||||
/// </summary>
|
||||
NamePlateQuotedParts FreeCompanyTagParts { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the status prefix for this nameplate. This prefix is used by the game to add BitmapFontIcon-based
|
||||
/// online status icons to player nameplates.
|
||||
/// </summary>
|
||||
SeString StatusPrefix { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the target suffix for this nameplate. This suffix is used by the game to add the squared-letter
|
||||
/// target tags to the end of combat target nameplates.
|
||||
/// </summary>
|
||||
SeString TargetSuffix { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the level prefix for this nameplate. This "Lv60" style prefix is added to enemy and friendly battle
|
||||
/// NPC nameplates to indicate the NPC level.
|
||||
/// </summary>
|
||||
SeString LevelPrefix { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Removes the contents of the name field for this nameplate. This differs from simply setting the field
|
||||
/// to an empty string because it writes a special value to memory, and other setters (except SetField variants)
|
||||
/// will refuse to overwrite this value. Therefore, fields removed this way are more likely to stay removed.
|
||||
/// </summary>
|
||||
void RemoveName();
|
||||
|
||||
/// <summary>
|
||||
/// Removes the contents of the title field for this nameplate. This differs from simply setting the field
|
||||
/// to an empty string because it writes a special value to memory, and other setters (except SetField variants)
|
||||
/// will refuse to overwrite this value. Therefore, fields removed this way are more likely to stay removed.
|
||||
/// </summary>
|
||||
void RemoveTitle();
|
||||
|
||||
/// <summary>
|
||||
/// Removes the contents of the FC tag field for this nameplate. This differs from simply setting the field
|
||||
/// to an empty string because it writes a special value to memory, and other setters (except SetField variants)
|
||||
/// will refuse to overwrite this value. Therefore, fields removed this way are more likely to stay removed.
|
||||
/// </summary>
|
||||
void RemoveFreeCompanyTag();
|
||||
|
||||
/// <summary>
|
||||
/// Removes the contents of the status prefix field for this nameplate. This differs from simply setting the field
|
||||
/// to an empty string because it writes a special value to memory, and other setters (except SetField variants)
|
||||
/// will refuse to overwrite this value. Therefore, fields removed this way are more likely to stay removed.
|
||||
/// </summary>
|
||||
void RemoveStatusPrefix();
|
||||
|
||||
/// <summary>
|
||||
/// Removes the contents of the target suffix field for this nameplate. This differs from simply setting the field
|
||||
/// to an empty string because it writes a special value to memory, and other setters (except SetField variants)
|
||||
/// will refuse to overwrite this value. Therefore, fields removed this way are more likely to stay removed.
|
||||
/// </summary>
|
||||
void RemoveTargetSuffix();
|
||||
|
||||
/// <summary>
|
||||
/// Removes the contents of the level prefix field for this nameplate. This differs from simply setting the field
|
||||
/// to an empty string because it writes a special value to memory, and other setters (except SetField variants)
|
||||
/// will refuse to overwrite this value. Therefore, fields removed this way are more likely to stay removed.
|
||||
/// </summary>
|
||||
void RemoveLevelPrefix();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a pointer to the string array value in the provided field.
|
||||
/// </summary>
|
||||
/// <param name="field">The field to read from.</param>
|
||||
/// <returns>A pointer to a sequence of non-null bytes.</returns>
|
||||
unsafe byte* GetFieldAsPointer(NamePlateStringField field);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a byte span containing the string array value in the provided field.
|
||||
/// </summary>
|
||||
/// <param name="field">The field to read from.</param>
|
||||
/// <returns>A ReadOnlySpan containing a sequence of non-null bytes.</returns>
|
||||
ReadOnlySpan<byte> GetFieldAsSpan(NamePlateStringField field);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a UTF8 string copy of the string array value in the provided field.
|
||||
/// </summary>
|
||||
/// <param name="field">The field to read from.</param>
|
||||
/// <returns>A copy of the string array value as a string.</returns>
|
||||
string GetFieldAsString(NamePlateStringField field);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a parsed SeString copy of the string array value in the provided field.
|
||||
/// </summary>
|
||||
/// <param name="field">The field to read from.</param>
|
||||
/// <returns>A copy of the string array value as a parsed SeString.</returns>
|
||||
SeString GetFieldAsSeString(NamePlateStringField field);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the string array value for the provided field.
|
||||
/// </summary>
|
||||
/// <param name="field">The field to write to.</param>
|
||||
/// <param name="value">The string to write.</param>
|
||||
void SetField(NamePlateStringField field, string value);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the string array value for the provided field.
|
||||
/// </summary>
|
||||
/// <param name="field">The field to write to.</param>
|
||||
/// <param name="value">The SeString to write.</param>
|
||||
void SetField(NamePlateStringField field, SeString value);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the string array value for the provided field.
|
||||
/// </summary>
|
||||
/// <param name="field">The field to write to.</param>
|
||||
/// <param name="value">The ReadOnlySpan of bytes to write.</param>
|
||||
void SetField(NamePlateStringField field, ReadOnlySpan<byte> value);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the string array value for the provided field.
|
||||
/// </summary>
|
||||
/// <param name="field">The field to write to.</param>
|
||||
/// <param name="value">The pointer to a null-terminated sequence of bytes to write.</param>
|
||||
unsafe void SetField(NamePlateStringField field, byte* value);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the string array value for the provided field to a fixed pointer to an empty string in unmanaged memory.
|
||||
/// Other methods may notice this fixed pointer and refuse to overwrite it, preserving the emptiness of the field.
|
||||
/// </summary>
|
||||
/// <param name="field">The field to write to.</param>
|
||||
void RemoveField(NamePlateStringField field);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A class representing a single nameplate. Provides mechanisms to look up the game object associated with the
|
||||
/// nameplate and allows for modification of various backing fields in number and string array data, which in turn
|
||||
/// affect aspects of the nameplate's appearance when drawn. Instances of this class are only valid for a single frame
|
||||
/// and should not be kept across frames.
|
||||
/// </summary>
|
||||
internal unsafe class NamePlateUpdateHandler : INamePlateUpdateHandler
|
||||
{
|
||||
private readonly NamePlateUpdateContext context;
|
||||
|
||||
private ulong? gameObjectId;
|
||||
private IGameObject? gameObject;
|
||||
private NamePlateInfoView? infoView;
|
||||
private NamePlatePartsContainer? partsContainer;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NamePlateUpdateHandler"/> class.
|
||||
/// </summary>
|
||||
/// <param name="context">The current update context.</param>
|
||||
/// <param name="arrayIndex">The index for this nameplate data in the backing number and string array data. This is
|
||||
/// not the same as the rendered index, which can be retrieved from <see cref="NamePlateIndex"/>.</param>
|
||||
internal NamePlateUpdateHandler(NamePlateUpdateContext context, int arrayIndex)
|
||||
{
|
||||
this.context = context;
|
||||
this.ArrayIndex = arrayIndex;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int ArrayIndex { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ulong GameObjectId => this.gameObjectId ??= this.NamePlateInfo->ObjectId;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IGameObject? GameObject => this.gameObject ??= this.context.ObjectTable.SearchById(this.GameObjectId);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IBattleChara? BattleChara => this.GameObject as IBattleChara;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IPlayerCharacter? PlayerCharacter => this.GameObject as IPlayerCharacter;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public INamePlateInfoView InfoView => this.infoView ??= new NamePlateInfoView(this.NamePlateInfo);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public nint NamePlateInfoAddress => (nint)this.NamePlateInfo;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public nint NamePlateObjectAddress => (nint)this.NamePlateObject;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public NamePlateKind NamePlateKind => (NamePlateKind)this.ObjectData->NamePlateKind;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int UpdateFlags
|
||||
{
|
||||
get => this.ObjectData->UpdateFlags;
|
||||
private set => this.ObjectData->UpdateFlags = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public uint TextColor
|
||||
{
|
||||
get => this.ObjectData->NameTextColor;
|
||||
set
|
||||
{
|
||||
if (value != this.TextColor) this.UpdateFlags |= 2;
|
||||
this.ObjectData->NameTextColor = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public uint EdgeColor
|
||||
{
|
||||
get => this.ObjectData->NameEdgeColor;
|
||||
set
|
||||
{
|
||||
if (value != this.EdgeColor) this.UpdateFlags |= 2;
|
||||
this.ObjectData->NameEdgeColor = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int MarkerIconId
|
||||
{
|
||||
get => this.ObjectData->MarkerIconId;
|
||||
set => this.ObjectData->MarkerIconId = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int NameIconId
|
||||
{
|
||||
get => this.ObjectData->NameIconId;
|
||||
set => this.ObjectData->NameIconId = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int NamePlateIndex => this.ObjectData->NamePlateObjectIndex;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int DrawFlags
|
||||
{
|
||||
get => this.ObjectData->DrawFlags;
|
||||
private set => this.ObjectData->DrawFlags = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int VisibilityFlags
|
||||
{
|
||||
get => ObjectData->VisibilityFlags;
|
||||
set => ObjectData->VisibilityFlags = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsUpdating => (this.UpdateFlags & 1) != 0;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsPrefixTitle
|
||||
{
|
||||
get => (this.DrawFlags & 1) != 0;
|
||||
set => this.DrawFlags = value ? this.DrawFlags | 1 : this.DrawFlags & ~1;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool DisplayTitle
|
||||
{
|
||||
get => (this.DrawFlags & 0x80) == 0;
|
||||
set => this.DrawFlags = value ? this.DrawFlags & ~0x80 : this.DrawFlags | 0x80;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public SeString Name
|
||||
{
|
||||
get => this.GetFieldAsSeString(NamePlateStringField.Name);
|
||||
set => this.WeakSetField(NamePlateStringField.Name, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public NamePlateSimpleParts NameParts => this.PartsContainer.Name;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public SeString Title
|
||||
{
|
||||
get => this.GetFieldAsSeString(NamePlateStringField.Title);
|
||||
set => this.WeakSetField(NamePlateStringField.Title, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public NamePlateQuotedParts TitleParts => this.PartsContainer.Title;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public SeString FreeCompanyTag
|
||||
{
|
||||
get => this.GetFieldAsSeString(NamePlateStringField.FreeCompanyTag);
|
||||
set => this.WeakSetField(NamePlateStringField.FreeCompanyTag, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public NamePlateQuotedParts FreeCompanyTagParts => this.PartsContainer.FreeCompanyTag;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public SeString StatusPrefix
|
||||
{
|
||||
get => this.GetFieldAsSeString(NamePlateStringField.StatusPrefix);
|
||||
set => this.WeakSetField(NamePlateStringField.StatusPrefix, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public SeString TargetSuffix
|
||||
{
|
||||
get => this.GetFieldAsSeString(NamePlateStringField.TargetSuffix);
|
||||
set => this.WeakSetField(NamePlateStringField.TargetSuffix, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public SeString LevelPrefix
|
||||
{
|
||||
get => this.GetFieldAsSeString(NamePlateStringField.LevelPrefix);
|
||||
set => this.WeakSetField(NamePlateStringField.LevelPrefix, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or (lazily) creates a part builder container for this nameplate.
|
||||
/// </summary>
|
||||
internal NamePlatePartsContainer PartsContainer =>
|
||||
this.partsContainer ??= new NamePlatePartsContainer(this.context);
|
||||
|
||||
private RaptureAtkModule.NamePlateInfo* NamePlateInfo =>
|
||||
this.context.RaptureAtkModule->NamePlateInfoEntries.GetPointer(this.NamePlateIndex);
|
||||
|
||||
private AddonNamePlate.NamePlateObject* NamePlateObject =>
|
||||
&this.context.Addon->NamePlateObjectArray[this.NamePlateIndex];
|
||||
|
||||
private AddonNamePlate.NamePlateIntArrayData.NamePlateObjectIntArrayData* ObjectData =>
|
||||
this.context.NumberStruct->ObjectData.GetPointer(this.ArrayIndex);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void RemoveName() => this.RemoveField(NamePlateStringField.Name);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void RemoveTitle() => this.RemoveField(NamePlateStringField.Title);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void RemoveFreeCompanyTag() => this.RemoveField(NamePlateStringField.FreeCompanyTag);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void RemoveStatusPrefix() => this.RemoveField(NamePlateStringField.StatusPrefix);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void RemoveTargetSuffix() => this.RemoveField(NamePlateStringField.TargetSuffix);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void RemoveLevelPrefix() => this.RemoveField(NamePlateStringField.LevelPrefix);
|
||||
|
||||
/// <inheritdoc/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public byte* GetFieldAsPointer(NamePlateStringField field)
|
||||
{
|
||||
return this.context.StringData->StringArray[this.ArrayIndex + (int)field];
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ReadOnlySpan<byte> GetFieldAsSpan(NamePlateStringField field)
|
||||
{
|
||||
return MemoryMarshal.CreateReadOnlySpanFromNullTerminated(this.GetFieldAsPointer(field));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public string GetFieldAsString(NamePlateStringField field)
|
||||
{
|
||||
return Encoding.UTF8.GetString(this.GetFieldAsSpan(field));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public SeString GetFieldAsSeString(NamePlateStringField field)
|
||||
{
|
||||
return SeString.Parse(this.GetFieldAsSpan(field));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetField(NamePlateStringField field, string value)
|
||||
{
|
||||
this.context.StringData->SetValue(this.ArrayIndex + (int)field, value, true, true, true);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetField(NamePlateStringField field, SeString value)
|
||||
{
|
||||
this.context.StringData->SetValue(this.ArrayIndex + (int)field, value.Encode(), true, true, true);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetField(NamePlateStringField field, ReadOnlySpan<byte> value)
|
||||
{
|
||||
this.context.StringData->SetValue(this.ArrayIndex + (int)field, value, true, true, true);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetField(NamePlateStringField field, byte* value)
|
||||
{
|
||||
this.context.StringData->SetValue(this.ArrayIndex + (int)field, value, true, true, true);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void RemoveField(NamePlateStringField field)
|
||||
{
|
||||
this.context.StringData->SetValue(
|
||||
this.ArrayIndex + (int)field,
|
||||
(byte*)NamePlateGui.EmptyStringPointer,
|
||||
true,
|
||||
false,
|
||||
true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the state of this handler for re-use in a new update.
|
||||
/// </summary>
|
||||
internal void ResetState()
|
||||
{
|
||||
this.gameObjectId = null;
|
||||
this.gameObject = null;
|
||||
this.infoView = null;
|
||||
this.partsContainer = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the string array value for the provided field, unless it was already set to the special empty string
|
||||
/// pointer used by the Remove methods.
|
||||
/// </summary>
|
||||
/// <param name="field">The field to write to.</param>
|
||||
/// <param name="value">The SeString to write.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void WeakSetField(NamePlateStringField field, SeString value)
|
||||
{
|
||||
if ((nint)this.GetFieldAsPointer(field) == NamePlateGui.EmptyStringPointer)
|
||||
return;
|
||||
this.context.StringData->SetValue(this.ArrayIndex + (int)field, value.Encode(), true, true, true);
|
||||
}
|
||||
}
|
||||
37
Dalamud/Plugin/Services/INamePlateGui.cs
Normal file
37
Dalamud/Plugin/Services/INamePlateGui.cs
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
using Dalamud.Game.Gui.NamePlate;
|
||||
|
||||
namespace Dalamud.Plugin.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Class used to modify the data used when rendering nameplates.
|
||||
/// </summary>
|
||||
public interface INamePlateGui
|
||||
{
|
||||
/// <summary>
|
||||
/// The delegate used for receiving nameplate update events.
|
||||
/// </summary>
|
||||
/// <param name="context">An object containing information about the pending data update.</param>
|
||||
/// <param name="handlers>">A list of handlers used for updating nameplate data.</param>
|
||||
public delegate void OnPlateUpdateDelegate(
|
||||
INamePlateUpdateContext context, IReadOnlyList<INamePlateUpdateHandler> handlers);
|
||||
|
||||
/// <summary>
|
||||
/// An event which fires when nameplate data is updated and at least one nameplate has important updates. The
|
||||
/// subscriber is provided with a list of handlers for nameplates with important updates.
|
||||
/// </summary>
|
||||
event OnPlateUpdateDelegate? OnNamePlateUpdate;
|
||||
|
||||
/// <summary>
|
||||
/// An event which fires when nameplate data is updated. The subscriber is provided with a list of handlers for all
|
||||
/// nameplates. This event is likely to fire every frame even when no nameplates are actually updated, so in most
|
||||
/// cases <see cref="OnNamePlateUpdate"/> is preferred.
|
||||
/// </summary>
|
||||
event OnPlateUpdateDelegate? OnDataUpdate;
|
||||
|
||||
/// <summary>
|
||||
/// Requests that all nameplates should be redrawn on the following frame.
|
||||
/// </summary>
|
||||
void RequestRedraw();
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue