Pluggable Renderer selection

This commit is contained in:
Liza 2023-02-08 16:06:43 +01:00
parent 9263b9edfc
commit db89966be2
14 changed files with 505 additions and 167 deletions

View File

@ -22,6 +22,7 @@ namespace Pal.Client
#region Saved configuration values #region Saved configuration values
public bool FirstUse { get; set; } = true; public bool FirstUse { get; set; } = true;
public EMode Mode { get; set; } = EMode.Offline; public EMode Mode { get; set; } = EMode.Offline;
public ERenderer Renderer { get; set; } = ERenderer.Splatoon;
[Obsolete] [Obsolete]
public string? DebugAccountId { private get; set; } public string? DebugAccountId { private get; set; }
@ -159,6 +160,15 @@ namespace Pal.Client
Offline = 2, Offline = 2,
} }
public enum ERenderer
{
/// <see cref="Rendering.SimpleRenderer"/>
Simple = 0,
/// <see cref="Rendering.SplatoonRenderer"/>
Splatoon = 1,
}
public class AccountInfo public class AccountInfo
{ {
[JsonConverter(typeof(AccountIdConverter))] [JsonConverter(typeof(AccountIdConverter))]

View File

@ -1,4 +1,5 @@
using ECommons.SplatoonAPI; using ECommons.SplatoonAPI;
using Pal.Client.Rendering;
using Pal.Common; using Pal.Common;
using Palace; using Palace;
using System; using System;
@ -57,6 +58,9 @@ namespace Pal.Client
public string? SinceVersion { get; set; } public string? SinceVersion { get; set; }
[JsonIgnore] [JsonIgnore]
public IRenderElement? RenderElement { get; set; }
[Obsolete]
public Element? SplatoonElement { get; set; } public Element? SplatoonElement { get; set; }
public Marker(EType type, Vector3 position, Guid? networkId = null) public Marker(EType type, Vector3 position, Guid? networkId = null)

View File

@ -3,7 +3,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0-windows</TargetFramework> <TargetFramework>net7.0-windows</TargetFramework>
<LangVersion>11.0</LangVersion> <LangVersion>11.0</LangVersion>
<Version>2.6</Version> <Version>2.7</Version>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>

View File

@ -9,11 +9,10 @@ using Dalamud.Interface.Windowing;
using Dalamud.Logging; using Dalamud.Logging;
using Dalamud.Plugin; using Dalamud.Plugin;
using ECommons; using ECommons;
using ECommons.Schedulers;
using ECommons.SplatoonAPI;
using Grpc.Core; using Grpc.Core;
using ImGuiNET; using ImGuiNET;
using Lumina.Excel.GeneratedSheets; using Lumina.Excel.GeneratedSheets;
using Pal.Client.Rendering;
using Pal.Client.Scheduled; using Pal.Client.Scheduled;
using Pal.Client.Windows; using Pal.Client.Windows;
using Pal.Common; using Pal.Common;
@ -30,18 +29,8 @@ namespace Pal.Client
{ {
public class Plugin : IDalamudPlugin public class Plugin : IDalamudPlugin
{ {
private const long ON_TERRITORY_CHANGE = -2; internal const uint COLOR_INVISIBLE = 0;
private const uint COLOR_INVISIBLE = 0;
private const string SPLATOON_TRAP_HOARD = "PalacePal.TrapHoard";
private const string SPLATOON_REGULAR_COFFERS = "PalacePal.RegularCoffers";
private readonly static Dictionary<Marker.EType, MarkerConfig> _markerConfig = new Dictionary<Marker.EType, MarkerConfig>
{
{ 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(); private LocalizedChatMessages _localizedChatMessages = new();
internal ConcurrentDictionary<ushort, LocalState> FloorMarkers { get; } = new(); internal ConcurrentDictionary<ushort, LocalState> FloorMarkers { get; } = new();
@ -54,6 +43,7 @@ namespace Pal.Client
internal Queue<IQueueOnFrameworkThread> EarlyEventQueue { get; } = new(); internal Queue<IQueueOnFrameworkThread> EarlyEventQueue { get; } = new();
internal Queue<IQueueOnFrameworkThread> LateEventQueue { get; } = new(); internal Queue<IQueueOnFrameworkThread> LateEventQueue { get; } = new();
internal ConcurrentQueue<nint> NextUpdateObjects { get; } = new(); internal ConcurrentQueue<nint> NextUpdateObjects { get; } = new();
internal IRenderer Renderer { get; private set; } = null!;
public string Name => "Palace Pal"; public string Name => "Palace Pal";
@ -74,12 +64,13 @@ namespace Pal.Client
} }
#endif #endif
ECommonsMain.Init(pluginInterface, this, Module.SplatoonAPI);
pluginInterface.Create<Service>(); pluginInterface.Create<Service>();
Service.Plugin = this; Service.Plugin = this;
Service.Configuration = (Configuration?)pluginInterface.GetPluginConfig() ?? pluginInterface.Create<Configuration>()!; Service.Configuration = (Configuration?)pluginInterface.GetPluginConfig() ?? pluginInterface.Create<Configuration>()!;
Service.Configuration.Migrate(); Service.Configuration.Migrate();
ResetRenderer();
Service.Hooks = new Hooks(); Service.Hooks = new Hooks();
var agreementWindow = pluginInterface.Create<AgreementWindow>(); var agreementWindow = pluginInterface.Create<AgreementWindow>();
@ -101,7 +92,7 @@ namespace Pal.Client
Service.WindowSystem.AddWindow(statisticsWindow); Service.WindowSystem.AddWindow(statisticsWindow);
} }
pluginInterface.UiBuilder.Draw += Service.WindowSystem.Draw; pluginInterface.UiBuilder.Draw += Draw;
pluginInterface.UiBuilder.OpenConfigUi += OnOpenConfigUi; pluginInterface.UiBuilder.OpenConfigUi += OnOpenConfigUi;
Service.Framework.Update += OnFrameworkUpdate; Service.Framework.Update += OnFrameworkUpdate;
Service.Chat.ChatMessage += OnChatMessage; Service.Chat.ChatMessage += OnChatMessage;
@ -197,7 +188,7 @@ namespace Pal.Client
if (!disposing) return; if (!disposing) return;
Service.CommandManager.RemoveHandler("/pal"); Service.CommandManager.RemoveHandler("/pal");
Service.PluginInterface.UiBuilder.Draw -= Service.WindowSystem.Draw; Service.PluginInterface.UiBuilder.Draw -= Draw;
Service.PluginInterface.UiBuilder.OpenConfigUi -= OnOpenConfigUi; Service.PluginInterface.UiBuilder.OpenConfigUi -= OnOpenConfigUi;
Service.Framework.Update -= OnFrameworkUpdate; Service.Framework.Update -= OnFrameworkUpdate;
Service.Chat.ChatMessage -= OnChatMessage; Service.Chat.ChatMessage -= OnChatMessage;
@ -207,16 +198,8 @@ namespace Pal.Client
Service.RemoteApi.Dispose(); Service.RemoteApi.Dispose();
Service.Hooks.Dispose(); Service.Hooks.Dispose();
try if (Renderer is IDisposable disposable)
{ disposable.Dispose();
Splatoon.RemoveDynamicElements(SPLATOON_TRAP_HOARD);
Splatoon.RemoveDynamicElements(SPLATOON_REGULAR_COFFERS);
}
catch
{
// destroyed on territory change either way
}
ECommonsMain.Dispose();
} }
public void Dispose() public void Dispose()
@ -321,6 +304,7 @@ namespace Pal.Client
return FloorMarkers.GetOrAdd(territoryType, tt => LocalState.Load(tt) ?? new LocalState(tt)); return FloorMarkers.GetOrAdd(territoryType, tt => LocalState.Load(tt) ?? new LocalState(tt));
} }
#region Rendering markers
private void HandlePersistentMarkers(LocalState currentFloor, IList<Marker> visibleMarkers, bool saveMarkers, bool recreateLayout) private void HandlePersistentMarkers(LocalState currentFloor, IList<Marker> visibleMarkers, bool saveMarkers, bool recreateLayout)
{ {
var config = Service.Configuration; var config = Service.Configuration;
@ -360,14 +344,14 @@ namespace Pal.Client
foreach (var marker in currentFloorMarkers) foreach (var marker in currentFloorMarkers)
{ {
uint desiredColor = DetermineColor(marker, visibleMarkers); uint desiredColor = DetermineColor(marker, visibleMarkers);
if (marker.SplatoonElement == null || !marker.SplatoonElement.IsValid()) if (marker.RenderElement == null || !marker.RenderElement.IsValid)
{ {
recreateLayout = true; recreateLayout = true;
break; break;
} }
if (marker.SplatoonElement.color != desiredColor) if (marker.RenderElement.Color != desiredColor)
marker.SplatoonElement.color = desiredColor; marker.RenderElement.Color = desiredColor;
} }
} }
catch (Exception e) catch (Exception e)
@ -403,30 +387,24 @@ namespace Pal.Client
if (recreateLayout) if (recreateLayout)
{ {
Splatoon.RemoveDynamicElements(SPLATOON_TRAP_HOARD); Renderer.ResetLayer(ELayer.TrapHoard);
List<Element> elements = new List<Element>(); List<IRenderElement> elements = new();
foreach (var marker in currentFloorMarkers) foreach (var marker in currentFloorMarkers)
{ {
if (marker.Seen || config.Mode == Configuration.EMode.Online || (marker.WasImported && marker.Imports.Count > 0)) if (marker.Seen || config.Mode == Configuration.EMode.Online || (marker.WasImported && marker.Imports.Count > 0))
{ {
if (marker.Type == Marker.EType.Trap && config.ShowTraps) if (marker.Type == Marker.EType.Trap && config.ShowTraps)
{ {
var element = CreateSplatoonElement(marker.Type, marker.Position, DetermineColor(marker, visibleMarkers)); CreateRenderElement(marker, elements, DetermineColor(marker, visibleMarkers));
marker.SplatoonElement = element;
elements.Add(element);
} }
else if (marker.Type == Marker.EType.Hoard && config.ShowHoard) else if (marker.Type == Marker.EType.Hoard && config.ShowHoard)
{ {
var element = CreateSplatoonElement(marker.Type, marker.Position, DetermineColor(marker, visibleMarkers)); CreateRenderElement(marker, elements, DetermineColor(marker, visibleMarkers));
marker.SplatoonElement = element;
elements.Add(element);
} }
else if (marker.Type == Marker.EType.Debug && Service.Configuration.BetaKey == "VFX") else if (marker.Type == Marker.EType.Debug && Service.Configuration.BetaKey == "VFX")
{ {
var element = CreateSplatoonElement(marker.Type, marker.Position, DetermineColor(marker, visibleMarkers)); CreateRenderElement(marker, elements, DetermineColor(marker, visibleMarkers));
marker.SplatoonElement = element;
elements.Add(element);
} }
} }
} }
@ -434,18 +412,37 @@ namespace Pal.Client
if (elements.Count == 0) if (elements.Count == 0)
return; 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 Renderer.SetLayer(ELayer.TrapHoard, elements);
new TickScheduler(delegate }
}
private void HandleEphemeralMarkers(IList<Marker> 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<IRenderElement> 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 else
return COLOR_INVISIBLE; return COLOR_INVISIBLE;
} }
else if (marker.Type == Marker.EType.SilverCoffer)
return ImGui.ColorConvertFloat4ToU32(Service.Configuration.SilverCofferColor);
else else
return ImGui.ColorConvertFloat4ToU32(new Vector4(1, 0.5f, 1, 0.4f)); return ImGui.ColorConvertFloat4ToU32(new Vector4(1, 0.5f, 1, 0.4f));
} }
private void HandleEphemeralMarkers(IList<Marker> visibleMarkers, bool recreateLayout) private void CreateRenderElement(Marker marker, List<IRenderElement> elements, uint color, bool fill = false)
{ {
recreateLayout |= EphemeralMarkers.Any(existingMarker => !visibleMarkers.Any(x => x == existingMarker)); var element = Renderer.CreateElement(marker.Type, marker.Position, color, fill);
recreateLayout |= visibleMarkers.Any(visibleMarker => !EphemeralMarkers.Any(x => x == visibleMarker)); marker.RenderElement = element;
elements.Add(element);
if (recreateLayout)
{
Splatoon.RemoveDynamicElements(SPLATOON_REGULAR_COFFERS);
EphemeralMarkers.Clear();
var config = Service.Configuration;
List<Element> elements = new List<Element>();
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}";
}
});
}
} }
#endregion
#region Up-/Download #region Up-/Download
private async Task DownloadMarkersForTerritory(ushort territoryId) private async Task DownloadMarkersForTerritory(ushort territoryId)
@ -616,7 +581,7 @@ namespace Pal.Client
var nearbyMarkers = state.Markers var nearbyMarkers = state.Markers
.Where(m => predicate(m)) .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 }) .Select(m => new { m = m, distance = (playerPosition - m.Position)?.Length() ?? float.MaxValue })
.OrderBy(m => m.distance) .OrderBy(m => m.distance)
.Take(5) .Take(5)
@ -672,27 +637,6 @@ namespace Pal.Client
&& Service.Condition[ConditionFlag.InDeepDungeon] && Service.Condition[ConditionFlag.InDeepDungeon]
&& typeof(ETerritoryType).IsEnumDefined(Service.ClientState.TerritoryType); && 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() private void ReloadLanguageStrings()
{ {
_localizedChatMessages = new LocalizedChatMessages _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) private string GetLocalizedString(uint id)
{ {
return Service.DataManager.GetExcelSheet<LogMessage>()?.GetRow(id)?.Text?.ToString() ?? "Unknown"; return Service.DataManager.GetExcelSheet<LogMessage>()?.GetRow(id)?.Text?.ToString() ?? "Unknown";
@ -719,12 +687,6 @@ namespace Pal.Client
PomanderOfSafetyUsed, PomanderOfSafetyUsed,
} }
private class MarkerConfig
{
public float OffsetY { get; set; } = 0;
public float Radius { get; set; } = 0.25f;
}
private class LocalizedChatMessages private class LocalizedChatMessages
{ {
public string MapRevealed { get; set; } = "???"; //"The map for this floor has been revealed!"; public string MapRevealed { get; set; } = "???"; //"The map for this floor has been revealed!";

View File

@ -0,0 +1,8 @@
namespace Pal.Client.Rendering
{
internal enum ELayer
{
TrapHoard,
RegularCoffers,
}
}

View File

@ -0,0 +1,9 @@
using System.Numerics;
namespace Pal.Client.Rendering
{
internal interface IDrawDebugItems
{
void DrawDebugItems(Vector4 trapColor, Vector4 hoardColor);
}
}

View File

@ -0,0 +1,9 @@
namespace Pal.Client.Rendering
{
public interface IRenderElement
{
bool IsValid { get; }
uint Color { get; set; }
}
}

View File

@ -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<IRenderElement> elements);
void ResetLayer(ELayer layer);
IRenderElement CreateElement(Marker.EType type, Vector3 pos, uint color, bool fill = false);
}
}

