From db89966be2d40056665786d7d1f674af464f03f0 Mon Sep 17 00:00:00 2001 From: Liza Carvelli Date: Wed, 8 Feb 2023 16:06:43 +0100 Subject: [PATCH] Pluggable Renderer selection --- Pal.Client/Configuration.cs | 10 ++ Pal.Client/Marker.cs | 4 + Pal.Client/Pal.Client.csproj | 2 +- Pal.Client/Plugin.cs | 196 +++++++++------------ Pal.Client/Rendering/ELayer.cs | 8 + Pal.Client/Rendering/IDrawDebugItems.cs | 9 + Pal.Client/Rendering/IRenderElement.cs | 9 + Pal.Client/Rendering/IRenderer.cs | 19 ++ Pal.Client/Rendering/MarkerConfig.cs | 21 +++ Pal.Client/Rendering/SimpleRenderer.cs | 163 +++++++++++++++++ Pal.Client/Rendering/SplatoonRenderer.cs | 149 ++++++++++++++++ Pal.Client/Scheduled/QueuedConfigUpdate.cs | 2 + Pal.Client/Service.cs | 1 + Pal.Client/Windows/ConfigWindow.cs | 79 ++++----- 14 files changed, 505 insertions(+), 167 deletions(-) create mode 100644 Pal.Client/Rendering/ELayer.cs create mode 100644 Pal.Client/Rendering/IDrawDebugItems.cs create mode 100644 Pal.Client/Rendering/IRenderElement.cs create mode 100644 Pal.Client/Rendering/IRenderer.cs create mode 100644 Pal.Client/Rendering/MarkerConfig.cs create mode 100644 Pal.Client/Rendering/SimpleRenderer.cs create mode 100644 Pal.Client/Rendering/SplatoonRenderer.cs diff --git a/Pal.Client/Configuration.cs b/Pal.Client/Configuration.cs index ec0b97e..f8e5fbf 100644 --- a/Pal.Client/Configuration.cs +++ b/Pal.Client/Configuration.cs @@ -22,6 +22,7 @@ namespace Pal.Client #region Saved configuration values public bool FirstUse { get; set; } = true; public EMode Mode { get; set; } = EMode.Offline; + public ERenderer Renderer { get; set; } = ERenderer.Splatoon; [Obsolete] public string? DebugAccountId { private get; set; } @@ -159,6 +160,15 @@ namespace Pal.Client Offline = 2, } + public enum ERenderer + { + /// + Simple = 0, + + /// + Splatoon = 1, + } + public class AccountInfo { [JsonConverter(typeof(AccountIdConverter))] diff --git a/Pal.Client/Marker.cs b/Pal.Client/Marker.cs index 29f4932..72d2551 100644 --- a/Pal.Client/Marker.cs +++ b/Pal.Client/Marker.cs @@ -1,4 +1,5 @@ using ECommons.SplatoonAPI; +using Pal.Client.Rendering; using Pal.Common; using Palace; using System; @@ -57,6 +58,9 @@ namespace Pal.Client public string? SinceVersion { get; set; } [JsonIgnore] + public IRenderElement? RenderElement { get; set; } + + [Obsolete] public Element? SplatoonElement { get; set; } public Marker(EType type, Vector3 position, Guid? networkId = null) diff --git a/Pal.Client/Pal.Client.csproj b/Pal.Client/Pal.Client.csproj index dd5f9bf..ab9ba20 100644 --- a/Pal.Client/Pal.Client.csproj +++ b/Pal.Client/Pal.Client.csproj @@ -3,7 +3,7 @@ net7.0-windows 11.0 - 2.6 + 2.7 enable diff --git a/Pal.Client/Plugin.cs b/Pal.Client/Plugin.cs index 4d8031a..d133938 100644 --- a/Pal.Client/Plugin.cs +++ b/Pal.Client/Plugin.cs @@ -9,11 +9,10 @@ using Dalamud.Interface.Windowing; using Dalamud.Logging; using Dalamud.Plugin; using ECommons; -using ECommons.Schedulers; -using ECommons.SplatoonAPI; using Grpc.Core; using ImGuiNET; using Lumina.Excel.GeneratedSheets; +using Pal.Client.Rendering; using Pal.Client.Scheduled; using Pal.Client.Windows; using Pal.Common; @@ -30,18 +29,8 @@ namespace Pal.Client { public class Plugin : IDalamudPlugin { - private const long ON_TERRITORY_CHANGE = -2; - private const uint COLOR_INVISIBLE = 0; - private const string SPLATOON_TRAP_HOARD = "PalacePal.TrapHoard"; - private const string SPLATOON_REGULAR_COFFERS = "PalacePal.RegularCoffers"; + internal const uint COLOR_INVISIBLE = 0; - private readonly static Dictionary _markerConfig = new Dictionary - { - { Marker.EType.Trap, new MarkerConfig { Radius = 1.7f } }, - { Marker.EType.Hoard, new MarkerConfig { Radius = 1.7f, OffsetY = -0.03f } }, - { Marker.EType.SilverCoffer, new MarkerConfig { Radius = 1f } }, - { Marker.EType.Debug, new MarkerConfig { Radius = 1.5f } }, - }; private LocalizedChatMessages _localizedChatMessages = new(); internal ConcurrentDictionary FloorMarkers { get; } = new(); @@ -54,6 +43,7 @@ namespace Pal.Client internal Queue EarlyEventQueue { get; } = new(); internal Queue LateEventQueue { get; } = new(); internal ConcurrentQueue NextUpdateObjects { get; } = new(); + internal IRenderer Renderer { get; private set; } = null!; public string Name => "Palace Pal"; @@ -74,12 +64,13 @@ namespace Pal.Client } #endif - ECommonsMain.Init(pluginInterface, this, Module.SplatoonAPI); - pluginInterface.Create(); Service.Plugin = this; Service.Configuration = (Configuration?)pluginInterface.GetPluginConfig() ?? pluginInterface.Create()!; Service.Configuration.Migrate(); + + ResetRenderer(); + Service.Hooks = new Hooks(); var agreementWindow = pluginInterface.Create(); @@ -101,7 +92,7 @@ namespace Pal.Client Service.WindowSystem.AddWindow(statisticsWindow); } - pluginInterface.UiBuilder.Draw += Service.WindowSystem.Draw; + pluginInterface.UiBuilder.Draw += Draw; pluginInterface.UiBuilder.OpenConfigUi += OnOpenConfigUi; Service.Framework.Update += OnFrameworkUpdate; Service.Chat.ChatMessage += OnChatMessage; @@ -197,7 +188,7 @@ namespace Pal.Client if (!disposing) return; Service.CommandManager.RemoveHandler("/pal"); - Service.PluginInterface.UiBuilder.Draw -= Service.WindowSystem.Draw; + Service.PluginInterface.UiBuilder.Draw -= Draw; Service.PluginInterface.UiBuilder.OpenConfigUi -= OnOpenConfigUi; Service.Framework.Update -= OnFrameworkUpdate; Service.Chat.ChatMessage -= OnChatMessage; @@ -207,16 +198,8 @@ namespace Pal.Client Service.RemoteApi.Dispose(); Service.Hooks.Dispose(); - try - { - Splatoon.RemoveDynamicElements(SPLATOON_TRAP_HOARD); - Splatoon.RemoveDynamicElements(SPLATOON_REGULAR_COFFERS); - } - catch - { - // destroyed on territory change either way - } - ECommonsMain.Dispose(); + if (Renderer is IDisposable disposable) + disposable.Dispose(); } public void Dispose() @@ -321,6 +304,7 @@ namespace Pal.Client return FloorMarkers.GetOrAdd(territoryType, tt => LocalState.Load(tt) ?? new LocalState(tt)); } + #region Rendering markers private void HandlePersistentMarkers(LocalState currentFloor, IList visibleMarkers, bool saveMarkers, bool recreateLayout) { var config = Service.Configuration; @@ -360,14 +344,14 @@ namespace Pal.Client foreach (var marker in currentFloorMarkers) { uint desiredColor = DetermineColor(marker, visibleMarkers); - if (marker.SplatoonElement == null || !marker.SplatoonElement.IsValid()) + if (marker.RenderElement == null || !marker.RenderElement.IsValid) { recreateLayout = true; break; } - if (marker.SplatoonElement.color != desiredColor) - marker.SplatoonElement.color = desiredColor; + if (marker.RenderElement.Color != desiredColor) + marker.RenderElement.Color = desiredColor; } } catch (Exception e) @@ -403,30 +387,24 @@ namespace Pal.Client if (recreateLayout) { - Splatoon.RemoveDynamicElements(SPLATOON_TRAP_HOARD); + Renderer.ResetLayer(ELayer.TrapHoard); - List elements = new List(); + List elements = new(); foreach (var marker in currentFloorMarkers) { if (marker.Seen || config.Mode == Configuration.EMode.Online || (marker.WasImported && marker.Imports.Count > 0)) { if (marker.Type == Marker.EType.Trap && config.ShowTraps) { - var element = CreateSplatoonElement(marker.Type, marker.Position, DetermineColor(marker, visibleMarkers)); - marker.SplatoonElement = element; - elements.Add(element); + CreateRenderElement(marker, elements, DetermineColor(marker, visibleMarkers)); } else if (marker.Type == Marker.EType.Hoard && config.ShowHoard) { - var element = CreateSplatoonElement(marker.Type, marker.Position, DetermineColor(marker, visibleMarkers)); - marker.SplatoonElement = element; - elements.Add(element); + CreateRenderElement(marker, elements, DetermineColor(marker, visibleMarkers)); } else if (marker.Type == Marker.EType.Debug && Service.Configuration.BetaKey == "VFX") { - var element = CreateSplatoonElement(marker.Type, marker.Position, DetermineColor(marker, visibleMarkers)); - marker.SplatoonElement = element; - elements.Add(element); + CreateRenderElement(marker, elements, DetermineColor(marker, visibleMarkers)); } } } @@ -434,18 +412,37 @@ namespace Pal.Client if (elements.Count == 0) return; - // we need to delay this, as the current framework update could be before splatoon's, in which case it would immediately delete the layout - new TickScheduler(delegate + Renderer.SetLayer(ELayer.TrapHoard, elements); + } + } + + private void HandleEphemeralMarkers(IList visibleMarkers, bool recreateLayout) + { + recreateLayout |= EphemeralMarkers.Any(existingMarker => !visibleMarkers.Any(x => x == existingMarker)); + recreateLayout |= visibleMarkers.Any(visibleMarker => !EphemeralMarkers.Any(x => x == visibleMarker)); + + if (recreateLayout) + { + Renderer.ResetLayer(ELayer.RegularCoffers); + EphemeralMarkers.Clear(); + + var config = Service.Configuration; + + List elements = new(); + foreach (var marker in visibleMarkers) { - try + EphemeralMarkers.Add(marker); + + if (marker.Type == Marker.EType.SilverCoffer && config.ShowSilverCoffers) { - Splatoon.AddDynamicElements(SPLATOON_TRAP_HOARD, elements.ToArray(), new long[] { Environment.TickCount64 + 60 * 60 * 1000, ON_TERRITORY_CHANGE }); + CreateRenderElement(marker, elements, DetermineColor(marker, visibleMarkers), config.FillSilverCoffers); } - catch (Exception e) - { - DebugMessage = $"{DateTime.Now}\n{e}"; - } - }); + } + + if (elements.Count == 0) + return; + + Renderer.SetLayer(ELayer.RegularCoffers, elements); } } @@ -465,51 +462,19 @@ namespace Pal.Client else return COLOR_INVISIBLE; } + else if (marker.Type == Marker.EType.SilverCoffer) + return ImGui.ColorConvertFloat4ToU32(Service.Configuration.SilverCofferColor); else return ImGui.ColorConvertFloat4ToU32(new Vector4(1, 0.5f, 1, 0.4f)); } - private void HandleEphemeralMarkers(IList visibleMarkers, bool recreateLayout) + private void CreateRenderElement(Marker marker, List elements, uint color, bool fill = false) { - recreateLayout |= EphemeralMarkers.Any(existingMarker => !visibleMarkers.Any(x => x == existingMarker)); - recreateLayout |= visibleMarkers.Any(visibleMarker => !EphemeralMarkers.Any(x => x == visibleMarker)); - - if (recreateLayout) - { - Splatoon.RemoveDynamicElements(SPLATOON_REGULAR_COFFERS); - EphemeralMarkers.Clear(); - - var config = Service.Configuration; - - List elements = new List(); - foreach (var marker in visibleMarkers) - { - EphemeralMarkers.Add(marker); - - if (marker.Type == Marker.EType.SilverCoffer && config.ShowSilverCoffers) - { - var element = CreateSplatoonElement(marker.Type, marker.Position, config.SilverCofferColor, config.FillSilverCoffers); - marker.SplatoonElement = element; - elements.Add(element); - } - } - - if (elements.Count == 0) - return; - - new TickScheduler(delegate - { - try - { - Splatoon.AddDynamicElements(SPLATOON_REGULAR_COFFERS, elements.ToArray(), new long[] { Environment.TickCount64 + 60 * 60 * 1000, ON_TERRITORY_CHANGE }); - } - catch (Exception e) - { - DebugMessage = $"{DateTime.Now}\n{e}"; - } - }); - } + var element = Renderer.CreateElement(marker.Type, marker.Position, color, fill); + marker.RenderElement = element; + elements.Add(element); } + #endregion #region Up-/Download private async Task DownloadMarkersForTerritory(ushort territoryId) @@ -616,7 +581,7 @@ namespace Pal.Client var nearbyMarkers = state.Markers .Where(m => predicate(m)) - .Where(m => m.SplatoonElement != null && m.SplatoonElement.color != COLOR_INVISIBLE) + .Where(m => m.RenderElement != null && m.RenderElement.Color != COLOR_INVISIBLE) .Select(m => new { m = m, distance = (playerPosition - m.Position)?.Length() ?? float.MaxValue }) .OrderBy(m => m.distance) .Take(5) @@ -672,27 +637,6 @@ namespace Pal.Client && Service.Condition[ConditionFlag.InDeepDungeon] && typeof(ETerritoryType).IsEnumDefined(Service.ClientState.TerritoryType); - internal static Element CreateSplatoonElement(Marker.EType type, Vector3 pos, Vector4 color, bool fill = false) - => CreateSplatoonElement(type, pos, ImGui.ColorConvertFloat4ToU32(color), fill); - - internal static Element CreateSplatoonElement(Marker.EType type, Vector3 pos, uint color, bool fill = false) - { - return new Element(ElementType.CircleAtFixedCoordinates) - { - refX = pos.X, - refY = pos.Z, // z and y are swapped - refZ = pos.Y, - offX = 0, - offY = 0, - offZ = _markerConfig[type].OffsetY, - Filled = fill, - radius = _markerConfig[type].Radius, - FillStep = 1, - color = color, - thicc = 2, - }; - } - private void ReloadLanguageStrings() { _localizedChatMessages = new LocalizedChatMessages @@ -706,6 +650,30 @@ namespace Pal.Client }; } + internal void ResetRenderer() + { + if (Renderer is SplatoonRenderer && Service.Configuration.Renderer == Configuration.ERenderer.Splatoon) + return; + else if (Renderer is SimpleRenderer && Service.Configuration.Renderer == Configuration.ERenderer.Simple) + return; + + if (Renderer is IDisposable disposable) + disposable.Dispose(); + + if (Service.Configuration.Renderer == Configuration.ERenderer.Splatoon) + Renderer = new SplatoonRenderer(Service.PluginInterface, this); + else + Renderer = new SimpleRenderer(); + } + + private void Draw() + { + if (Renderer is SimpleRenderer sr) + sr.DrawLayers(); + + Service.WindowSystem.Draw(); + } + private string GetLocalizedString(uint id) { return Service.DataManager.GetExcelSheet()?.GetRow(id)?.Text?.ToString() ?? "Unknown"; @@ -719,12 +687,6 @@ namespace Pal.Client PomanderOfSafetyUsed, } - private class MarkerConfig - { - public float OffsetY { get; set; } = 0; - public float Radius { get; set; } = 0.25f; - } - private class LocalizedChatMessages { public string MapRevealed { get; set; } = "???"; //"The map for this floor has been revealed!"; diff --git a/Pal.Client/Rendering/ELayer.cs b/Pal.Client/Rendering/ELayer.cs new file mode 100644 index 0000000..be7881e --- /dev/null +++ b/Pal.Client/Rendering/ELayer.cs @@ -0,0 +1,8 @@ +namespace Pal.Client.Rendering +{ + internal enum ELayer + { + TrapHoard, + RegularCoffers, + } +} diff --git a/Pal.Client/Rendering/IDrawDebugItems.cs b/Pal.Client/Rendering/IDrawDebugItems.cs new file mode 100644 index 0000000..e584545 --- /dev/null +++ b/Pal.Client/Rendering/IDrawDebugItems.cs @@ -0,0 +1,9 @@ +using System.Numerics; + +namespace Pal.Client.Rendering +{ + internal interface IDrawDebugItems + { + void DrawDebugItems(Vector4 trapColor, Vector4 hoardColor); + } +} diff --git a/Pal.Client/Rendering/IRenderElement.cs b/Pal.Client/Rendering/IRenderElement.cs new file mode 100644 index 0000000..8f11a82 --- /dev/null +++ b/Pal.Client/Rendering/IRenderElement.cs @@ -0,0 +1,9 @@ +namespace Pal.Client.Rendering +{ + public interface IRenderElement + { + bool IsValid { get; } + + uint Color { get; set; } + } +} diff --git a/Pal.Client/Rendering/IRenderer.cs b/Pal.Client/Rendering/IRenderer.cs new file mode 100644 index 0000000..9ecf7d2 --- /dev/null +++ b/Pal.Client/Rendering/IRenderer.cs @@ -0,0 +1,19 @@ +using ImGuiNET; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using System.Text; +using System.Threading.Tasks; + +namespace Pal.Client.Rendering +{ + internal interface IRenderer + { + void SetLayer(ELayer layer, IReadOnlyList elements); + + void ResetLayer(ELayer layer); + + IRenderElement CreateElement(Marker.EType type, Vector3 pos, uint color, bool fill = false); + } +} diff --git a/Pal.Client/Rendering/MarkerConfig.cs b/Pal.Client/Rendering/MarkerConfig.cs new file mode 100644 index 0000000..77ac161 --- /dev/null +++ b/Pal.Client/Rendering/MarkerConfig.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; + +namespace Pal.Client.Rendering +{ + internal class MarkerConfig + { + + private readonly static Dictionary _markerConfig = new Dictionary + { + { Marker.EType.Trap, new MarkerConfig { Radius = 1.7f } }, + { Marker.EType.Hoard, new MarkerConfig { Radius = 1.7f, OffsetY = -0.03f } }, + { Marker.EType.SilverCoffer, new MarkerConfig { Radius = 1f } }, + { Marker.EType.Debug, new MarkerConfig { Radius = 1.7f, OffsetY = 0.1f } }, + }; + + public float OffsetY { get; set; } = 0; + public float Radius { get; set; } = 0.25f; + + public static MarkerConfig ForType(Marker.EType type) => _markerConfig[type] ?? new MarkerConfig(); + } +} diff --git a/Pal.Client/Rendering/SimpleRenderer.cs b/Pal.Client/Rendering/SimpleRenderer.cs new file mode 100644 index 0000000..08f5657 --- /dev/null +++ b/Pal.Client/Rendering/SimpleRenderer.cs @@ -0,0 +1,163 @@ +using Dalamud.Game.Gui; +using Dalamud.Interface; +using Dalamud.Plugin; +using ECommons.ExcelServices.TerritoryEnumeration; +using ImGuiNET; +using Lumina.Excel.GeneratedSheets; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using System.Xml.Linq; + +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 class SimpleRenderer : IRenderer, IDisposable + { + private ConcurrentDictionary layers = new(); + + public void SetLayer(ELayer layer, IReadOnlyList elements) + { + layers[layer] = new SimpleLayer + { + TerritoryType = Service.ClientState.TerritoryType, + Elements = elements.Cast().ToList() + }; + } + + public void ResetLayer(ELayer layer) + { + if (layers.Remove(layer, out var l)) + l.Dispose(); + } + + public IRenderElement CreateElement(Marker.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 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)) + { + ushort territoryType = Service.ClientState.TerritoryType; + + foreach (var layer in layers.Values.Where(l => l.TerritoryType == territoryType)) + layer.Draw(); + + foreach (var key in layers.Where(l => l.Value.TerritoryType != territoryType).Select(l => l.Key).ToList()) + ResetLayer(key); + + ImGui.End(); + } + ImGui.PopStyleVar(); + } + + public void Dispose() + { + foreach (var l in layers.Values) + l.Dispose(); + } + + public class SimpleLayer : IDisposable + { + public required ushort TerritoryType { get; init; } + public required IReadOnlyList Elements { get; set; } + + public void Draw() + { + foreach (var element in Elements) + element.Draw(); + } + + public void Dispose() + { + foreach (var e in Elements) + e.IsValid = false; + } + } + + public class SimpleElement : IRenderElement + { + private const int segmentCount = 20; + + public bool IsValid { get; set; } = true; + public required Marker.EType Type { get; set; } + public required Vector3 Position { get; set; } + public required uint Color { get; set; } + public required float Radius { get; set; } + public required bool Fill { get; set; } + + public void Draw() + { + if (Color == Plugin.COLOR_INVISIBLE) + return; + + switch (Type) + { + case Marker.EType.Hoard: + // ignore distance if this is a found hoard coffer + if (Service.Plugin.PomanderOfIntuition == Plugin.PomanderState.Active && Service.Configuration.OnlyVisibleHoardAfterPomander) + break; + + goto case Marker.EType.Trap; + + case Marker.EType.Trap: + case Marker.EType.Debug: + var playerPos = Service.ClientState.LocalPlayer?.Position; + if (playerPos == null) + return; + + if ((playerPos.Value - Position).Length() > 65) + return; + break; + } + + bool onScreen = false; + for (int index = 0; index < 2 * segmentCount; ++index) + { + onScreen |= Service.GameGui.WorldToScreen(new Vector3( + Position.X + Radius * (float)Math.Sin(Math.PI / segmentCount * index), + Position.Y, + Position.Z + Radius * (float)Math.Cos(Math.PI / segmentCount * index)), + out Vector2 vector2); + + ImGui.GetWindowDrawList().PathLineTo(vector2); + } + + if (onScreen) + { + if (Fill) + ImGui.GetWindowDrawList().PathFillConvex(Color); + else + ImGui.GetWindowDrawList().PathStroke(Color, ImDrawFlags.Closed, 2); + } + else + ImGui.GetWindowDrawList().PathClear(); + } + } + } +} diff --git a/Pal.Client/Rendering/SplatoonRenderer.cs b/Pal.Client/Rendering/SplatoonRenderer.cs new file mode 100644 index 0000000..4865eb5 --- /dev/null +++ b/Pal.Client/Rendering/SplatoonRenderer.cs @@ -0,0 +1,149 @@ +using Dalamud.Logging; +using Dalamud.Plugin; +using ECommons; +using ECommons.Reflection; +using ECommons.Schedulers; +using ECommons.SplatoonAPI; +using ImGuiNET; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace Pal.Client.Rendering +{ + internal class SplatoonRenderer : IRenderer, IDrawDebugItems, IDisposable + { + private const long ON_TERRITORY_CHANGE = -2; + + public SplatoonRenderer(DalamudPluginInterface pluginInterface, IDalamudPlugin plugin) + { + ECommonsMain.Init(pluginInterface, plugin, ECommons.Module.SplatoonAPI); + } + + public void SetLayer(ELayer layer, IReadOnlyList elements) + { + // we need to delay this, as the current framework update could be before splatoon's, in which case it would immediately delete the layout + new TickScheduler(delegate + { + try + { + Splatoon.AddDynamicElements(ToLayerName(layer), elements.Cast().Select(x => x.Delegate).ToArray(), new long[] { Environment.TickCount64 + 60 * 60 * 1000, ON_TERRITORY_CHANGE }); + } + catch (Exception e) + { + PluginLog.Error(e, $"Could not create splatoon layer {layer} with {elements.Count} elements"); + Service.Plugin.DebugMessage = $"{DateTime.Now}\n{e}"; + } + }); + } + + public void ResetLayer(ELayer layer) + { + try + { + Splatoon.RemoveDynamicElements(ToLayerName(layer)); + } + catch (Exception e) + { + PluginLog.Error(e, $"Could not reset splatoon layer {layer}"); + } + } + + private string ToLayerName(ELayer layer) + => $"PalacePal.{layer}"; + + public IRenderElement CreateElement(Marker.EType type, Vector3 pos, uint color, bool fill = false) + { + MarkerConfig config = MarkerConfig.ForType(type); + Element element = new Element(ElementType.CircleAtFixedCoordinates) + { + refX = pos.X, + refY = pos.Z, // z and y are swapped + refZ = pos.Y, + offX = 0, + offY = 0, + offZ = config.OffsetY, + Filled = fill, + radius = config.Radius, + FillStep = 1, + color = color, + thicc = 2, + }; + return new SplatoonElement(element); + } + + public void DrawDebugItems(Vector4 trapColor, Vector4 hoardColor) + { + try + { + Vector3? pos = Service.ClientState.LocalPlayer?.Position; + if (pos != null) + { + var elements = new List + { + CreateElement(Marker.EType.Trap, pos.Value, ImGui.ColorConvertFloat4ToU32(trapColor)), + CreateElement(Marker.EType.Hoard, pos.Value, ImGui.ColorConvertFloat4ToU32(hoardColor)), + }; + + if (!Splatoon.AddDynamicElements("PalacePal.Test", elements.Cast().Select(x => x.Delegate).ToArray(), new long[] { Environment.TickCount64 + 10000 })) + { + Service.Chat.PrintError("Could not draw markers :("); + } + } + } + catch (Exception) + { + try + { + var pluginManager = DalamudReflector.GetPluginManager(); + IList installedPlugins = pluginManager.GetType().GetProperty("InstalledPlugins")?.GetValue(pluginManager) as IList ?? new List(); + + foreach (var t in installedPlugins) + { + AssemblyName? assemblyName = (AssemblyName?)t.GetType().GetProperty("AssemblyName")?.GetValue(t); + string? pluginName = (string?)t.GetType().GetProperty("Name")?.GetValue(t); + if (assemblyName?.Name == "Splatoon" && pluginName != "Splatoon") + { + Service.Chat.PrintError($"[Palace Pal] Splatoon is installed under the plugin name '{pluginName}', which is incompatible with the Splatoon API."); + Service.Chat.Print("[Palace Pal] You need to install Splatoon from the official repository at https://github.com/NightmareXIV/MyDalamudPlugins."); + return; + } + } + } + catch (Exception) { } + + Service.Chat.PrintError("Could not draw markers, is Splatoon installed and enabled?"); + } + } + + public void Dispose() + { + ResetLayer(ELayer.TrapHoard); + ResetLayer(ELayer.RegularCoffers); + + ECommonsMain.Dispose(); + } + + public class SplatoonElement : IRenderElement + { + public SplatoonElement(Element element) + { + Delegate = element; + } + + public Element Delegate { get; } + + public bool IsValid => Delegate.IsValid(); + public uint Color + { + get => Delegate.color; + set => Delegate.color = value; + } + } + } +} diff --git a/Pal.Client/Scheduled/QueuedConfigUpdate.cs b/Pal.Client/Scheduled/QueuedConfigUpdate.cs index 2f55574..d816ec1 100644 --- a/Pal.Client/Scheduled/QueuedConfigUpdate.cs +++ b/Pal.Client/Scheduled/QueuedConfigUpdate.cs @@ -14,6 +14,8 @@ recreateLayout = true; saveMarkers = true; } + + plugin.ResetRenderer(); } } } diff --git a/Pal.Client/Service.cs b/Pal.Client/Service.cs index 5db8122..bb04800 100644 --- a/Pal.Client/Service.cs +++ b/Pal.Client/Service.cs @@ -22,6 +22,7 @@ namespace Pal.Client [PluginService] public static Condition Condition { get; set; } = null!; [PluginService] public static CommandManager CommandManager { get; set; } = null!; [PluginService] public static DataManager DataManager { get; set; } = null!; + [PluginService] public static GameGui GameGui { get; set; } = null!; public static Plugin Plugin { get; set; } = null!; public static WindowSystem WindowSystem { get; set; } = new(typeof(Service).AssemblyQualifiedName); diff --git a/Pal.Client/Windows/ConfigWindow.cs b/Pal.Client/Windows/ConfigWindow.cs index 8456dcd..d3a84f0 100644 --- a/Pal.Client/Windows/ConfigWindow.cs +++ b/Pal.Client/Windows/ConfigWindow.cs @@ -10,6 +10,7 @@ using ECommons.SplatoonAPI; using Google.Protobuf; using ImGuiNET; using Pal.Client.Net; +using Pal.Client.Rendering; using Pal.Client.Scheduled; using System; using System.Collections; @@ -28,6 +29,7 @@ namespace Pal.Client.Windows internal class ConfigWindow : Window { private int _mode; + private int _renderer; private bool _showTraps; private Vector4 _trapColor; private bool _onlyVisibleTrapsAfterPomander; @@ -64,6 +66,7 @@ namespace Pal.Client.Windows { var config = Service.Configuration; _mode = (int)config.Mode; + _renderer = (int)config.Renderer; _showTraps = config.ShowTraps; _trapColor = config.TrapColor; _onlyVisibleTrapsAfterPomander = config.OnlyVisibleTrapsAfterPomander; @@ -92,6 +95,7 @@ namespace Pal.Client.Windows DrawCommunityTab(ref saveAndClose); DrawImportTab(); DrawExportTab(); + DrawRenderTab(ref save, ref saveAndClose); DrawDebugTab(); ImGui.EndTabBar(); @@ -103,6 +107,7 @@ namespace Pal.Client.Windows { var config = Service.Configuration; config.Mode = (Configuration.EMode)_mode; + config.Renderer = (Configuration.ERenderer)_renderer; config.ShowTraps = _showTraps; config.TrapColor = _trapColor; config.OnlyVisibleTrapsAfterPomander = _onlyVisibleTrapsAfterPomander; @@ -277,6 +282,31 @@ namespace Pal.Client.Windows } } + private void DrawRenderTab(ref bool save, ref bool saveAndClose) + { + if (ImGui.BeginTabItem("Renderer")) + { + ImGui.Text("Select which render backend to use for markers:"); + ImGui.RadioButton("Splatoon (default, required Splatoon to be installed)", ref _renderer, (int)Configuration.ERenderer.Splatoon); + ImGui.RadioButton("Simple (experimental)", ref _renderer, (int)Configuration.ERenderer.Simple); + + ImGui.Separator(); + + save = ImGui.Button("Save"); + ImGui.SameLine(); + saveAndClose = ImGui.Button("Save & Close"); + + ImGui.Separator(); + ImGui.Text("Splatoon Test:"); + ImGui.BeginDisabled(!(Service.Plugin.Renderer is IDrawDebugItems)); + if (ImGui.Button("Draw trap & coffer circles around self")) + (Service.Plugin.Renderer as IDrawDebugItems)?.DrawDebugItems(_trapColor, _hoardColor); + ImGui.EndDisabled(); + + ImGui.EndTabItem(); + } + } + private void DrawDebugTab() { if (ImGui.BeginTabItem("Debug")) @@ -323,59 +353,10 @@ namespace Pal.Client.Windows else ImGui.Text("You are NOT in a deep dungeon."); - ImGui.Separator(); - - if (ImGui.Button("Draw trap & coffer circles around self")) - DrawDebugItems(); - ImGui.EndTabItem(); } } - private void DrawDebugItems() - { - try - { - Vector3? pos = Service.ClientState.LocalPlayer?.Position; - if (pos != null) - { - var elements = new List - { - Plugin.CreateSplatoonElement(Marker.EType.Trap, pos.Value, _trapColor), - Plugin.CreateSplatoonElement(Marker.EType.Hoard, pos.Value, _hoardColor), - }; - - if (!Splatoon.AddDynamicElements("PalacePal.Test", elements.ToArray(), new long[] { Environment.TickCount64 + 10000 })) - { - Service.Chat.PrintError("Could not draw markers :("); - } - } - } - catch (Exception) - { - try - { - var pluginManager = DalamudReflector.GetPluginManager(); - IList installedPlugins = pluginManager.GetType().GetProperty("InstalledPlugins")?.GetValue(pluginManager) as IList ?? new List(); - - foreach (var t in installedPlugins) - { - AssemblyName? assemblyName = (AssemblyName?)t.GetType().GetProperty("AssemblyName")?.GetValue(t); - string? pluginName = (string?)t.GetType().GetProperty("Name")?.GetValue(t); - if (assemblyName?.Name == "Splatoon" && pluginName != "Splatoon") - { - Service.Chat.PrintError($"[Palace Pal] Splatoon is installed under the plugin name '{pluginName}', which is incompatible with the Splatoon API."); - Service.Chat.Print("[Palace Pal] You need to install Splatoon from the official repository at https://github.com/NightmareXIV/MyDalamudPlugins."); - return; - } - } - } - catch (Exception) { } - - Service.Chat.PrintError("Could not draw markers, is Splatoon installed and enabled?"); - } - } - /// /// None of the default BeginTabItem methods allow using flags without making the tab have a close button for some reason. ///