Skip to content

Commit

Permalink
add basic nameplate service
Browse files Browse the repository at this point in the history
  • Loading branch information
Pilzinsel64 committed Jun 8, 2024
1 parent c21e525 commit 967a9fc
Show file tree
Hide file tree
Showing 7 changed files with 411 additions and 0 deletions.
27 changes: 27 additions & 0 deletions Dalamud/Game/Gui/Nameplates/EventArgs/NameplateUpdateEventArgs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using Dalamud.Game.Gui.Nameplates.Model;

namespace Dalamud.Game.Gui.Nameplates.EventArgs;

/// <summary>
/// Event arguments for <see cref="INameplatesGui.OnNameplateUpdate"/>.
/// </summary>
/// <param name="nameplateInfo">dddd.</param>
/// <param name="nameplateObject">dedd.</param>
public class NameplateUpdateEventArgs(NameplateInfo nameplateInfo, NameplateObject nameplateObject)
{
/// <summary>
/// Gets an object that represents the nameplate object (mostly like the player or NPC).
/// </summary>
public NameplateObject NameplateObject { get; } = nameplateObject;

/// <summary>
/// Gets an object that holds some infos about the nameplate.
/// </summary>
public NameplateInfo NameplateInfo { get; } = nameplateInfo;

/// <summary>
/// Gets or sets a value indicating whether <see cref="NameplateInfo"/> has been changed.
/// <br/>Set this to <see cref="T:true"/> if you changes to <see cref="NameplateInfo"/> should take affect.
/// </summary>
public bool HasChanged { get; set; }
}
46 changes: 46 additions & 0 deletions Dalamud/Game/Gui/Nameplates/Model/NameplateInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using Dalamud.Game.Text.SeStringHandling;

namespace Dalamud.Game.Gui.Nameplates.Model;

/// <summary>
/// Represents a nameplate by providing some information about the visual look.
/// </summary>
public class NameplateInfo
{
/// <summary>
/// Gets or sets the title text of the Nameplate.
/// </summary>
public SeString Title { get; set; }

/// <summary>
/// Gets or sets the name text of the Nameplate.
/// </summary>
public SeString Name { get; set; }

/// <summary>
/// Gets or sets the free company text.
/// </summary>
public SeString FreeCompany { get; set; }

/// <summary>
/// Gets or sets the prefix text. Mostly used for the job name shortcuts or some status icons that can be shown within a text.
/// </summary>
public SeString Prefix { get; set; }

/// <summary>
/// Gets or sets a value indicating whether the title is shown above the name.
/// <br/>If the value is true, the title is shown above the name. Otherwise the title is shown below the name.
/// </summary>
public bool IsTitleAboveName { get; set; }

/// <summary>
/// Gets or sets a value indicating whether the title is visible.
/// <br/>If the title value is true, the title is visible. Otherwise the title text is not visible and ignored.
/// </summary>
public bool IsTitleVisible { get; set; }

/// <summary>
/// Gets or sets the icon shown on the nameplate. Mostly used for the status icon.
/// </summary>
public StatusIcons IconID { get; set; }
}
13 changes: 13 additions & 0 deletions Dalamud/Game/Gui/Nameplates/Model/NameplateObject.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace Dalamud.Game.Gui.Nameplates.Model;

/// <summary>
/// Represents a nameplate object (mostly like player or NPC).
/// </summary>
/// <param name="pointer">The pointer for the nameplate object.</param>
public class NameplateObject(IntPtr pointer)
{
/// <summary>
/// Gets the pointer for the nameplate object.
/// </summary>
public IntPtr Pointer { get; } = pointer;
}
91 changes: 91 additions & 0 deletions Dalamud/Game/Gui/Nameplates/Model/StatusIcons.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;

namespace Dalamud.Game.Gui.Nameplates.Model;