View File

@ -0,0 +1,21 @@
using System.Collections.Generic;
namespace Pal.Client.Rendering
{
internal class MarkerConfig
{
private readonly static Dictionary<Marker.EType, MarkerConfig> _markerConfig = new Dictionary<Marker.EType, MarkerConfig>
{
{ 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();
}
}

View File

@ -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
{
/// <summary>
/// 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).
/// </summary>
internal class SimpleRenderer : IRenderer, IDisposable
{
private ConcurrentDictionary<ELayer, SimpleLayer> layers = new();
public void SetLayer(ELayer layer, IReadOnlyList<IRenderElement> elements)
{
layers[layer] = new SimpleLayer
{
TerritoryType = Service.ClientState.TerritoryType,
Elements = elements.Cast<SimpleElement>().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<SimpleElement> 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();
}
}
}
}

View File

@ -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<IRenderElement> 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<SplatoonElement>().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<IRenderElement>
{
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<SplatoonElement>().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<object>();
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;
}
}
}
}

View File

@ -14,6 +14,8 @@
recreateLayout = true; recreateLayout = true;
saveMarkers = true; saveMarkers = true;
} }
plugin.ResetRenderer();
} }
} }
} }

View File

@ -22,6 +22,7 @@ namespace Pal.Client
[PluginService] public static Condition Condition { get; set; } = null!; [PluginService] public static Condition Condition { get; set; } = null!;
[PluginService] public static CommandManager CommandManager { get; set; } = null!; [PluginService] public static CommandManager CommandManager { get; set; } = null!;
[PluginService] public static DataManager DataManager { 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 Plugin Plugin { get; set; } = null!;
public static WindowSystem WindowSystem { get; set; } = new(typeof(Service).AssemblyQualifiedName); public static WindowSystem WindowSystem { get; set; } = new(typeof(Service).AssemblyQualifiedName);

View File

@ -10,6 +10,7 @@ using ECommons.SplatoonAPI;
using Google.Protobuf; using Google.Protobuf;
using ImGuiNET; using ImGuiNET;
using Pal.Client.Net; using Pal.Client.Net;
using Pal.Client.Rendering;
using Pal.Client.Scheduled; using Pal.Client.Scheduled;
using System; using System;
using System.Collections; using System.Collections;
@ -28,6 +29,7 @@ namespace Pal.Client.Windows
internal class ConfigWindow : Window internal class ConfigWindow : Window
{ {
private int _mode; private int _mode;
private int _renderer;
private bool _showTraps; private bool _showTraps;
private Vector4 _trapColor; private Vector4 _trapColor;
private bool _onlyVisibleTrapsAfterPomander; private bool _onlyVisibleTrapsAfterPomander;
@ -64,6 +66,7 @@ namespace Pal.Client.Windows
{ {
var config = Service.Configuration; var config = Service.Configuration;
_mode = (int)config.Mode; _mode = (int)config.Mode;
_renderer = (int)config.Renderer;
_showTraps = config.ShowTraps; _showTraps = config.ShowTraps;
_trapColor = config.TrapColor; _trapColor = config.TrapColor;
_onlyVisibleTrapsAfterPomander = config.OnlyVisibleTrapsAfterPomander; _onlyVisibleTrapsAfterPomander = config.OnlyVisibleTrapsAfterPomander;
@ -92,6 +95,7 @@ namespace Pal.Client.Windows
DrawCommunityTab(ref saveAndClose); DrawCommunityTab(ref saveAndClose);
DrawImportTab(); DrawImportTab();
DrawExportTab(); DrawExportTab();
DrawRenderTab(ref save, ref saveAndClose);
DrawDebugTab(); DrawDebugTab();
ImGui.EndTabBar(); ImGui.EndTabBar();
@ -103,6 +107,7 @@ namespace Pal.Client.Windows
{ {
var config = Service.Configuration; var config = Service.Configuration;
config.Mode = (Configuration.EMode)_mode; config.Mode = (Configuration.EMode)_mode;
config.Renderer = (Configuration.ERenderer)_renderer;
config.ShowTraps = _showTraps; config.ShowTraps = _showTraps;
config.TrapColor = _trapColor; config.TrapColor = _trapColor;
config.OnlyVisibleTrapsAfterPomander = _onlyVisibleTrapsAfterPomander; 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() private void DrawDebugTab()
{ {
if (ImGui.BeginTabItem("Debug")) if (ImGui.BeginTabItem("Debug"))
@ -323,59 +353,10 @@ namespace Pal.Client.Windows
else else
ImGui.Text("You are NOT in a deep dungeon."); ImGui.Text("You are NOT in a deep dungeon.");
ImGui.Separator();
if (ImGui.Button("Draw trap & coffer circles around self"))
DrawDebugItems();
ImGui.EndTabItem(); ImGui.EndTabItem();
} }
} }
private void DrawDebugItems()
{
try
{
Vector3? pos = Service.ClientState.LocalPlayer?.Position;
if (pos != null)
{
var elements = new List<Element>
{
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<object>();
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?");
}
}
/// <summary> /// <summary>
/// None of the default BeginTabItem methods allow using flags without making the tab have a close button for some reason. /// None of the default BeginTabItem methods allow using flags without making the tab have a close button for some reason.
/// </summary> /// </summary>