diff --git a/Pal.Client/ConfigWindow.cs b/Pal.Client/ConfigWindow.cs index e2a5c27..a3ed21e 100644 --- a/Pal.Client/ConfigWindow.cs +++ b/Pal.Client/ConfigWindow.cs @@ -1,15 +1,11 @@ -using Dalamud.Interface.Windowing; -using ECommons.Automation; -using ECommons.DalamudServices; +using Dalamud.Interface.Components; +using Dalamud.Interface.Windowing; using ECommons.SplatoonAPI; using ImGuiNET; -using ImGuizmoNET; using System; using System.Collections.Generic; using System.Linq; using System.Numerics; -using System.Reflection; -using System.Text; using System.Threading.Tasks; namespace Pal.Client @@ -21,6 +17,10 @@ namespace Pal.Client private Vector4 _trapColor; private bool _showHoard; private Vector4 _hoardColor; + private bool _showSilverCoffers; + private Vector4 _silverCofferColor; + private bool _fillSilverCoffers; + private string _connectionText; public ConfigWindow() : base("Palace Pal - Configuration###PalPalaceConfig") @@ -39,6 +39,9 @@ namespace Pal.Client _trapColor = config.TrapColor; _showHoard = config.ShowHoard; _hoardColor = config.HoardColor; + _showSilverCoffers = config.ShowSilverCoffers; + _silverCofferColor = config.SilverCofferColor; + _fillSilverCoffers = config.FillSilverCoffers; _connectionText = null; } @@ -70,6 +73,18 @@ namespace Pal.Client ImGui.Separator(); + ImGui.Checkbox("Show silver coffers on current floor", ref _showSilverCoffers); + ImGuiComponents.HelpMarker("Shows all the silver coffers visible to you on the current floor.\nThis is not synchronized with other players and not saved between floors/runs.\n\nExperimental feature."); + ImGui.Indent(); + ImGui.BeginDisabled(!_showSilverCoffers); + ImGui.Spacing(); + ImGui.ColorEdit4("Silver Coffer color", ref _silverCofferColor, ImGuiColorEditFlags.NoInputs); + ImGui.Checkbox("Draw filled", ref _fillSilverCoffers); + ImGui.EndDisabled(); + ImGui.Unindent(); + + ImGui.Separator(); + save = ImGui.Button("Save"); ImGui.SameLine(); saveAndClose = ImGui.Button("Save & Close"); @@ -124,14 +139,26 @@ namespace Pal.Client ImGui.Indent(); if (plugin.FloorMarkers.TryGetValue(plugin.LastTerritory, out var currentFloorMarkers)) { - if (_showTraps) - ImGui.Text($"{currentFloorMarkers.Count(x => x != null && x.Type == Palace.ObjectType.Trap)} known traps"); + if (_showTraps) + { + int traps = currentFloorMarkers.Count(x => x != null && x.Type == Marker.EType.Trap); + ImGui.Text($"{traps} known trap{(traps == 1 ? "" : "s")}"); + } if (_showHoard) - ImGui.Text($"{currentFloorMarkers.Count(x => x != null && x.Type == Palace.ObjectType.Hoard)} known hoard coffers"); + { + int hoardCoffers = currentFloorMarkers.Count(x => x != null && x.Type == Marker.EType.Hoard); + ImGui.Text($"{hoardCoffers} known hoard coffer{(hoardCoffers == 1 ? "" : "s")}"); + } + if (_showSilverCoffers) + { + int silverCoffers = plugin.EphemeralMarkers.Count(x => x != null && x.Type == Marker.EType.SilverCoffer); + ImGui.Text($"{silverCoffers} silver coffer{(silverCoffers == 1 ? "" : "s")} visible on current floor"); + } } else ImGui.Text("Could not query current trap/coffer count."); ImGui.Unindent(); + ImGui.TextWrapped("Traps and coffers may not be discovered even after using a pomander if they're far away (around 1,5-2 rooms)."); } else ImGui.Text("You are NOT in a deep dungeon."); @@ -145,8 +172,8 @@ namespace Pal.Client var pos = Service.ClientState.LocalPlayer.Position; var elements = new List { - Plugin.CreateSplatoonElement(Palace.ObjectType.Trap, pos, _trapColor), - Plugin.CreateSplatoonElement(Palace.ObjectType.Hoard, pos, _hoardColor), + Plugin.CreateSplatoonElement(Marker.EType.Trap, pos, _trapColor), + Plugin.CreateSplatoonElement(Marker.EType.Hoard, pos, _hoardColor), }; if (!Splatoon.AddDynamicElements("PalacePal.Test", elements.ToArray(), new long[] { Environment.TickCount64 + 10000 })) @@ -174,6 +201,9 @@ namespace Pal.Client config.TrapColor = _trapColor; config.ShowHoard = _showHoard; config.HoardColor = _hoardColor; + config.ShowSilverCoffers = _showSilverCoffers; + config.SilverCofferColor = _silverCofferColor; + config.FillSilverCoffers = _fillSilverCoffers; config.Save(); if (saveAndClose) diff --git a/Pal.Client/Configuration.cs b/Pal.Client/Configuration.cs index ab75422..c90dc76 100644 --- a/Pal.Client/Configuration.cs +++ b/Pal.Client/Configuration.cs @@ -20,6 +20,9 @@ namespace Pal.Client public Vector4 TrapColor { get; set; } = new Vector4(1, 0, 0, 0.4f); public bool ShowHoard { get; set; } = true; public Vector4 HoardColor { get; set; } = new Vector4(0, 1, 1, 0.4f); + public bool ShowSilverCoffers { get; set; } = false; + public Vector4 SilverCofferColor { get; set; } = new Vector4(1, 1, 1, 0.4f); + public bool FillSilverCoffers { get; set; } = true; #endregion public delegate void OnSaved(); diff --git a/Pal.Client/Marker.cs b/Pal.Client/Marker.cs index e13787c..a7a71c7 100644 --- a/Pal.Client/Marker.cs +++ b/Pal.Client/Marker.cs @@ -8,7 +8,7 @@ namespace Pal.Client { internal class Marker { - public ObjectType Type { get; set; } = ObjectType.Unknown; + public EType Type { get; set; } = EType.Unknown; public Vector3 Position { get; set; } public bool Seen { get; set; } = false; @@ -18,7 +18,7 @@ namespace Pal.Client [JsonIgnore] public Element SplatoonElement { get; set; } - public Marker(ObjectType type, Vector3 position) + public Marker(EType type, Vector3 position) { Type = type; Position = position; @@ -31,7 +31,34 @@ namespace Pal.Client public override bool Equals(object obj) { - return obj is Marker otherMarker && Type == otherMarker.Type && Position == otherMarker.Position; + return obj is Marker otherMarker && Type == otherMarker.Type && (int)Position.X == (int)otherMarker.Position.X && (int)Position.Y == (int)otherMarker.Position.Y && (int)Position.Z == (int)otherMarker.Position.Z; + } + + public static bool operator ==(Marker a, object b) + { + return Equals(a, b); + } + + public static bool operator !=(Marker a, object b) + { + return !Equals(a, b); + } + + + public bool IsPermanent() => Type == EType.Trap || Type == EType.Hoard; + + public enum EType + { + Unknown = ObjectType.Unknown, + + #region Permanent Markers + Trap = ObjectType.Trap, + Hoard = ObjectType.Hoard, + #endregion + + # region Markers that only show up if they're currently visible + SilverCoffer = 100, + #endregion } } } diff --git a/Pal.Client/Pal.Client.csproj b/Pal.Client/Pal.Client.csproj index c3ac629..fd39574 100644 --- a/Pal.Client/Pal.Client.csproj +++ b/Pal.Client/Pal.Client.csproj @@ -3,7 +3,7 @@ net6.0-windows 9.0 - 1.3.0.0 + 1.4.0.0 diff --git a/Pal.Client/Plugin.cs b/Pal.Client/Plugin.cs index 6c36bec..b924bfd 100644 --- a/Pal.Client/Plugin.cs +++ b/Pal.Client/Plugin.cs @@ -1,8 +1,5 @@ using Dalamud.Game; -using Dalamud.Game.ClientState; using Dalamud.Game.ClientState.Conditions; -using Dalamud.Game.ClientState.Objects; -using Dalamud.Game.ClientState.Objects.Enums; using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Game.Command; using Dalamud.Interface.Windowing; @@ -10,9 +7,7 @@ using Dalamud.Plugin; using ECommons; using ECommons.Schedulers; using ECommons.SplatoonAPI; -using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using ImGuiNET; -using Lumina.Excel.GeneratedSheets; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -30,9 +25,16 @@ namespace Pal.Client private const long ON_TERRITORY_CHANGE = -2; private readonly ConcurrentQueue<(ushort territoryId, bool success, IList markers)> _remoteDownloads = new(); + 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 } }, + }; private bool _configUpdated = false; internal ConcurrentDictionary> FloorMarkers { get; } = new(); + internal ConcurrentBag EphemeralMarkers { get; set; } = new(); internal ushort LastTerritory { get; private set; } public SyncState TerritorySyncState { get; set; } public string DebugMessage { get; set; } @@ -127,6 +129,7 @@ namespace Pal.Client { try { + bool recreateLayout = false; if (_configUpdated) { if (Service.Configuration.Mode == Configuration.EMode.Offline) @@ -141,12 +144,13 @@ namespace Pal.Client } FloorMarkers.Clear(); + EphemeralMarkers.Clear(); LastTerritory = 0; } _configUpdated = false; + recreateLayout = true; } - bool recreateLayout = false; bool saveMarkers = false; if (LastTerritory != Service.ClientState.TerritoryType) { @@ -155,6 +159,7 @@ namespace Pal.Client if (IsInPotdOrHoh()) FloorMarkers[LastTerritory] = new ConcurrentBag(LoadSavedMarkers()); + EphemeralMarkers.Clear(); recreateLayout = true; DebugMessage = null; } @@ -179,75 +184,8 @@ namespace Pal.Client FloorMarkers[LastTerritory] = currentFloorMarkers = new ConcurrentBag(); IList visibleMarkers = GetRelevantGameObjects(); - foreach (var visibleMarker in visibleMarkers) - { - Marker knownMarker = currentFloorMarkers.SingleOrDefault(x => x != null && x.GetHashCode() == visibleMarker.GetHashCode()); - if (knownMarker != null) - { - if (!knownMarker.Seen) - { - knownMarker.Seen = true; - saveMarkers = true; - } - continue; - } - - currentFloorMarkers.Add(visibleMarker); - recreateLayout = true; - saveMarkers = true; - } - - if (saveMarkers) - { - SaveMarkers(); - - if (TerritorySyncState == SyncState.Complete) - { - var markersToUpload = currentFloorMarkers.Where(x => !x.RemoteSeen).ToList(); - Task.Run(async () => await Service.RemoteApi.UploadMarker(LastTerritory, markersToUpload)); - } - } - - if (recreateLayout) - { - Splatoon.RemoveDynamicElements("PalacePal.Markers"); - - - var config = Service.Configuration; - - List elements = new List(); - foreach (var marker in currentFloorMarkers) - { - if (marker.Seen || config.Mode == Configuration.EMode.Online) - { - if (marker.Type == Palace.ObjectType.Trap && config.ShowTraps) - { - var element = CreateSplatoonElement(marker.Type, marker.Position, config.TrapColor); - marker.SplatoonElement = element; - elements.Add(element); - } - else if (marker.Type == Palace.ObjectType.Hoard && config.ShowHoard) - { - var element = CreateSplatoonElement(marker.Type, marker.Position, config.HoardColor); - marker.SplatoonElement = element; - elements.Add(element); - } - } - } - - // 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("PalacePal.Markers", elements.ToArray(), new long[] { Environment.TickCount64 + 60 * 60 * 1000, ON_TERRITORY_CHANGE }); - } - catch (Exception e) - { - DebugMessage = $"{DateTime.Now}\n{e}"; - } - }); - } + HandlePersistentMarkers(currentFloorMarkers, visibleMarkers.Where(x => x.IsPermanent()).ToList(), saveMarkers, recreateLayout); + HandleEphemeralMarkers(visibleMarkers.Where(x => !x.IsPermanent()).ToList(), recreateLayout); } catch (Exception e) { @@ -255,6 +193,124 @@ namespace Pal.Client } } + private void HandlePersistentMarkers(ConcurrentBag currentFloorMarkers, IList visibleMarkers, bool saveMarkers, bool recreateLayout) + { + + foreach (var visibleMarker in visibleMarkers) + { + Marker knownMarker = currentFloorMarkers.SingleOrDefault(x => x == visibleMarker); + if (knownMarker != null) + { + if (!knownMarker.Seen) + { + knownMarker.Seen = true; + saveMarkers = true; + } + continue; + } + + currentFloorMarkers.Add(visibleMarker); + recreateLayout = true; + saveMarkers = true; + } + + if (saveMarkers) + { + SaveMarkers(); + + if (TerritorySyncState == SyncState.Complete) + { + var markersToUpload = currentFloorMarkers.Where(x => x.IsPermanent() && !x.RemoteSeen).ToList(); + Task.Run(async () => await Service.RemoteApi.UploadMarker(LastTerritory, markersToUpload)); + } + } + + if (recreateLayout) + { + Splatoon.RemoveDynamicElements("PalacePal.TrapHoard"); + + var config = Service.Configuration; + + List elements = new List(); + foreach (var marker in currentFloorMarkers) + { + if (marker.Seen || config.Mode == Configuration.EMode.Online) + { + if (marker.Type == Marker.EType.Trap && config.ShowTraps) + { + var element = CreateSplatoonElement(marker.Type, marker.Position, config.TrapColor); + marker.SplatoonElement = element; + elements.Add(element); + } + else if (marker.Type == Marker.EType.Hoard && config.ShowHoard) + { + var element = CreateSplatoonElement(marker.Type, marker.Position, config.HoardColor); + marker.SplatoonElement = element; + elements.Add(element); + } + } + } + + 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 + { + try + { + Splatoon.AddDynamicElements("PalacePal.TrapHoard", elements.ToArray(), new long[] { Environment.TickCount64 + 60 * 60 * 1000, ON_TERRITORY_CHANGE }); + } + catch (Exception e) + { + DebugMessage = $"{DateTime.Now}\n{e}"; + } + }); + } + } + + 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) + { + Splatoon.RemoveDynamicElements("PalacePal.RegularCoffers"); + 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("PalacePal.RegularCoffers", elements.ToArray(), new long[] { Environment.TickCount64 + 60 * 60 * 1000, ON_TERRITORY_CHANGE }); + } + catch (Exception e) + { + DebugMessage = $"{DateTime.Now}\n{e}"; + } + }); + } + } + public string GetSaveForCurrentTerritory() => Path.Join(Service.PluginInterface.GetPluginConfigDirectory(), $"{LastTerritory}.json"); private List LoadSavedMarkers() @@ -294,7 +350,7 @@ namespace Pal.Client { foreach (var downloadedMarker in downloadedMarkers) { - Marker seenMarker = currentFloorMarkers.SingleOrDefault(x => x.GetHashCode() == downloadedMarker.GetHashCode()); + Marker seenMarker = currentFloorMarkers.SingleOrDefault(x => x == downloadedMarker); if (seenMarker != null) { seenMarker.RemoteSeen = true; @@ -333,12 +389,16 @@ namespace Pal.Client case 2007185: case 2007186: case 2009504: - result.Add(new Marker(Palace.ObjectType.Trap, obj.Position) { Seen = true }); + result.Add(new Marker(Marker.EType.Trap, obj.Position) { Seen = true }); break; case 2007542: case 2007543: - result.Add(new Marker(Palace.ObjectType.Hoard, obj.Position) { Seen = true }); + result.Add(new Marker(Marker.EType.Hoard, obj.Position) { Seen = true }); + break; + + case 2007357: + result.Add(new Marker(Marker.EType.SilverCoffer, obj.Position) { Seen = true }); break; } } @@ -348,7 +408,7 @@ namespace Pal.Client internal bool IsInPotdOrHoh() => Service.ClientState.IsLoggedIn && Service.Condition[ConditionFlag.InDeepDungeon]; - internal static Element CreateSplatoonElement(Palace.ObjectType type, Vector3 pos, Vector4 color) + internal static Element CreateSplatoonElement(Marker.EType type, Vector3 pos, Vector4 color, bool fill = false) { return new Element(ElementType.CircleAtFixedCoordinates) { @@ -357,9 +417,9 @@ namespace Pal.Client refZ = pos.Y, offX = 0, offY = 0, - offZ = type == Palace.ObjectType.Trap ? 0 : -0.03f, - Filled = false, - radius = 1.7f, + offZ = _markerConfig[type].OffsetY, + Filled = fill, + radius = _markerConfig[type].Radius, FillStep = 1, color = ImGui.ColorConvertFloat4ToU32(color), thicc = 2, @@ -373,5 +433,11 @@ namespace Pal.Client Complete, Failed, } + + private class MarkerConfig + { + public float OffsetY { get; set; } = 0; + public float Radius { get; set; } = 0.25f; + } } } diff --git a/Pal.Client/RemoteApi.cs b/Pal.Client/RemoteApi.cs index e12918c..371c3d6 100644 --- a/Pal.Client/RemoteApi.cs +++ b/Pal.Client/RemoteApi.cs @@ -115,7 +115,7 @@ namespace Pal.Client var palaceClient = new PalaceService.PalaceServiceClient(_channel); var downloadReply = await palaceClient.DownloadFloorsAsync(new DownloadFloorsRequest { TerritoryType = territoryId }, headers: AuthorizedHeaders(), cancellationToken: cancellationToken); - return (downloadReply.Success, downloadReply.Objects.Select(o => new Marker(o.Type, new Vector3(o.X, o.Y, o.Z)) { RemoteSeen = true }).ToList()); + return (downloadReply.Success, downloadReply.Objects.Select(o => new Marker((Marker.EType)o.Type, new Vector3(o.X, o.Y, o.Z)) { RemoteSeen = true }).ToList()); } public async Task UploadMarker(ushort territoryType, IList markers, CancellationToken cancellationToken = default) @@ -133,7 +133,7 @@ namespace Pal.Client }; uploadRequest.Objects.AddRange(markers.Select(m => new PalaceObject { - Type = m.Type, + Type = (ObjectType)m.Type, X = m.Position.X, Y = m.Position.Y, Z = m.Position.Z