/// <summary>
/// Possible status icons that are able to show as icon on the Nameplate.
/// </summary>
[JsonConverter(typeof(StringEnumConverter))]
public enum StatusIcons
{
/// <summary>
/// The status for a player that is disconnecting.
/// </summary>
Disconnecting = 061503,

/// <summary>
/// The status for a player that is in a duty.
/// </summary>
InDuty = 061506,

/// <summary>
/// The status for a player that is viewing a cutscene.
/// </summary>
ViewingCutscene = 061508,

/// <summary>
/// The status for a player that is marked as busy.
/// </summary>
Busy = 061509,

/// <summary>
/// The status for a player that is afk.
/// </summary>
Idle = 061511,

/// <summary>
/// The status for a player that is registred for a duty and searching a group.
/// </summary>
DutyFinder = 061517,

/// <summary>
/// The status for a player that is leader of a party.
/// </summary>
PartyLeader = 061521,

/// <summary>
/// The status for a player that is member of a party.
/// </summary>
PartyMember = 061522,

/// <summary>
/// The status for a player that is marked as role-play.
/// </summary>
RolePlaying = 061545,

/// <summary>
/// The status for a player that makes nice photos.
/// </summary>
GroupPose = 061546,

/// <summary>
/// The status for new players.
/// </summary>
NewAdventurer = 061523,

/// <summary>
/// The status for mentors.
/// </summary>
Mentor = 061540,

/// <summary>
/// The status for PvE mentors.
/// </summary>
MentorPvE = 061542,

/// <summary>
/// The status for crafting mentors.
/// </summary>
MentorCrafting = 061543,

/// <summary>
/// The status for PvP mentors.
/// </summary>
MentorPvP = 061544,

/// <summary>
/// The status for a player that recently took a break playing FFXIV.
/// </summary>
Returner = 061547,
}
178 changes: 178 additions & 0 deletions Dalamud/Game/Gui/Nameplates/NameplateGui.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
using System.Linq;
using System.Runtime.InteropServices;

using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Game.Gui.Nameplates.EventArgs;
using Dalamud.Game.Gui.Nameplates.Model;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Hooking;
using Dalamud.IoC.Internal;
using Dalamud.Memory;

using FFXIVClientStructs.FFXIV.Client.UI;

namespace Dalamud.Game.Gui.Nameplates;

