-
Notifications
You must be signed in to change notification settings - Fork 283
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add NamePlateGui #1915
Add NamePlateGui #1915
Conversation
Nice work! With this setup, is it possible to apply changes like colours to also include the quotes (like «») of the part without unsetting the quotes and setting it in the text part of it? What about setting visibility of the entire nameplate? I see the "VisibilityFlags" part in |
Right now this isn't supported as a distinct "part" property, but it can be done by putting color payloads in the quote characters, e.g. via handler.TitleParts.LeftQuote = new SeString().Append(new UIForegroundPayload(16)).Append("《");
handler.TitleParts.RightQuote = new SeString().Append("》").Append(UIForegroundPayload.UIForegroundOff); We could consider adding
You can hide a nameplate entirely by setting the |
Looks good! I'm about to migrate Player Tags at the moment. Already spend over three hours for refactoring just now and finally got it compiled against this PR. But I didn't have time for testing in-game yet, need to do that later this day or maybe tomorrow. Will leave a comment here whenever I finished my tests with Player Tags.
I would use it for Player Tags as the user can decide to apply color to the whole nameplate. Leaving that parts uncolored might look weird and I would need even more complex code just for including the quotes. But we will see. I think this is ok for now. :) |
b64a605
to
ed70c76
Compare
After discussing with several developers of plugins which modify nameplates, it seems there was a desire to add a "wrap" part to title and free company tag fields outside the quotes in addition to inside the quotes, as some plugins want to color the entire part. Based on that I added an A more extreme version of the initial example allows the following: handler.TitleParts.OuterWrap = (new SeString(new UIForegroundPayload(522)), new SeString(UIForegroundPayload.UIForegroundOff));
handler.FreeCompanyTagParts.OuterWrap = (new SeString(new UIForegroundPayload(710)), new SeString(UIForegroundPayload.UIForegroundOff));
handler.TitleParts.LeftQuote = "[";
handler.TitleParts.RightQuote = "]";
handler.FreeCompanyTagParts.LeftQuote = " (";
handler.FreeCompanyTagParts.RightQuote = ")";
handler.FreeCompanyTagParts.TextWrap = (new SeString(new UIForegroundPayload(43)), new SeString(UIForegroundPayload.UIForegroundOff));
handler.FreeCompanyTagParts.Text = "Hello";
handler.TitleParts.TextWrap = (new SeString(new UIForegroundPayload(16)), new SeString(UIForegroundPayload.UIForegroundOff));
handler.TitleParts.Text = "Plate";
handler.IsPrefixTitle = true;
handler.DisplayTitle = true;
handler.NameParts.Text = "Anonymous Player";
handler.NameParts.TextWrap = (new SeString(new UIForegroundPayload(37)), new SeString(UIForegroundPayload.UIForegroundOff)); |
Finished migration and testing for PlayerTrack. No issues so far for my use. |
Otherwise I fully migrated FC Name Color as well and everything works pretty much perfectly! |
You should be able to hide that icon by setting |
d383a03
to
c0968a6
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good! Some notes, nothing major though - I would appreciate if you could add a self-test for this to Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps
that does some basic nameplate modifications. Ideally you would test if they reflect in the "rendered data" but if that is super involved then don't bother.
/// <summary> | ||
/// An empty null-terminated string pointer allocated in unmanaged memory, used to tag removed fields. | ||
/// </summary> | ||
internal static readonly nint EmptyStringPointer = CreateEmptyStringPointer(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will leak, is that intentional?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's intentional. This will live for the scope of Dalamud and I think it's worth a byte basically. If we freed this we could potentially end up with bad data in the string array.
/// <summary> | ||
/// Gets the flags for this nameplate according to the nameplate info object. | ||
/// </summary> | ||
int Flags { get; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How do you know what these mean? Does it make sense to expose the raw bitset like this as an int, without describing the actual value? (same for the other flags exposed like this)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was a little conflicted on this. The issue is that I believe they aren't fully understood, so I found it hard to have a "proper" enum here. But I also think it's possible to do some things like maybe find the title number from here for example (maybe? I kind of forget myself) and maybe it could be improved over time as we learn more if we let people poke in these flag fields. Other flags can do useful things but it's often the case that I can only explain maybe one or two flags and others are unknown.
We could hide them and just assume people who need them will cast the address to a CS struct though. But it becomes a bit harder to use then. So not sure what is best here.
/// <inheritdoc/> | ||
public void RequestRedraw() | ||
{ | ||
this.parentService.RequestRedraw(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it matter if multiple plugins call this very frequently/multiple times in the same frame? I assume some game code reads that flag set in the number array and re-renders the nameplates?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right. The game reads the flag we set which tells it to do a full update, and we poke the array data to cause an update on the next frame. Basically it's not a problem to call every frame if you're dragging a slider around in a settings window or something, and multiple plugins can call it in the same frame without any issue. But if a plugin decided to call it every frame during gameplay as a matter of course that would not be good for performance.
Let me try to figure something out here. We could read the rendered data from nameplate nodes but it would require an additional post-hook and some probing of Atk nodes which might end up kind of ugly (the nice part about the current way is we don't have to do that). I'll at least try the first part and see what I can do with the second. |
Introduction
First of all, huge thanks to @Pilzinsel64 for his work on the SetPlayerNamePlate-based API which paved the way for this Nameplate API iteration. Unfortunately, changes to the base game caused a lot of problems for everyone and forced us to take a different approach. On the plus side, this new arraydata-based approach allows us to support all nameplate types, not just player nameplates. Thanks also to @aers for your support and for pushing me to create this, and to everyone else who helped and provided feedback.
At its core this API uses addon lifecycle hooks to run before the
NamePlate
addon'sOnRequestedUpdate
function and modify the backing data before plates are rendered. It provides two events which users can subscribe to:OnNamePlateUpdate
, which fires only when a nameplate has important updates (as determined by the base game) and passes to the consumer only those nameplates which are updated. This should be the preferred event to use in most cases.OnDataUpdate
, which fires when there are any nameplate data changes at all, and passes to the consumer a list of all active nameplates. This should be avoided unless you are making changes which would otherwise be overwritten by the game every frame.Usage
Example usage for changing parts of a nameplate with
OnNamePlateUpdate
(used for the image at the top):The above example uses "Parts" fields, which can be used to allow multiple plugins to collaboratively build a field by its parts. If your plugin modifies or adds colours to one of these fields, consider using these "Parts" fields so that your plugin's modifications have a chance of working alongside those of another plugin. Simple getters/setters which don't use the parts system are also available.
Example usage for debugging nameplate kinds and updates with
OnDataUpdate
:The above attaches an icon to each nameplate indicating its
NamePlateKind
, and flashes that icon whenever the plate is updated (i.e. when it would be considered "updated" for the purpose ofOnNamePlateUpdate
). This uses the data update event which runs every frame because marker icons are set and cleared each frame.More detailed documentation on what changes are possible is visible on methods of
INamePlateUpdateHandler
.Other than these events, the only other public method is
RequestRedraw()
, which requests the game to redraw all nameplates on the next frame. Plugins can call this method to force a redraw of all nameplates when settings change, the plugin is loaded, etc. even while using the more efficientOnNamePlateUpdate
event, because calling this method will cause all plates to be considered updated on the next frame.Notes
In an earlier design I had more events related to nameplate drawing, which are mainly needed by plugins which do additional changes to native UI nodes and so on. However, the needs of such plugins are much more complicated and hard to generalise, and in the end it felt like a lot of unnecessary complexity. Such plugins should probably use
PostRequestedUpdate
orPreDraw
lifecycle hooks to carry out their additional custom node logic there.One minor downside of the arraydata-based approach is that modifying arraydata will typically cause the NamePlate addon's
OnRequestedUpdate
function to fire every frame. However, this function will already fire every frame if the camera or any on-screen entity with a nameplate is moving, so this only affects situations where load is very low in the first place (e.g. idling in the inn). The service itself takes care not to perform unnecessary processing whenever possible, so I think the overall impact should be small even when subscribers exist, and virtually non-existent when no subscribers exist.Any advice or feedback is welcome. I have ported the new version of Party Icons to use this service and things are working well for me, but I would especially appreciate feedback from developers of other plugins with existing nameplate functionality.