Combo highlight config, tooltips, nameplates, hotbars fixes
- Combo highlight: configurable color, glow, line style (solid/dashed/dotted), thickness - Tooltips: font selection, scaling slider, improved wrap/cramping handling - Nameplates: custom quest icons with config, position smoothing fix for jitter - Hotbars: hide keybinds on empty slots, combo highlight within icon bounds - HudHelper: restore default nameplates on plugin disable Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -176,6 +176,57 @@ namespace HSUI.Helpers
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>Draw a rect outline with optional glow and line style (solid, dashed, dotted).</summary>
|
||||||
|
public static void DrawComboHighlightRect(ImDrawListPtr drawList, Vector2 min, Vector2 max, uint color, float thickness, bool showGlow, int lineStyle)
|
||||||
|
{
|
||||||
|
const float inset = 2f;
|
||||||
|
Vector2 innerMin = min + new Vector2(inset, inset);
|
||||||
|
Vector2 innerMax = max - new Vector2(inset, inset);
|
||||||
|
if (innerMax.X <= innerMin.X || innerMax.Y <= innerMin.Y) return;
|
||||||
|
|
||||||
|
if (showGlow)
|
||||||
|
{
|
||||||
|
for (int g = 5; g >= 1; g--)
|
||||||
|
{
|
||||||
|
float o = g;
|
||||||
|
uint alpha = (uint)(byte)(70 * (6 - g)) << 24;
|
||||||
|
drawList.AddRect(innerMin - new Vector2(o, o), innerMax + new Vector2(o, o),
|
||||||
|
alpha | (color & 0x00FFFFFF), 0, ImDrawFlags.None, 2f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lineStyle == 0) // Solid
|
||||||
|
{
|
||||||
|
drawList.AddRect(innerMin, innerMax, color, 0, ImDrawFlags.None, thickness);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dashed or Dotted: draw each side as segments
|
||||||
|
float dashLen = lineStyle == 1 ? 4f : 2f;
|
||||||
|
float gapLen = lineStyle == 1 ? 3f : 3f;
|
||||||
|
float step = dashLen + gapLen;
|
||||||
|
|
||||||
|
void DrawSegmentedLine(Vector2 a, Vector2 b)
|
||||||
|
{
|
||||||
|
Vector2 d = b - a;
|
||||||
|
float len = d.Length();
|
||||||
|
if (len < 0.001f) return;
|
||||||
|
Vector2 u = d / len;
|
||||||
|
float t = 0;
|
||||||
|
while (t < len)
|
||||||
|
{
|
||||||
|
float tEnd = Math.Min(t + dashLen, len);
|
||||||
|
drawList.AddLine(a + u * t, a + u * tEnd, color, thickness);
|
||||||
|
t += step;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawSegmentedLine(innerMin, new Vector2(innerMax.X, innerMin.Y)); // top
|
||||||
|
DrawSegmentedLine(new Vector2(innerMax.X, innerMin.Y), innerMax); // right
|
||||||
|
DrawSegmentedLine(innerMax, new Vector2(innerMin.X, innerMax.Y)); // bottom
|
||||||
|
DrawSegmentedLine(new Vector2(innerMin.X, innerMax.Y), innerMin); // left
|
||||||
|
}
|
||||||
|
|
||||||
public static void DrawIcon(uint iconId, Vector2 position, Vector2 size, bool drawBorder, uint color, ImDrawListPtr drawList)
|
public static void DrawIcon(uint iconId, Vector2 position, Vector2 size, bool drawBorder, uint color, ImDrawListPtr drawList)
|
||||||
{
|
{
|
||||||
IDalamudTextureWrap? texture = TexturesHelper.GetTextureFromIconId(iconId);
|
IDalamudTextureWrap? texture = TexturesHelper.GetTextureFromIconId(iconId);
|
||||||
|
|||||||
+23
-11
@@ -42,8 +42,8 @@ namespace HSUI.Helpers
|
|||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
private float MaxWidth => 340 * ImGuiHelpers.GlobalScale;
|
private float MaxWidth => Math.Max(160, 340 * ImGuiHelpers.GlobalScale * _config.TooltipScale);
|
||||||
private float Margin => 5 * ImGuiHelpers.GlobalScale;
|
private float Margin => Math.Max(4, 5 * ImGuiHelpers.GlobalScale * _config.TooltipScale);
|
||||||
|
|
||||||
private TooltipsConfig _config => ConfigurationManager.Instance.GetConfigObject<TooltipsConfig>();
|
private TooltipsConfig _config => ConfigurationManager.Instance.GetConfigObject<TooltipsConfig>();
|
||||||
|
|
||||||
@@ -95,7 +95,8 @@ namespace HSUI.Helpers
|
|||||||
_currentTooltipTitle += " (ID: " + id + ")";
|
_currentTooltipTitle += " (ID: " + id + ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
using (FontsManager.Instance.PushFont(FontsConfig.DefaultSmallFontKey))
|
string fontId = _config.FontID ?? FontsConfig.DefaultSmallFontKey;
|
||||||
|
using (FontsManager.Instance.PushFont(fontId))
|
||||||
{
|
{
|
||||||
_titleSize = ImGui.CalcTextSize(_currentTooltipTitle, false, MaxWidth);
|
_titleSize = ImGui.CalcTextSize(_currentTooltipTitle, false, MaxWidth);
|
||||||
_titleSize.Y += Margin;
|
_titleSize.Y += Margin;
|
||||||
@@ -103,7 +104,8 @@ namespace HSUI.Helpers
|
|||||||
}
|
}
|
||||||
|
|
||||||
// calculate text size
|
// calculate text size
|
||||||
using (FontsManager.Instance.PushFont(FontsConfig.DefaultSmallFontKey))
|
string fontIdForText = _config.FontID ?? FontsConfig.DefaultSmallFontKey;
|
||||||
|
using (FontsManager.Instance.PushFont(fontIdForText))
|
||||||
{
|
{
|
||||||
_textSize = ImGui.CalcTextSize(_currentTooltipText, false, MaxWidth);
|
_textSize = ImGui.CalcTextSize(_currentTooltipText, false, MaxWidth);
|
||||||
}
|
}
|
||||||
@@ -170,25 +172,27 @@ namespace HSUI.Helpers
|
|||||||
// no idea why i have to do this
|
// no idea why i have to do this
|
||||||
float globalScaleCorrection = -15 + 15 * ImGuiHelpers.GlobalScale;
|
float globalScaleCorrection = -15 + 15 * ImGuiHelpers.GlobalScale;
|
||||||
|
|
||||||
|
string fontId = _config.FontID ?? FontsConfig.DefaultSmallFontKey;
|
||||||
|
float wrapWidth = Math.Max(_titleSize.X, _textSize.X) + Margin;
|
||||||
if (_currentTooltipTitle != null)
|
if (_currentTooltipTitle != null)
|
||||||
{
|
{
|
||||||
// title
|
// title
|
||||||
Vector2 cursorPos;
|
Vector2 cursorPos;
|
||||||
using (FontsManager.Instance.PushFont(FontsConfig.DefaultSmallFontKey))
|
using (FontsManager.Instance.PushFont(fontId))
|
||||||
{
|
{
|
||||||
cursorPos = new Vector2(windowMargin.X + _size.X / 2f - _titleSize.X / 2f, Margin);
|
cursorPos = new Vector2(windowMargin.X + _size.X / 2f - _titleSize.X / 2f, Margin);
|
||||||
ImGui.SetCursorPos(cursorPos);
|
ImGui.SetCursorPos(cursorPos);
|
||||||
ImGui.PushTextWrapPos(cursorPos.X + _titleSize.X + globalScaleCorrection + Margin);
|
ImGui.PushTextWrapPos(cursorPos.X + wrapWidth + globalScaleCorrection);
|
||||||
ImGui.TextColored(_config.TitleColor.Vector, _currentTooltipTitle);
|
ImGui.TextColored(_config.TitleColor.Vector, _currentTooltipTitle);
|
||||||
ImGui.PopTextWrapPos();
|
ImGui.PopTextWrapPos();
|
||||||
}
|
}
|
||||||
|
|
||||||
// text
|
// text
|
||||||
using (FontsManager.Instance.PushFont(FontsConfig.DefaultSmallFontKey))
|
using (FontsManager.Instance.PushFont(fontId))
|
||||||
{
|
{
|
||||||
cursorPos = new Vector2(windowMargin.X + _size.X / 2f - _textSize.X / 2f, Margin + _titleSize.Y);
|
cursorPos = new Vector2(windowMargin.X + _size.X / 2f - _textSize.X / 2f, Margin + _titleSize.Y);
|
||||||
ImGui.SetCursorPos(cursorPos);
|
ImGui.SetCursorPos(cursorPos);
|
||||||
ImGui.PushTextWrapPos(cursorPos.X + _textSize.X + globalScaleCorrection + Margin);
|
ImGui.PushTextWrapPos(cursorPos.X + wrapWidth + globalScaleCorrection);
|
||||||
ImGui.TextColored(_config.TextColor.Vector, _currentTooltipText);
|
ImGui.TextColored(_config.TextColor.Vector, _currentTooltipText);
|
||||||
ImGui.PopTextWrapPos();
|
ImGui.PopTextWrapPos();
|
||||||
}
|
}
|
||||||
@@ -196,13 +200,13 @@ namespace HSUI.Helpers
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// text
|
// text
|
||||||
using (FontsManager.Instance.PushFont(FontsConfig.DefaultSmallFontKey))
|
using (FontsManager.Instance.PushFont(fontId))
|
||||||
{
|
{
|
||||||
var cursorPos = windowMargin + new Vector2(Margin, Margin);
|
var cursorPos = windowMargin + new Vector2(Margin, Margin);
|
||||||
var textWidth = _size.X - Margin * 2;
|
var textWidth = _size.X - Margin * 2;
|
||||||
|
|
||||||
ImGui.SetCursorPos(cursorPos);
|
ImGui.SetCursorPos(cursorPos);
|
||||||
ImGui.PushTextWrapPos(cursorPos.X + textWidth + globalScaleCorrection + Margin);
|
ImGui.PushTextWrapPos(cursorPos.X + textWidth + globalScaleCorrection);
|
||||||
ImGui.TextColored(_config.TextColor.Vector, _currentTooltipText);
|
ImGui.TextColored(_config.TextColor.Vector, _currentTooltipText);
|
||||||
ImGui.PopTextWrapPos();
|
ImGui.PopTextWrapPos();
|
||||||
}
|
}
|
||||||
@@ -248,8 +252,16 @@ namespace HSUI.Helpers
|
|||||||
[Order(3)]
|
[Order(3)]
|
||||||
public bool DebugTooltips = false;
|
public bool DebugTooltips = false;
|
||||||
|
|
||||||
|
[Font]
|
||||||
|
[Order(4)]
|
||||||
|
public string? FontID = null;
|
||||||
|
|
||||||
|
[DragFloat("Tooltip Scale", min = 0.5f, max = 2f, help = "Scale the tooltip window size. Useful for large resolutions.")]
|
||||||
|
[Order(6)]
|
||||||
|
public float TooltipScale = 1f;
|
||||||
|
|
||||||
[Checkbox("Show Status Effects IDs")]
|
[Checkbox("Show Status Effects IDs")]
|
||||||
[Order(5)]
|
[Order(7)]
|
||||||
public bool ShowStatusIDs = false;
|
public bool ShowStatusIDs = false;
|
||||||
|
|
||||||
[Checkbox("Show Source Name")]
|
[Checkbox("Show Source Name")]
|
||||||
|
|||||||
@@ -177,10 +177,9 @@ namespace HSUI.Interface.GeneralElements
|
|||||||
drawList.AddRect(pos, pos + size, 0xFF88CCFF, 0, ImDrawFlags.None, 2);
|
drawList.AddRect(pos, pos + size, 0xFF88CCFF, 0, ImDrawFlags.None, 2);
|
||||||
else if (isComboNext)
|
else if (isComboNext)
|
||||||
{
|
{
|
||||||
const uint gold = 0xFFFFD700;
|
var ch = Config.ComboHighlightConfig;
|
||||||
for (int g = 5; g >= 1; g--)
|
DrawHelper.DrawComboHighlightRect(drawList, pos, pos + size,
|
||||||
drawList.AddRect(pos - new Vector2(g, g), pos + size + new Vector2(g, g), (uint)((byte)(70 * (6 - g)) << 24 | (gold & 0xFFFFFF)), 0, ImDrawFlags.None, 2f);
|
ch.Color.Base, ch.Thickness, ch.ShowGlow, ch.LineStyle);
|
||||||
drawList.AddRect(pos, pos + size, gold, 0, ImDrawFlags.None, 4f);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showCd && slot.CooldownPercent > 0 && _pendingSlotIconIndex != i)
|
if (showCd && slot.CooldownPercent > 0 && _pendingSlotIconIndex != i)
|
||||||
@@ -198,7 +197,7 @@ namespace HSUI.Interface.GeneralElements
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Config.ShowSlotNumbers)
|
if (Config.ShowSlotNumbers && !slot.IsEmpty)
|
||||||
{
|
{
|
||||||
string label = !string.IsNullOrWhiteSpace(slot.KeybindHint)
|
string label = !string.IsNullOrWhiteSpace(slot.KeybindHint)
|
||||||
? slot.KeybindHint
|
? slot.KeybindHint
|
||||||
|
|||||||
@@ -66,6 +66,9 @@ namespace HSUI.Interface.GeneralElements
|
|||||||
[Order(29)]
|
[Order(29)]
|
||||||
public bool ShowComboHighlight = true;
|
public bool ShowComboHighlight = true;
|
||||||
|
|
||||||
|
[NestedConfig("Combo Highlight", 31)]
|
||||||
|
public ComboHighlightConfig ComboHighlightConfig = new();
|
||||||
|
|
||||||
[Checkbox("Debug Drag & Drop")]
|
[Checkbox("Debug Drag & Drop")]
|
||||||
[Order(30)]
|
[Order(30)]
|
||||||
public bool DebugDragDrop = false;
|
public bool DebugDragDrop = false;
|
||||||
@@ -106,6 +109,35 @@ namespace HSUI.Interface.GeneralElements
|
|||||||
public new static HotbarsConfig DefaultConfig() => new HotbarsConfig();
|
public new static HotbarsConfig DefaultConfig() => new HotbarsConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum ComboHighlightLineStyle
|
||||||
|
{
|
||||||
|
Solid = 0,
|
||||||
|
Dashed = 1,
|
||||||
|
Dotted = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
[Exportable(false)]
|
||||||
|
public class ComboHighlightConfig : PluginConfigObject
|
||||||
|
{
|
||||||
|
[ColorEdit4("Color")]
|
||||||
|
[Order(1)]
|
||||||
|
public PluginConfigColor Color = PluginConfigColor.FromHex(0xFFFFD700);
|
||||||
|
|
||||||
|
[Checkbox("Show Glow")]
|
||||||
|
[Order(2)]
|
||||||
|
public bool ShowGlow = false;
|
||||||
|
|
||||||
|
[Combo("Line Style", new string[] { "Solid", "Dashed", "Dotted" })]
|
||||||
|
[Order(3)]
|
||||||
|
public int LineStyle = (int)ComboHighlightLineStyle.Solid;
|
||||||
|
|
||||||
|
[DragInt("Border Thickness", min = 1, max = 8)]
|
||||||
|
[Order(4)]
|
||||||
|
public int Thickness = 3;
|
||||||
|
|
||||||
|
public ComboHighlightConfig() { }
|
||||||
|
}
|
||||||
|
|
||||||
public class HotbarsGeneralOptionsConfig : PluginConfigObject
|
public class HotbarsGeneralOptionsConfig : PluginConfigObject
|
||||||
{
|
{
|
||||||
[Checkbox("Enable drag and drop from game UI", help = "When enabled, you can drag actions, macros, and items from the Actions menu, Macro menu, and Inventory onto HSUI hotbars.")]
|
[Checkbox("Enable drag and drop from game UI", help = "When enabled, you can drag actions, macros, and items from the Actions menu, Macro menu, and Inventory onto HSUI hotbars.")]
|
||||||
|
|||||||
+23
-3
@@ -55,9 +55,7 @@ namespace HSUI.Interface
|
|||||||
|
|
||||||
Config.ValueChangeEvent -= ConfigValueChanged;
|
Config.ValueChangeEvent -= ConfigValueChanged;
|
||||||
|
|
||||||
// Only restore defaults when already on framework thread. Skip RunOnFrameworkThread
|
void RestoreDefaults()
|
||||||
// during unload—it can deadlock. Restore is best-effort; game state may be torn down.
|
|
||||||
if (Plugin.Framework.IsInFrameworkUpdateThread && Plugin.ObjectTable.LocalPlayer != null)
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -72,6 +70,26 @@ namespace HSUI.Interface
|
|||||||
Plugin.Logger.Error($"Exception during HudHelper.Dispose restore: {ex.Message}");
|
Plugin.Logger.Error($"Exception during HudHelper.Dispose restore: {ex.Message}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Plugin.Framework.IsInFrameworkUpdateThread && Plugin.ObjectTable.LocalPlayer != null)
|
||||||
|
{
|
||||||
|
RestoreDefaults();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Plugin.Framework.RunOnFrameworkThread(() =>
|
||||||
|
{
|
||||||
|
if (Plugin.ObjectTable.LocalPlayer != null)
|
||||||
|
RestoreDefaults();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Plugin.Logger.Error($"Exception scheduling HudHelper.Dispose restore: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Update()
|
public void Update()
|
||||||
@@ -230,6 +248,8 @@ namespace HSUI.Interface
|
|||||||
if (enemyList?.Enabled == true)
|
if (enemyList?.Enabled == true)
|
||||||
AddHashes("_EnemyList");
|
AddHashes("_EnemyList");
|
||||||
|
|
||||||
|
// Hide NamePlate when HSUI nameplates are enabled. We read icon IDs from the addon (still updated when hidden)
|
||||||
|
// and draw our own quest icons (! ? above NPCs) via NPC nameplate IconConfig.
|
||||||
var nameplatesConfig = ConfigurationManager.Instance?.GetConfigObject<NameplatesGeneralConfig>();
|
var nameplatesConfig = ConfigurationManager.Instance?.GetConfigObject<NameplatesGeneralConfig>();
|
||||||
if (nameplatesConfig?.Enabled == true)
|
if (nameplatesConfig?.Enabled == true)
|
||||||
AddHashes("NamePlate");
|
AddHashes("NamePlate");
|
||||||
|
|||||||
@@ -224,6 +224,26 @@ namespace HSUI.Interface.Nameplates
|
|||||||
NameplateAnchor? barAnchor = GetBarAnchor(data);
|
NameplateAnchor? barAnchor = GetBarAnchor(data);
|
||||||
drawActions.AddRange(GetMainLabelDrawActions(data, barAnchor));
|
drawActions.AddRange(GetMainLabelDrawActions(data, barAnchor));
|
||||||
|
|
||||||
|
// Quest/state icon (e.g. ! ? above NPCs) - drawn when we have icon config and game provided icon ID
|
||||||
|
if (_config is NameplateWithNPCBarConfig npcConfig &&
|
||||||
|
npcConfig.IconConfig.Enabled &&
|
||||||
|
data.NamePlateIconId > 0)
|
||||||
|
{
|
||||||
|
float alpha = _config.RangeConfig.AlphaForDistance(data.Distance, 1f);
|
||||||
|
Vector2 anchorPos = barAnchor?.Position ?? (_config.Position + data.ScreenPosition);
|
||||||
|
Vector2 anchorSize = barAnchor?.Size ?? Vector2.Zero;
|
||||||
|
var pos = Utils.GetAnchoredPosition(anchorPos, -anchorSize, npcConfig.IconConfig.FrameAnchor);
|
||||||
|
Vector2 iconPos = Utils.GetAnchoredPosition(pos + npcConfig.IconConfig.Position, npcConfig.IconConfig.Size, npcConfig.IconConfig.Anchor);
|
||||||
|
|
||||||
|
drawActions.Add((npcConfig.IconConfig.StrataLevel, () =>
|
||||||
|
{
|
||||||
|
DrawHelper.DrawInWindow(_config.ID + "_npcIcon", iconPos, npcConfig.IconConfig.Size, false, (drawList) =>
|
||||||
|
{
|
||||||
|
DrawHelper.DrawIcon((uint)data.NamePlateIconId, iconPos, npcConfig.IconConfig.Size, false, alpha, drawList);
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
return drawActions;
|
return drawActions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -413,6 +413,16 @@ namespace HSUI.Interface.GeneralElements
|
|||||||
[NestedConfig("Health Bar", 40)]
|
[NestedConfig("Health Bar", 40)]
|
||||||
public NameplateBarConfig BarConfig = null!;
|
public NameplateBarConfig BarConfig = null!;
|
||||||
|
|
||||||
|
/// <summary>Quest/state icon (e.g. ! ? above NPCs). Use Position and Size to resize and reposition around the nameplate.</summary>
|
||||||
|
[NestedConfig("Icon (Quest/State)", 42)]
|
||||||
|
public NameplateIconConfig IconConfig = new NameplateIconConfig(
|
||||||
|
new Vector2(0, -28),
|
||||||
|
new Vector2(32, 32),
|
||||||
|
DrawAnchor.Bottom,
|
||||||
|
DrawAnchor.Top
|
||||||
|
)
|
||||||
|
{ Strata = StrataLevel.LOWEST };
|
||||||
|
|
||||||
public NameplateBarConfig GetBarConfig() => BarConfig;
|
public NameplateBarConfig GetBarConfig() => BarConfig;
|
||||||
|
|
||||||
public NameplateWithNPCBarConfig(
|
public NameplateWithNPCBarConfig(
|
||||||
|
|||||||
@@ -75,10 +75,14 @@ namespace HSUI.Interface.Nameplates
|
|||||||
public IReadOnlyCollection<NameplateData> Data => _data.AsReadOnly();
|
public IReadOnlyCollection<NameplateData> Data => _data.AsReadOnly();
|
||||||
|
|
||||||
private NameplatesCache _cache = new NameplatesCache(50);
|
private NameplatesCache _cache = new NameplatesCache(50);
|
||||||
|
private Dictionary<uint, Vector2> _smoothedPositions = new(50);
|
||||||
|
private const float PositionSmoothFactor = 0.3f; // Lerp factor: lower = smoother, higher = more responsive
|
||||||
|
private const float PlayerPositionSmoothFactor = 0.15f; // Stronger smoothing for player (camera-follow causes more jitter)
|
||||||
|
|
||||||
private void ClientStateOnTerritoryChangedEvent(ushort territoryId)
|
private void ClientStateOnTerritoryChangedEvent(ushort territoryId)
|
||||||
{
|
{
|
||||||
_cache.Clear();
|
_cache.Clear();
|
||||||
|
_smoothedPositions.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public unsafe void Update()
|
public unsafe void Update()
|
||||||
@@ -107,6 +111,7 @@ namespace HSUI.Interface.Nameplates
|
|||||||
|
|
||||||
_data = new List<NameplateData>();
|
_data = new List<NameplateData>();
|
||||||
int activeCount = ui3DModule->NamePlateObjectInfoCount;
|
int activeCount = ui3DModule->NamePlateObjectInfoCount;
|
||||||
|
var nextSmoothed = new Dictionary<uint, Vector2>(Math.Min(activeCount + 4, 54));
|
||||||
|
|
||||||
for (int i = 0; i < activeCount; i++)
|
for (int i = 0; i < activeCount; i++)
|
||||||
{
|
{
|
||||||
@@ -127,18 +132,48 @@ namespace HSUI.Interface.Nameplates
|
|||||||
foundTarget = true;
|
foundTarget = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ui nameplate
|
// ui nameplate (may be stale when addon is hidden)
|
||||||
NamePlateObject nameplateObject = addon->NamePlateObjectArray[objectInfo->NamePlateIndex];
|
NamePlateObject nameplateObject = addon->NamePlateObjectArray[objectInfo->NamePlateIndex];
|
||||||
|
|
||||||
// position
|
|
||||||
Vector2 screenPos = new Vector2(
|
|
||||||
nameplateObject.RootComponentNode->AtkResNode.X + nameplateObject.RootComponentNode->AtkResNode.Width / 2f,
|
|
||||||
nameplateObject.RootComponentNode->AtkResNode.Y + nameplateObject.RootComponentNode->AtkResNode.Height
|
|
||||||
);
|
|
||||||
screenPos = ClampScreenPosition(screenPos);
|
|
||||||
|
|
||||||
Vector3 worldPos = new Vector3(obj->Position.X, obj->Position.Y + obj->Height * 2.2f, obj->Position.Z);
|
Vector3 worldPos = new Vector3(obj->Position.X, obj->Position.Y + obj->Height * 2.2f, obj->Position.Z);
|
||||||
|
|
||||||
|
// Screen position: use addon when available (game's logic, stable). WorldToScreen when addon hidden/stale.
|
||||||
|
var hudConfig = ConfigurationManager.Instance?.GetConfigObject<HUDOptionsConfig>();
|
||||||
|
bool hidingAddon = _config.Enabled && (hudConfig?.HideDefaultHudWhenReplaced ?? true);
|
||||||
|
bool isPlayer = Plugin.ObjectTable.LocalPlayer != null && new IntPtr(obj) == Plugin.ObjectTable.LocalPlayer.Address;
|
||||||
|
|
||||||
|
Vector2 screenPos;
|
||||||
|
bool addonNodeValid = nameplateObject.RootComponentNode != null;
|
||||||
|
// Use addon position when visible, or when hiding (game may still update nodes). Fall back to WorldToScreen if stale.
|
||||||
|
bool useAddonPos = addonNodeValid && (addon->IsVisible || hidingAddon);
|
||||||
|
if (useAddonPos)
|
||||||
|
{
|
||||||
|
float nx = nameplateObject.RootComponentNode->AtkResNode.X;
|
||||||
|
float ny = nameplateObject.RootComponentNode->AtkResNode.Y;
|
||||||
|
float nw = nameplateObject.RootComponentNode->AtkResNode.Width;
|
||||||
|
float nh = nameplateObject.RootComponentNode->AtkResNode.Height;
|
||||||
|
screenPos = new Vector2(nx + nw / 2f, ny + nh);
|
||||||
|
// Sanity: when addon hidden, if pos looks stale (off-screen), fall back to WorldToScreen
|
||||||
|
if (hidingAddon && (screenPos.X < -500 || screenPos.X > 3000 || screenPos.Y < -500 || screenPos.Y > 3000))
|
||||||
|
{
|
||||||
|
Plugin.GameGui.WorldToScreen(worldPos, out screenPos);
|
||||||
|
useAddonPos = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Plugin.GameGui.WorldToScreen(worldPos, out screenPos);
|
||||||
|
}
|
||||||
|
screenPos = ClampScreenPosition(screenPos);
|
||||||
|
// Temporal smoothing for WorldToScreen-sourced positions (addon pos is usually stable)
|
||||||
|
uint objId = obj->GetGameObjectId().ObjectId;
|
||||||
|
if (!useAddonPos && _smoothedPositions.TryGetValue(objId, out Vector2 prev))
|
||||||
|
{
|
||||||
|
float factor = isPlayer ? PlayerPositionSmoothFactor : PositionSmoothFactor;
|
||||||
|
screenPos = Vector2.Lerp(prev, screenPos, factor);
|
||||||
|
}
|
||||||
|
nextSmoothed[objId] = screenPos;
|
||||||
|
|
||||||
// distance
|
// distance
|
||||||
float distance = Vector3.Distance(camera.Object.Position, worldPos);
|
float distance = Vector3.Distance(camera.Object.Position, worldPos);
|
||||||
|
|
||||||
@@ -158,12 +193,18 @@ namespace HSUI.Interface.Nameplates
|
|||||||
isTitlePrefix = customTitleData.IsPrefix;
|
isTitlePrefix = customTitleData.IsPrefix;
|
||||||
}
|
}
|
||||||
|
|
||||||
// state icon
|
// Quest/state icon: use GameObject.NamePlateIconId (game logic, works when addon hidden).
|
||||||
int iconId = 0;
|
// Fallback to addon's NameIcon texture if GameObject has none (addon must be visible).
|
||||||
AtkUldAsset* textureInfo = nameplateObject.NameIcon->PartsList->Parts[nameplateObject.NameIcon->PartId].UldAsset;
|
int iconId = (int)obj->NamePlateIconId;
|
||||||
if (textureInfo != null && textureInfo->AtkTexture.Resource != null)
|
if (iconId == 0)
|
||||||
{
|
{
|
||||||
iconId = (int)textureInfo->AtkTexture.Resource->IconId;
|
try
|
||||||
|
{
|
||||||
|
AtkUldAsset* textureInfo = nameplateObject.NameIcon->PartsList->Parts[nameplateObject.NameIcon->PartId].UldAsset;
|
||||||
|
if (textureInfo != null && textureInfo->AtkTexture.Resource != null)
|
||||||
|
iconId = (int)textureInfo->AtkTexture.Resource->IconId;
|
||||||
|
}
|
||||||
|
catch { /* addon node may be null/stale when hidden */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
// order
|
// order
|
||||||
@@ -206,6 +247,7 @@ namespace HSUI.Interface.Nameplates
|
|||||||
catch { }
|
catch { }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_smoothedPositions = nextSmoothed;
|
||||||
_data.Reverse();
|
_data.Reverse();
|
||||||
|
|
||||||
// add target nameplate last
|
// add target nameplate last
|
||||||
@@ -253,22 +295,14 @@ namespace HSUI.Interface.Nameplates
|
|||||||
float margin = 20;
|
float margin = 20;
|
||||||
|
|
||||||
if (pos.X + nameplateSize.X > screenSize.X)
|
if (pos.X + nameplateSize.X > screenSize.X)
|
||||||
{
|
|
||||||
pos.X = screenSize.X - nameplateSize.X - margin;
|
pos.X = screenSize.X - nameplateSize.X - margin;
|
||||||
}
|
|
||||||
else if (pos.X - nameplateSize.X < 0)
|
else if (pos.X - nameplateSize.X < 0)
|
||||||
{
|
|
||||||
pos.X = nameplateSize.X + margin;
|
pos.X = nameplateSize.X + margin;
|
||||||
}
|
|
||||||
|
|
||||||
if (pos.Y + nameplateSize.Y > screenSize.Y)
|
if (pos.Y + nameplateSize.Y > screenSize.Y)
|
||||||
{
|
|
||||||
pos.Y = screenSize.Y - nameplateSize.Y - margin;
|
pos.Y = screenSize.Y - nameplateSize.Y - margin;
|
||||||
}
|
|
||||||
else if (pos.Y - nameplateSize.Y < 0)
|
else if (pos.Y - nameplateSize.Y < 0)
|
||||||
{
|
|
||||||
pos.Y = nameplateSize.Y + margin;
|
pos.Y = nameplateSize.Y + margin;
|
||||||
}
|
|
||||||
|
|
||||||
return pos;
|
return pos;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user