/// <summary>
/// This class handles interacting with native Nameplate update events and management.
/// </summary>
[InterfaceVersion("1.0")]
[ServiceManager.EarlyLoadedService]
internal class NameplateGui : IInternalDisposableService, INameplatesGui
{
private readonly GameGui gameGui;
private readonly ObjectTable objectTable;

private readonly NameplateGuiAddressResolver addresses;
private readonly IntPtr namePlatePtr;

private Hook<SetPlayerNameplateDetourDelegate>? setPlayerNameplateDetourHook = null;

/// <summary>
/// Initializes a new instance of the <see cref="NameplateGui"/> class.
/// </summary>
[ServiceManager.ServiceConstructor]
private NameplateGui(TargetSigScanner sigScanner)
{
// Services
this.gameGui = Service<GameGui>.Get();
this.objectTable = Service<ObjectTable>.Get();

// Pointers
this.namePlatePtr = this.gameGui.GetAddonByName("NamePlate", 1);

// Address resolver
this.addresses = new();
this.addresses.Setup(sigScanner);

// Hooks
this.setPlayerNameplateDetourHook = Hook<SetPlayerNameplateDetourDelegate>.FromAddress(this.addresses.SetPlayerNameplateDetour, this.HandleSetPlayerNameplateDetour);
this.setPlayerNameplateDetourHook.Enable();
}

private unsafe delegate IntPtr SetPlayerNameplateDetourDelegate(IntPtr playerNameplateObjectPtr, bool isTitleAboveName, bool isTitleVisible, IntPtr titlePtr, IntPtr namePtr, IntPtr freeCompanyPtr, IntPtr prefix, int iconId);

/// <inheritdoc/>
public event INameplatesGui.OnNameplateUpdateDelegate OnNameplateUpdate;

/// <inheritdoc/>
public void DisposeService()
{
this.setPlayerNameplateDetourHook.Dispose();
}

/// <inheritdoc/>
public T? GetNameplateGameObject<T>(NameplateObject nameplateObject) where T : GameObject
{
return this.GetNameplateGameObject<T>(nameplateObject.Pointer);
}

/// <inheritdoc/>
public T? GetNameplateGameObject<T>(IntPtr nameplateObjectPtr) where T : GameObject
{
// Get the nameplate object array
var nameplateAddonPtr = this.gameGui.GetAddonByName("NamePlate", 1);
var nameplateObjectArrayPtrPtr = nameplateAddonPtr + Marshal.OffsetOf(typeof(AddonNamePlate), nameof(AddonNamePlate.NamePlateObjectArray)).ToInt32();
var nameplateObjectArrayPtr = Marshal.ReadIntPtr(nameplateObjectArrayPtrPtr);

if (nameplateObjectArrayPtr == IntPtr.Zero)
return null;

// Determine the index of the nameplate object within the nameplate object array
var namePlateObjectSize = Marshal.SizeOf(typeof(AddonNamePlate.NamePlateObject));
var namePlateObjectPtr0 = nameplateObjectArrayPtr + namePlateObjectSize * 0;
var namePlateIndex = (nameplateObjectPtr.ToInt64() - namePlateObjectPtr0.ToInt64()) / namePlateObjectSize;

if (namePlateIndex < 0 || namePlateIndex >= AddonNamePlate.NumNamePlateObjects)
return null;

// Get the nameplate info array
var nameplateInfoArrayPtr = IntPtr.Zero;
unsafe
{
var framework = FFXIVClientStructs.FFXIV.Client.System.Framework.Framework.Instance();
nameplateInfoArrayPtr = new IntPtr(&framework->GetUiModule()->GetRaptureAtkModule()->NamePlateInfoArray);
}

// Get the nameplate info for the nameplate object
var namePlateInfoPtr = new IntPtr(nameplateInfoArrayPtr.ToInt64() + Marshal.SizeOf(typeof(RaptureAtkModule.NamePlateInfo)) * namePlateIndex);
var namePlateInfo = Marshal.PtrToStructure<RaptureAtkModule.NamePlateInfo>(namePlateInfoPtr);

// Return the object for its object id
var objectId = namePlateInfo.ObjectID.ObjectID;
return this.objectTable.SearchById(objectId) as T;
}

private IntPtr HandleSetPlayerNameplateDetour(IntPtr playerNameplateObjectPtr, bool isTitleAboveName, bool isTitleVisible, IntPtr titlePtr, IntPtr namePtr, IntPtr freeCompanyPtr, IntPtr prefixPtr, int iconId)
{

if (this.OnNameplateUpdate != null)
{
// Create NamePlateObject if possible
var namePlateObj = new NameplateObject(playerNameplateObjectPtr);

// Create new event
var nameplateInfo = new NameplateInfo
{
Title = MemoryHelper.ReadSeStringNullTerminated(titlePtr),
Name = MemoryHelper.ReadSeStringNullTerminated(namePtr),
FreeCompany = MemoryHelper.ReadSeStringNullTerminated(freeCompanyPtr),
Prefix = MemoryHelper.ReadSeStringNullTerminated(prefixPtr),
IsTitleAboveName = isTitleAboveName,
IsTitleVisible = isTitleVisible,
IconID = (StatusIcons)iconId,
};

// Invoke event
var eventArgs = new NameplateUpdateEventArgs(nameplateInfo, namePlateObj);
this.OnNameplateUpdate.Invoke(eventArgs);

if (eventArgs.HasChanged)
{
// Get new states
isTitleAboveName = nameplateInfo.IsTitleAboveName;
isTitleVisible = nameplateInfo.IsTitleVisible;
iconId = (int)nameplateInfo.IconID;

// Get new Title string content
var titleRaw = nameplateInfo.Title.Encode();
var titleNewRaw = nameplateInfo.Title.Encode();
if (!titleRaw.SequenceEqual(titleNewRaw))
MemoryHelper.WriteSeString(titlePtr, nameplateInfo.Title);

// Get new Name string content
var nameRaw = nameplateInfo.Name.Encode();
var nameNewRaw = nameplateInfo.Name.Encode();
if (!nameRaw.SequenceEqual(nameNewRaw))
MemoryHelper.WriteSeString(namePtr, nameplateInfo.Name);

// Get new Free Company string content
var freeCompanyRaw = nameplateInfo.FreeCompany.Encode();
var freeCompanyNewRaw = nameplateInfo.FreeCompany.Encode();
if (!freeCompanyRaw.SequenceEqual(freeCompanyNewRaw))
MemoryHelper.WriteSeString(freeCompanyPtr, nameplateInfo.FreeCompany);

// Get new Prefix string content
var prefixRaw = nameplateInfo.Prefix.Encode();
var prefixNewRaw = nameplateInfo.Prefix.Encode();
if (!prefixRaw.SequenceEqual(prefixNewRaw))
MemoryHelper.WriteSeString(prefixPtr, nameplateInfo.Prefix);
}
}

// Call original
var result = this.setPlayerNameplateDetourHook.Original(
playerNameplateObjectPtr,
isTitleAboveName,
isTitleVisible,
titlePtr,
namePtr,
freeCompanyPtr,
prefixPtr,
iconId);

// Return result
return result;
}
}
18 changes: 18 additions & 0 deletions Dalamud/Game/Gui/Nameplates/NameplateGuiAddressResolver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace Dalamud.Game.Gui.Nameplates;

/// <summary>
/// The address resolver for the <see cref="NameplateGui"/> class.
/// </summary>
internal class NameplateGuiAddressResolver : BaseAddressResolver
{
/// <summary>
/// Gets the address of the native SetPlayerNameplateDetour method.
/// </summary>
public IntPtr SetPlayerNameplateDetour { get; private set; }

/// <inheritdoc/>
protected override void SetupInternal(ISigScanner scanner)
{
this.SetPlayerNameplateDetour = scanner.ScanText("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 48 8B 5C 24 ?? 45 38 BE");
}
}
Loading

0 comments on commit 967a9fc

Please sign in to comment.