using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Numerics; using Dalamud.Interface.Utility; using Dalamud.Plugin.Services; using ImGuiNET; using Pal.Client.Configuration; using Pal.Client.Floors; namespace Pal.Client.Rendering; /// /// Simple renderer that only draws basic stuff. /// /// This is based on what SliceIsRight uses, and what PalacePal used before it was /// remade into PalacePal (which is the third or fourth iteration on the same idea /// I made, just with a clear vision). /// internal sealed class SimpleRenderer : IRenderer, IDisposable { private const int SegmentCount = 20; private readonly IClientState _clientState; private readonly IGameGui _gameGui; private readonly IPalacePalConfiguration _configuration; private readonly TerritoryState _territoryState; private readonly ConcurrentDictionary _layers = new(); public SimpleRenderer(IClientState clientState, IGameGui gameGui, IPalacePalConfiguration configuration, TerritoryState territoryState) { _clientState = clientState; _gameGui = gameGui; _configuration = configuration; _territoryState = territoryState; } public void SetLayer(ELayer layer, IReadOnlyList elements) { _layers[layer] = new SimpleLayer { TerritoryType = _clientState.TerritoryType, Elements = elements.Cast().ToList() }; } public void ResetLayer(ELayer layer) { if (_layers.Remove(layer, out var l)) l.Dispose(); } public IRenderElement CreateElement(MemoryLocation.EType type, Vector3 pos, uint color, bool fill = false) { var config = MarkerConfig.ForType(type); return new SimpleElement { Type = type, Position = pos + new Vector3(0, config.OffsetY, 0), Color = color, Radius = config.Radius, Fill = fill, }; } public void DrawDebugItems(uint trapColor, uint hoardColor) { _layers[ELayer.Test] = new SimpleLayer { TerritoryType = _clientState.TerritoryType, Elements = new List { (SimpleElement)CreateElement( MemoryLocation.EType.Trap, _clientState.LocalPlayer?.Position ?? default, trapColor), (SimpleElement)CreateElement( MemoryLocation.EType.Hoard, _clientState.LocalPlayer?.Position ?? default, hoardColor) }, ExpiresAt = Environment.TickCount64 + RenderData.TestLayerTimeout }; } public void DrawLayers() { if (_layers.Count == 0) return; ImGuiHelpers.ForceNextWindowMainViewport(); ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero); ImGuiHelpers.SetNextWindowPosRelativeMainViewport(Vector2.Zero, ImGuiCond.None, Vector2.Zero); ImGui.SetNextWindowSize(ImGuiHelpers.MainViewport.Size); if (ImGui.Begin("###PalacePalSimpleRender", ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoBackground | ImGuiWindowFlags.NoInputs | ImGuiWindowFlags.NoSavedSettings | ImGuiWindowFlags.AlwaysUseWindowPadding)) { foreach (var layer in _layers.Values.Where(l => l.IsValid(_clientState))) { foreach (var e in layer.Elements) Draw(e); } foreach (var key in _layers.Where(l => !l.Value.IsValid(_clientState)) .Select(l => l.Key) .ToList()) ResetLayer(key); ImGui.End(); } ImGui.PopStyleVar(); } private void Draw(SimpleElement e) { if (e.Color == RenderData.ColorInvisible) return; switch (e.Type) { case MemoryLocation.EType.Hoard: // ignore distance if this is a found hoard coffer if (_territoryState.PomanderOfIntuition == PomanderState.Active && _configuration.DeepDungeons.HoardCoffers.OnlyVisibleAfterPomander) break; goto case MemoryLocation.EType.Trap; case MemoryLocation.EType.Trap: var playerPos = _clientState.LocalPlayer?.Position; if (playerPos == null) return; if ((playerPos.Value - e.Position).Length() > 65) return; break; } bool onScreen = false; for (int index = 0; index < 2 * SegmentCount; ++index) { onScreen |= _gameGui.WorldToScreen(new Vector3( e.Position.X + e.Radius * (float)Math.Sin(Math.PI / SegmentCount * index), e.Position.Y, e.Position.Z + e.Radius * (float)Math.Cos(Math.PI / SegmentCount * index)), out Vector2 vector2); ImGui.GetWindowDrawList().PathLineTo(vector2); } if (onScreen) { if (e.Fill) ImGui.GetWindowDrawList().PathFillConvex(e.Color); else ImGui.GetWindowDrawList().PathStroke(e.Color, ImDrawFlags.Closed, 2); } else ImGui.GetWindowDrawList().PathClear(); } public ERenderer GetConfigValue() => ERenderer.Simple; public void Dispose() { foreach (var l in _layers.Values) l.Dispose(); } public sealed class SimpleLayer : IDisposable { public required ushort TerritoryType { get; init; } public required IReadOnlyList Elements { get; init; } public long ExpiresAt { get; init; } = long.MaxValue; public bool IsValid(IClientState clientState) => TerritoryType == clientState.TerritoryType && ExpiresAt >= Environment.TickCount64; public void Dispose() { foreach (var e in Elements) e.IsValid = false; } } public sealed class SimpleElement : IRenderElement { public bool IsValid { get; set; } = true; public required MemoryLocation.EType Type { get; init; } public required Vector3 Position { get; init; } public required uint Color { get; set; } public required float Radius { get; init; } public required bool Fill { get; init; } } }