using System; using System.Numerics; using Dalamud.Bindings.ImGui; using FFXIVClientStructs.FFXIV.Client.UI.Agent; using FFXIVClientStructs.FFXIV.Client.UI.Arrays; using Mappy.Classes; using Mappy.Extensions; namespace Mappy.MapRenderer; public partial class MapRenderer { private unsafe void DrawPlayer() { if (AgentMap.Instance()->SelectedMapId != AgentMap.Instance()->CurrentMapId) return; if (Service.ObjectTable.LocalPlayer is { } localPlayer) { var position = ImGui.GetWindowPos() + DrawPosition + (localPlayer.GetMapPosition() - DrawHelpers.GetMapOffsetVector() + DrawHelpers.GetMapCenterOffsetVector()) * Scale; DrawLookLine(position); DrawPlayerIcon(position); } } private void DrawLookLine(Vector2 position) { var angle = GetCameraRotation(); var lineLength = System.SystemConfig.ConeSize * (System.SystemConfig.ScalePlayerCone ? 1.0f : Scale); var halfConeAngle = DegreesToRadians(90.0f) / 2.0f; DrawAngledLineFromCenter(position, lineLength, angle - halfConeAngle); DrawAngledLineFromCenter(position, lineLength, angle + halfConeAngle); DrawLineArcFromCenter(position, lineLength, angle); DrawFilledSemiCircle(position, lineLength, angle); } private static void DrawAngledLineFromCenter(Vector2 center, float lineLength, float angle, Vector4? outlineColor = null) { var lineSegment = new Vector2(lineLength * MathF.Cos(angle), lineLength * MathF.Sin(angle)); var color = outlineColor ?? System.SystemConfig.PlayerConeOutlineColor; ImGui.GetWindowDrawList().AddLine(center, center + lineSegment, ImGui.GetColorU32(color), 3.0f); } private static void DrawLineArcFromCenter(Vector2 center, float distance, float rotation, Vector4? outlineColor = null) { var halfConeAngle = DegreesToRadians(90.0f) / 2.0f; var color = outlineColor ?? System.SystemConfig.PlayerConeOutlineColor; var start = rotation - halfConeAngle; var stop = rotation + halfConeAngle; ImGui.GetWindowDrawList().PathArcTo(center, distance, start, stop); ImGui.GetWindowDrawList().PathStroke(ImGui.GetColorU32(color), ImDrawFlags.None, 3.0f); } private static void DrawFilledSemiCircle(Vector2 center, float distance, float rotation) { var halfConeAngle = DegreesToRadians(90.0f) / 2.0f; var coneColor = ImGui.GetColorU32(System.SystemConfig.PlayerConeColor); var startAngle = rotation - halfConeAngle; var stopAngle = rotation + halfConeAngle; var startPosition = new Vector2(distance * MathF.Cos(rotation - halfConeAngle), distance * MathF.Sin(rotation - halfConeAngle)); ImGui.GetWindowDrawList().PathArcTo(center, distance, startAngle, stopAngle); ImGui.GetWindowDrawList().PathLineTo(center); ImGui.GetWindowDrawList().PathLineTo(center + startPosition); ImGui.GetWindowDrawList().PathFillConvex(coneColor); } private static unsafe float GetCameraRotation() => -DegreesToRadians(AreaMapNumberArray.Instance()->ConeRotation) - 0.5f * MathF.PI; private static float DegreesToRadians(float degrees) => MathF.PI / 180.0f * degrees; private void DrawPlayerIcon(Vector2 position) { if (!System.SystemConfig.ShowPlayerIcon) return; if (Service.ObjectTable is not { LocalPlayer: { } player }) return; var texture = Service.TextureProvider.GetFromGameIcon(60443).GetWrapOrEmpty(); var angle = -player.Rotation + MathF.PI / 2.0f; var scale = System.SystemConfig.ScaleWithZoom ? Scale : 1.0f; scale *= System.SystemConfig.PlayerIconScale; var vectors = GetRotationVectors(angle, position, texture.Size / 2.0f * scale); ImGui.GetWindowDrawList().AddImageQuad(texture.Handle, vectors[0], vectors[1], vectors[2], vectors[3]); } /// /// Draw only the minimap player cone (direction indicator). Call before DrawMinimapMarkers so markers draw on top of the cone. /// private void DrawMinimapConeAtCenter(Vector2 centerPos, float mapScale) { if (!System.SystemConfig.MinimapShowPlayerCone) return; var angle = GetCameraRotation(); var lineLength = System.SystemConfig.ConeSize * 0.5f; var halfConeAngle = DegreesToRadians(90.0f) / 2.0f; DrawMinimapConeGradient(centerPos, lineLength, angle - halfConeAngle, angle + halfConeAngle); var softWhite = new Vector4(1f, 1f, 1f, 0.2f); DrawAngledLineFromCenter(centerPos, lineLength, angle - halfConeAngle, softWhite); DrawAngledLineFromCenter(centerPos, lineLength, angle + halfConeAngle, softWhite); DrawLineArcFromCenter(centerPos, lineLength, angle, softWhite); } /// /// Draw player icon at center (for minimap). Cone is drawn earlier so markers can be drawn on top of it. /// private void DrawPlayerAtCenter(Vector2 centerPos, float mapScale) { if (Service.ObjectTable.LocalPlayer is not { } localPlayer) return; if (!System.SystemConfig.ShowPlayerIcon) return; var texture = Service.TextureProvider.GetFromGameIcon(60443).GetWrapOrEmpty(); var angle = -localPlayer.Rotation + MathF.PI / 2.0f; var iconScale = System.SystemConfig.PlayerIconScale * 1.5f; // 1.5x for minimap visibility var vectors = GetRotationVectors(angle, centerPos, texture.Size / 2.0f * iconScale); ImGui.GetWindowDrawList().AddImageQuad(texture.Handle, vectors[0], vectors[1], vectors[2], vectors[3]); } /// Draw minimap cone as white light with radial gradient (bright at center, fading at edge). Kept quite transparent so markers underneath remain visible. private static void DrawMinimapConeGradient(Vector2 center, float radius, float startAngle, float endAngle) { const int segments = 24; const float maxAlpha = 0.18f; var drawList = ImGui.GetWindowDrawList(); for (var j = segments - 1; j >= 0; j--) { var rInner = radius * j / segments; var rOuter = radius * (j + 1) / segments; var t = (j + 0.5f) / segments; var alpha = maxAlpha * (1f - t); if (alpha <= 0f) continue; var color = ImGui.GetColorU32(new Vector4(1f, 1f, 1f, alpha)); var outerStart = center + new Vector2(rOuter * MathF.Cos(startAngle), rOuter * MathF.Sin(startAngle)); var innerEnd = center + new Vector2(rInner * MathF.Cos(endAngle), rInner * MathF.Sin(endAngle)); drawList.PathClear(); drawList.PathLineTo(center); drawList.PathLineTo(outerStart); drawList.PathArcTo(center, rOuter, startAngle, endAngle); drawList.PathLineTo(innerEnd); drawList.PathArcTo(center, rInner, endAngle, startAngle); drawList.PathFillConvex(color); } } private static Vector2[] GetRotationVectors(float angle, Vector2 center, Vector2 size) { var cosA = MathF.Cos(angle + 0.5f * MathF.PI); var sinA = MathF.Sin(angle + 0.5f * MathF.PI); Vector2[] vectors = [ center + ImRotate(new Vector2(-size.X * 0.5f, -size.Y * 0.5f), cosA, sinA), center + ImRotate(new Vector2(+size.X * 0.5f, -size.Y * 0.5f), cosA, sinA), center + ImRotate(new Vector2(+size.X * 0.5f, +size.Y * 0.5f), cosA, sinA), center + ImRotate(new Vector2(-size.X * 0.5f, +size.Y * 0.5f), cosA, sinA), ]; return vectors; } private static Vector2 ImRotate(Vector2 v, float cosA, float sinA) => new(v.X * cosA - v.Y * sinA, v.X * sinA + v.Y * cosA); }