Show silver coffers

This commit is contained in:
Liza 2022-10-25 23:31:35 +02:00
parent 89d1c46009
commit c5d3c90b85
6 changed files with 225 additions and 99 deletions

View File

@ -1,15 +1,11 @@
using Dalamud.Interface.Windowing; using Dalamud.Interface.Components;
using ECommons.Automation; using Dalamud.Interface.Windowing;
using ECommons.DalamudServices;
using ECommons.SplatoonAPI; using ECommons.SplatoonAPI;
using ImGuiNET; using ImGuiNET;
using ImGuizmoNET;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Numerics; using System.Numerics;
using System.Reflection;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Pal.Client namespace Pal.Client
@ -21,6 +17,10 @@ namespace Pal.Client
private Vector4 _trapColor; private Vector4 _trapColor;
private bool _showHoard; private bool _showHoard;
private Vector4 _hoardColor; private Vector4 _hoardColor;
private bool _showSilverCoffers;
private Vector4 _silverCofferColor;
private bool _fillSilverCoffers;
private string _connectionText; private string _connectionText;
public ConfigWindow() : base("Palace Pal - Configuration###PalPalaceConfig") public ConfigWindow() : base("Palace Pal - Configuration###PalPalaceConfig")
@ -39,6 +39,9 @@ namespace Pal.Client
_trapColor = config.TrapColor; _trapColor = config.TrapColor;
_showHoard = config.ShowHoard; _showHoard = config.ShowHoard;
_hoardColor = config.HoardColor; _hoardColor = config.HoardColor;
_showSilverCoffers = config.ShowSilverCoffers;
_silverCofferColor = config.SilverCofferColor;
_fillSilverCoffers = config.FillSilverCoffers;
_connectionText = null; _connectionText = null;
} }
@ -70,6 +73,18 @@ namespace Pal.Client
ImGui.Separator(); 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"); save = ImGui.Button("Save");
ImGui.SameLine(); ImGui.SameLine();
saveAndClose = ImGui.Button("Save & Close"); saveAndClose = ImGui.Button("Save & Close");
@ -124,14 +139,26 @@ namespace Pal.Client
ImGui.Indent(); ImGui.Indent();
if (plugin.FloorMarkers.TryGetValue(plugin.LastTerritory, out var currentFloorMarkers)) if (plugin.FloorMarkers.TryGetValue(plugin.LastTerritory, out var currentFloorMarkers))
{ {
if (_showTraps) if (_showTraps)
ImGui.Text($"{currentFloorMarkers.Count(x => x != null && x.Type == Palace.ObjectType.Trap)} known traps"); {
int traps = currentFloorMarkers.Count(x => x != null && x.Type == Marker.EType.Trap);
ImGui.Text($"{traps} known trap{(traps == 1 ? "" : "s")}");
}
if (_showHoard) 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 else
ImGui.Text("Could not query current trap/coffer count."); ImGui.Text("Could not query current trap/coffer count.");
ImGui.Unindent(); 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 else
ImGui.Text("You are NOT in a deep dungeon."); ImGui.Text("You are NOT in a deep dungeon.");
@ -145,8 +172,8 @@ namespace Pal.Client
var pos = Service.ClientState.LocalPlayer.Position; var pos = Service.ClientState.LocalPlayer.Position;
var elements = new List<Element> var elements = new List<Element>
{ {
Plugin.CreateSplatoonElement(Palace.ObjectType.Trap, pos, _trapColor), Plugin.CreateSplatoonElement(Marker.EType.Trap, pos, _trapColor),
Plugin.CreateSplatoonElement(Palace.ObjectType.Hoard, pos, _hoardColor), Plugin.CreateSplatoonElement(Marker.EType.Hoard, pos, _hoardColor),
}; };
if (!Splatoon.AddDynamicElements("PalacePal.Test", elements.ToArray(), new long[] { Environment.TickCount64 + 10000 })) if (!Splatoon.AddDynamicElements("PalacePal.Test", elements.ToArray(), new long[] { Environment.TickCount64 + 10000 }))
@ -174,6 +201,9 @@ namespace Pal.Client
config.TrapColor = _trapColor; config.TrapColor = _trapColor;
config.ShowHoard = _showHoard; config.ShowHoard = _showHoard;
config.HoardColor = _hoardColor; config.HoardColor = _hoardColor;
config.ShowSilverCoffers = _showSilverCoffers;
config.SilverCofferColor = _silverCofferColor;
config.FillSilverCoffers = _fillSilverCoffers;
config.Save(); config.Save();
if (saveAndClose) if (saveAndClose)

View File

@ -20,6 +20,9 @@ namespace Pal.Client
public Vector4 TrapColor { get; set; } = new Vector4(1, 0, 0, 0.4f); public Vector4 TrapColor { get; set; } = new Vector4(1, 0, 0, 0.4f);
public bool ShowHoard { get; set; } = true; public bool ShowHoard { get; set; } = true;
public Vector4 HoardColor { get; set; } = new Vector4(0, 1, 1, 0.4f); 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 #endregion
public delegate void OnSaved(); public delegate void OnSaved();

View File

@ -8,7 +8,7 @@ namespace Pal.Client
{ {
internal class Marker internal class Marker
{ {
public ObjectType Type { get; set; } = ObjectType.Unknown; public EType Type { get; set; } = EType.Unknown;
public Vector3 Position { get; set; } public Vector3 Position { get; set; }
public bool Seen { get; set; } = false; public bool Seen { get; set; } = false;
@ -18,7 +18,7 @@ namespace Pal.Client
[JsonIgnore] [JsonIgnore]
public Element SplatoonElement { get; set; } public Element SplatoonElement { get; set; }
public Marker(ObjectType type, Vector3 position) public Marker(EType type, Vector3 position)
{ {
Type = type; Type = type;
Position = position; Position = position;
@ -31,7 +31,34 @@ namespace Pal.Client
public override bool Equals(object obj) 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
} }
} }
} }

View File

@ -3,7 +3,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0-windows</TargetFramework> <TargetFramework>net6.0-windows</TargetFramework>
<LangVersion>9.0</LangVersion> <LangVersion>9.0</LangVersion>
<Version>1.3.0.0</Version> <Version>1.4.0.0</Version>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>

View File

@ -1,8 +1,5 @@
using Dalamud.Game; using Dalamud.Game;
using Dalamud.Game.ClientState;
using Dalamud.Game.ClientState.Conditions; 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.ClientState.Objects.Types;
using Dalamud.Game.Command; using Dalamud.Game.Command;
using Dalamud.Interface.Windowing; using Dalamud.Interface.Windowing;
@ -10,9 +7,7 @@ using Dalamud.Plugin;
using ECommons; using ECommons;
using ECommons.Schedulers; using ECommons.Schedulers;
using ECommons.SplatoonAPI; using ECommons.SplatoonAPI;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using ImGuiNET; using ImGuiNET;
using Lumina.Excel.GeneratedSheets;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
@ -30,9 +25,16 @@ namespace Pal.Client
private const long ON_TERRITORY_CHANGE = -2; private const long ON_TERRITORY_CHANGE = -2;
private readonly ConcurrentQueue<(ushort territoryId, bool success, IList<Marker> markers)> _remoteDownloads = new(); private readonly ConcurrentQueue<(ushort territoryId, bool success, IList<Marker> markers)> _remoteDownloads = new();
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 } },
};
private bool _configUpdated = false; private bool _configUpdated = false;
internal ConcurrentDictionary<ushort, ConcurrentBag<Marker>> FloorMarkers { get; } = new(); internal ConcurrentDictionary<ushort, ConcurrentBag<Marker>> FloorMarkers { get; } = new();
internal ConcurrentBag<Marker> EphemeralMarkers { get; set; } = new();
internal ushort LastTerritory { get; private set; } internal ushort LastTerritory { get; private set; }
public SyncState TerritorySyncState { get; set; } public SyncState TerritorySyncState { get; set; }
public string DebugMessage { get; set; } public string DebugMessage { get; set; }
@ -127,6 +129,7 @@ namespace Pal.Client
{ {
try try
{ {
bool recreateLayout = false;
if (_configUpdated) if (_configUpdated)
{ {
if (Service.Configuration.Mode == Configuration.EMode.Offline) if (Service.Configuration.Mode == Configuration.EMode.Offline)
@ -141,12 +144,13 @@ namespace Pal.Client
} }
FloorMarkers.Clear(); FloorMarkers.Clear();
EphemeralMarkers.Clear();
LastTerritory = 0; LastTerritory = 0;
} }
_configUpdated = false; _configUpdated = false;
recreateLayout = true;
} }
bool recreateLayout = false;
bool saveMarkers = false; bool saveMarkers = false;
if (LastTerritory != Service.ClientState.TerritoryType) if (LastTerritory != Service.ClientState.TerritoryType)
{ {
@ -155,6 +159,7 @@ namespace Pal.Client
if (IsInPotdOrHoh()) if (IsInPotdOrHoh())
FloorMarkers[LastTerritory] = new ConcurrentBag<Marker>(LoadSavedMarkers()); FloorMarkers[LastTerritory] = new ConcurrentBag<Marker>(LoadSavedMarkers());
EphemeralMarkers.Clear();
recreateLayout = true; recreateLayout = true;
DebugMessage = null; DebugMessage = null;
} }
@ -179,75 +184,8 @@ namespace Pal.Client
FloorMarkers[LastTerritory] = currentFloorMarkers = new ConcurrentBag<Marker>(); FloorMarkers[LastTerritory] = currentFloorMarkers = new ConcurrentBag<Marker>();
IList<Marker> visibleMarkers = GetRelevantGameObjects(); IList<Marker> visibleMarkers = GetRelevantGameObjects();
foreach (var visibleMarker in visibleMarkers) HandlePersistentMarkers(currentFloorMarkers, visibleMarkers.Where(x => x.IsPermanent()).ToList(), saveMarkers, recreateLayout);
{ HandleEphemeralMarkers(visibleMarkers.Where(x => !x.IsPermanent()).ToList(), recreateLayout);
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<Element> elements = new List<Element>();
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}";
}
});
}
} }
catch (Exception e) catch (Exception e)
{ {
@ -255,6 +193,124 @@ namespace Pal.Client
} }
} }
private void HandlePersistentMarkers(ConcurrentBag<Marker> currentFloorMarkers, IList<Marker> 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<Element> elements = new List<Element>();
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<Marker> 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<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("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"); public string GetSaveForCurrentTerritory() => Path.Join(Service.PluginInterface.GetPluginConfigDirectory(), $"{LastTerritory}.json");
private List<Marker> LoadSavedMarkers() private List<Marker> LoadSavedMarkers()
@ -294,7 +350,7 @@ namespace Pal.Client
{ {
foreach (var downloadedMarker in downloadedMarkers) foreach (var downloadedMarker in downloadedMarkers)
{ {
Marker seenMarker = currentFloorMarkers.SingleOrDefault(x => x.GetHashCode() == downloadedMarker.GetHashCode()); Marker seenMarker = currentFloorMarkers.SingleOrDefault(x => x == downloadedMarker);
if (seenMarker != null) if (seenMarker != null)
{ {
seenMarker.RemoteSeen = true; seenMarker.RemoteSeen = true;
@ -333,12 +389,16 @@ namespace Pal.Client
case 2007185: case 2007185:
case 2007186: case 2007186:
case 2009504: 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; break;
case 2007542: case 2007542:
case 2007543: 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; break;
} }
} }
@ -348,7 +408,7 @@ namespace Pal.Client
internal bool IsInPotdOrHoh() => Service.ClientState.IsLoggedIn && Service.Condition[ConditionFlag.InDeepDungeon]; 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) return new Element(ElementType.CircleAtFixedCoordinates)
{ {
@ -357,9 +417,9 @@ namespace Pal.Client
refZ = pos.Y, refZ = pos.Y,
offX = 0, offX = 0,
offY = 0, offY = 0,
offZ = type == Palace.ObjectType.Trap ? 0 : -0.03f, offZ = _markerConfig[type].OffsetY,
Filled = false, Filled = fill,
radius = 1.7f, radius = _markerConfig[type].Radius,
FillStep = 1, FillStep = 1,
color = ImGui.ColorConvertFloat4ToU32(color), color = ImGui.ColorConvertFloat4ToU32(color),
thicc = 2, thicc = 2,
@ -373,5 +433,11 @@ namespace Pal.Client
Complete, Complete,
Failed, Failed,
} }
private class MarkerConfig
{
public float OffsetY { get; set; } = 0;
public float Radius { get; set; } = 0.25f;
}
} }
} }

View File

@ -115,7 +115,7 @@ namespace Pal.Client
var palaceClient = new PalaceService.PalaceServiceClient(_channel); var palaceClient = new PalaceService.PalaceServiceClient(_channel);
var downloadReply = await palaceClient.DownloadFloorsAsync(new DownloadFloorsRequest { TerritoryType = territoryId }, headers: AuthorizedHeaders(), cancellationToken: cancellationToken); 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<bool> UploadMarker(ushort territoryType, IList<Marker> markers, CancellationToken cancellationToken = default) public async Task<bool> UploadMarker(ushort territoryType, IList<Marker> markers, CancellationToken cancellationToken = default)
@ -133,7 +133,7 @@ namespace Pal.Client
}; };
uploadRequest.Objects.AddRange(markers.Select(m => new PalaceObject uploadRequest.Objects.AddRange(markers.Select(m => new PalaceObject
{ {
Type = m.Type, Type = (ObjectType)m.Type,
X = m.Position.X, X = m.Position.X,
Y = m.Position.Y, Y = m.Position.Y,
Z = m.Position.Z Z = m.Position.Z