Show silver coffers

rendering v1.4
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 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<Element>
{
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)

View File

@ -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();

View File

@ -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
}
}
}

View File

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

View File

@ -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<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;
internal ConcurrentDictionary<ushort, ConcurrentBag<Marker>> FloorMarkers { get; } = new();
internal ConcurrentBag<Marker> 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<Marker>(LoadSavedMarkers());
EphemeralMarkers.Clear();
recreateLayout = true;
DebugMessage = null;
}
@ -179,75 +184,8 @@ namespace Pal.Client
FloorMarkers[LastTerritory] = currentFloorMarkers = new ConcurrentBag<Marker>();
IList<Marker> 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<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}";
}
});
}
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<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");
private List<Marker> 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;
}
}
}

View File

@ -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<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
{
Type = m.Type,
Type = (ObjectType)m.Type,
X = m.Position.X,
Y = m.Position.Y,
Z = m.Position.Z