Statistics view
This commit is contained in:
parent
c5d3c90b85
commit
4bd37bdefd
@ -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.4.0.0</Version>
|
<Version>1.5.0.0</Version>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
@ -29,6 +29,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Pal.Common\Pal.Common.csproj" />
|
||||||
<ProjectReference Include="..\vendor\ECommons\ECommons\ECommons.csproj" />
|
<ProjectReference Include="..\vendor\ECommons\ECommons\ECommons.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
@ -7,7 +7,9 @@ using Dalamud.Plugin;
|
|||||||
using ECommons;
|
using ECommons;
|
||||||
using ECommons.Schedulers;
|
using ECommons.Schedulers;
|
||||||
using ECommons.SplatoonAPI;
|
using ECommons.SplatoonAPI;
|
||||||
|
using Grpc.Core;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
|
using Pal.Client.Windows;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@ -63,6 +65,12 @@ namespace Pal.Client
|
|||||||
Service.WindowSystem.AddWindow(configWindow);
|
Service.WindowSystem.AddWindow(configWindow);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var statisticsWindow = pluginInterface.Create<StatisticsWindow>();
|
||||||
|
if (statisticsWindow is not null)
|
||||||
|
{
|
||||||
|
Service.WindowSystem.AddWindow(statisticsWindow);
|
||||||
|
}
|
||||||
|
|
||||||
pluginInterface.UiBuilder.Draw += Service.WindowSystem.Draw;
|
pluginInterface.UiBuilder.Draw += Service.WindowSystem.Draw;
|
||||||
pluginInterface.UiBuilder.OpenConfigUi += OnOpenConfigUi;
|
pluginInterface.UiBuilder.OpenConfigUi += OnOpenConfigUi;
|
||||||
Service.Framework.Update += OnFrameworkUpdate;
|
Service.Framework.Update += OnFrameworkUpdate;
|
||||||
@ -93,7 +101,16 @@ namespace Pal.Client
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Service.WindowSystem.GetWindow<ConfigWindow>()?.Toggle();
|
switch (arguments)
|
||||||
|
{
|
||||||
|
case "stats":
|
||||||
|
Task.Run(async () => await FetchFloorStatistics());
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
Service.WindowSystem.GetWindow<ConfigWindow>()?.Toggle();
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#region IDisposable Support
|
#region IDisposable Support
|
||||||
@ -150,7 +167,7 @@ namespace Pal.Client
|
|||||||
_configUpdated = false;
|
_configUpdated = false;
|
||||||
recreateLayout = true;
|
recreateLayout = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool saveMarkers = false;
|
bool saveMarkers = false;
|
||||||
if (LastTerritory != Service.ClientState.TerritoryType)
|
if (LastTerritory != Service.ClientState.TerritoryType)
|
||||||
{
|
{
|
||||||
@ -282,8 +299,8 @@ namespace Pal.Client
|
|||||||
var config = Service.Configuration;
|
var config = Service.Configuration;
|
||||||
|
|
||||||
List<Element> elements = new List<Element>();
|
List<Element> elements = new List<Element>();
|
||||||
foreach (var marker in visibleMarkers)
|
foreach (var marker in visibleMarkers)
|
||||||
{
|
{
|
||||||
EphemeralMarkers.Add(marker);
|
EphemeralMarkers.Add(marker);
|
||||||
|
|
||||||
if (marker.Type == Marker.EType.SilverCoffer && config.ShowSilverCoffers)
|
if (marker.Type == Marker.EType.SilverCoffer && config.ShowSilverCoffers)
|
||||||
@ -341,6 +358,38 @@ namespace Pal.Client
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task FetchFloorStatistics()
|
||||||
|
{
|
||||||
|
if (Service.Configuration.Mode != Configuration.EMode.Online)
|
||||||
|
{
|
||||||
|
Service.Chat.Print($"[Palace Pal] You can view statistics for the floor you're currently on by opening the 'Debug' tab in the configuration window.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var (success, floorStatistics) = await Service.RemoteApi.FetchStatistics();
|
||||||
|
if (success)
|
||||||
|
{
|
||||||
|
var statisticsWindow = Service.WindowSystem.GetWindow<StatisticsWindow>();
|
||||||
|
statisticsWindow.SetFloorData(floorStatistics);
|
||||||
|
statisticsWindow.IsOpen = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Service.Chat.PrintError("[Palace Pal] Unable to fetch statistics.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (RpcException e) when (e.StatusCode == StatusCode.PermissionDenied)
|
||||||
|
{
|
||||||
|
Service.Chat.Print($"[Palace Pal] You can view statistics for the floor you're currently on by opening the 'Debug' tab in the configuration window.");
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Service.Chat.PrintError($"[Palace Pal] {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void HandleRemoteDownloads()
|
private void HandleRemoteDownloads()
|
||||||
{
|
{
|
||||||
while (_remoteDownloads.TryDequeue(out var download))
|
while (_remoteDownloads.TryDequeue(out var download))
|
||||||
|
@ -20,8 +20,7 @@ namespace Pal.Client
|
|||||||
private const string remoteUrl = "https://pal.μ.tv";
|
private const string remoteUrl = "https://pal.μ.tv";
|
||||||
#endif
|
#endif
|
||||||
private GrpcChannel _channel;
|
private GrpcChannel _channel;
|
||||||
private string _authToken;
|
private LoginReply _lastLoginReply;
|
||||||
private DateTime _tokenExpiresAt;
|
|
||||||
|
|
||||||
private async Task<bool> Connect(CancellationToken cancellationToken, bool retry = true)
|
private async Task<bool> Connect(CancellationToken cancellationToken, bool retry = true)
|
||||||
{
|
{
|
||||||
@ -67,18 +66,12 @@ namespace Pal.Client
|
|||||||
if (string.IsNullOrEmpty(accountId))
|
if (string.IsNullOrEmpty(accountId))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(_authToken) || _tokenExpiresAt < DateTime.Now)
|
if (_lastLoginReply == null || string.IsNullOrEmpty(_lastLoginReply.AuthToken) || _lastLoginReply.ExpiresAt.ToDateTime().ToLocalTime() < DateTime.Now)
|
||||||
{
|
{
|
||||||
var loginReply = await accountClient.LoginAsync(new LoginRequest { AccountId = accountId }, deadline: DateTime.UtcNow.AddSeconds(10), cancellationToken: cancellationToken);
|
_lastLoginReply = await accountClient.LoginAsync(new LoginRequest { AccountId = accountId }, deadline: DateTime.UtcNow.AddSeconds(10), cancellationToken: cancellationToken);
|
||||||
if (loginReply.Success)
|
if (!_lastLoginReply.Success)
|
||||||
{
|
{
|
||||||
_authToken = loginReply.AuthToken;
|
if (_lastLoginReply.Error == LoginError.InvalidAccountId)
|
||||||
_tokenExpiresAt = loginReply.ExpiresAt.ToDateTime().ToLocalTime();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_authToken = null;
|
|
||||||
if (loginReply.Error == LoginError.InvalidAccountId)
|
|
||||||
{
|
{
|
||||||
accountId = null;
|
accountId = null;
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
@ -95,7 +88,7 @@ namespace Pal.Client
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return !string.IsNullOrEmpty(_authToken);
|
return !string.IsNullOrEmpty(_lastLoginReply?.AuthToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> VerifyConnection(CancellationToken cancellationToken = default)
|
public async Task<string> VerifyConnection(CancellationToken cancellationToken = default)
|
||||||
@ -142,9 +135,19 @@ namespace Pal.Client
|
|||||||
return uploadReply.Success;
|
return uploadReply.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<(bool, List<FloorStatistics>)> FetchStatistics(CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
if (!await Connect(cancellationToken))
|
||||||
|
return new(false, new List<FloorStatistics>());
|
||||||
|
|
||||||
|
var palaceClient = new PalaceService.PalaceServiceClient(_channel);
|
||||||
|
var statisticsReply = await palaceClient.FetchStatisticsAsync(new StatisticsRequest(), headers: AuthorizedHeaders(), deadline: DateTime.UtcNow.AddSeconds(30), cancellationToken: cancellationToken);
|
||||||
|
return (statisticsReply.Success, statisticsReply.FloorStatistics.ToList());
|
||||||
|
}
|
||||||
|
|
||||||
private Metadata AuthorizedHeaders() => new Metadata
|
private Metadata AuthorizedHeaders() => new Metadata
|
||||||
{
|
{
|
||||||
{ "Authorization", $"Bearer {_authToken}" },
|
{ "Authorization", $"Bearer {_lastLoginReply?.AuthToken}" },
|
||||||
};
|
};
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
@ -4,7 +4,7 @@ using ECommons;
|
|||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
|
||||||
namespace Pal.Client
|
namespace Pal.Client.Windows
|
||||||
{
|
{
|
||||||
internal class AgreementWindow : Window
|
internal class AgreementWindow : Window
|
||||||
{
|
{
|
||||||
@ -35,10 +35,10 @@ namespace Pal.Client
|
|||||||
|
|
||||||
ImGui.TextWrapped("Ideally, we want to discover every potential trap and chest location in the game, but doing this alone is very tedious. Floor 51-60 has over 100 trap locations and over 50 coffer locations, the last of which took over 50 runs to find - and we don't know if that map is complete. Higher floors naturally see fewer runs, making solo attempts to map the place much harder.");
|
ImGui.TextWrapped("Ideally, we want to discover every potential trap and chest location in the game, but doing this alone is very tedious. Floor 51-60 has over 100 trap locations and over 50 coffer locations, the last of which took over 50 runs to find - and we don't know if that map is complete. Higher floors naturally see fewer runs, making solo attempts to map the place much harder.");
|
||||||
ImGui.TextWrapped("You can decide whether you want to share traps and chests you find with the community, which likewise also will let you see chests and coffers found by other players. This can be changed at any time. No data regarding your FFXIV character or account is ever sent to our server.");
|
ImGui.TextWrapped("You can decide whether you want to share traps and chests you find with the community, which likewise also will let you see chests and coffers found by other players. This can be changed at any time. No data regarding your FFXIV character or account is ever sent to our server.");
|
||||||
|
|
||||||
ImGui.RadioButton("Upload my discoveries, show traps & coffers other players have discovered", ref _choice, (int)Configuration.EMode.Online);
|
ImGui.RadioButton("Upload my discoveries, show traps & coffers other players have discovered", ref _choice, (int)Configuration.EMode.Online);
|
||||||
ImGui.RadioButton("Never upload discoveries, show only traps and coffers I found myself", ref _choice, (int)Configuration.EMode.Offline);
|
ImGui.RadioButton("Never upload discoveries, show only traps and coffers I found myself", ref _choice, (int)Configuration.EMode.Offline);
|
||||||
|
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
|
|
||||||
ImGui.TextColored(ImGuiColors.DalamudRed, "While this is not an automation feature, you're still very likely to break the ToS.");
|
ImGui.TextColored(ImGuiColors.DalamudRed, "While this is not an automation feature, you're still very likely to break the ToS.");
|
@ -8,7 +8,7 @@ using System.Linq;
|
|||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Pal.Client
|
namespace Pal.Client.Windows
|
||||||
{
|
{
|
||||||
internal class ConfigWindow : Window
|
internal class ConfigWindow : Window
|
||||||
{
|
{
|
||||||
@ -139,7 +139,7 @@ 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)
|
||||||
{
|
{
|
||||||
int traps = currentFloorMarkers.Count(x => x != null && x.Type == Marker.EType.Trap);
|
int traps = currentFloorMarkers.Count(x => x != null && x.Type == Marker.EType.Trap);
|
||||||
ImGui.Text($"{traps} known trap{(traps == 1 ? "" : "s")}");
|
ImGui.Text($"{traps} known trap{(traps == 1 ? "" : "s")}");
|
84
Pal.Client/Windows/StatisticsWindow.cs
Normal file
84
Pal.Client/Windows/StatisticsWindow.cs
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
using Dalamud.Interface.Windowing;
|
||||||
|
using ImGuiNET;
|
||||||
|
using Lumina.Excel.GeneratedSheets;
|
||||||
|
using Pal.Common;
|
||||||
|
using Palace;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data.Common;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Pal.Client.Windows
|
||||||
|
{
|
||||||
|
internal class StatisticsWindow : Window
|
||||||
|
{
|
||||||
|
private SortedDictionary<ETerritoryType, TerritoryStatistics> _territoryStatistics = new();
|
||||||
|
|
||||||
|
public StatisticsWindow() : base("Palace Pal - Statistics###PalacePalStats")
|
||||||
|
{
|
||||||
|
Size = new Vector2(500, 500);
|
||||||
|
SizeCondition = ImGuiCond.FirstUseEver;
|
||||||
|
|
||||||
|
foreach (ETerritoryType territory in typeof(ETerritoryType).GetEnumValues())
|
||||||
|
{
|
||||||
|
_territoryStatistics[territory] = new TerritoryStatistics { TerritoryName = territory.ToString() };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Draw()
|
||||||
|
{
|
||||||
|
if (ImGui.CollapsingHeader("Discovered Traps & Coffers per Instance", ImGuiTreeNodeFlags.DefaultOpen))
|
||||||
|
{
|
||||||
|
if (ImGui.BeginTable("TrapHoardStatistics", 3, ImGuiTableFlags.Borders))
|
||||||
|
{
|
||||||
|
ImGui.TableSetupColumn("Instance", ImGuiTableColumnFlags.WidthFixed);
|
||||||
|
ImGui.TableSetupColumn("Traps", ImGuiTableColumnFlags.WidthFixed, 100);
|
||||||
|
ImGui.TableSetupColumn("Hoard", ImGuiTableColumnFlags.WidthFixed, 100);
|
||||||
|
ImGui.TableHeadersRow();
|
||||||
|
|
||||||
|
foreach (var (territoryType, stats) in _territoryStatistics)
|
||||||
|
{
|
||||||
|
ImGui.TableNextRow();
|
||||||
|
if (ImGui.TableNextColumn())
|
||||||
|
ImGui.Text(stats.TerritoryName);
|
||||||
|
|
||||||
|
if (ImGui.TableNextColumn())
|
||||||
|
ImGui.Text(stats.TrapCount?.ToString() ?? "-");
|
||||||
|
|
||||||
|
if (ImGui.TableNextColumn())
|
||||||
|
ImGui.Text(stats.HoardCofferCount?.ToString() ?? "-");
|
||||||
|
}
|
||||||
|
ImGui.EndTable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void SetFloorData(IEnumerable<FloorStatistics> floorStatistics)
|
||||||
|
{
|
||||||
|
foreach (var territoryStatistics in _territoryStatistics.Values)
|
||||||
|
{
|
||||||
|
territoryStatistics.TrapCount = null;
|
||||||
|
territoryStatistics.HoardCofferCount = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var floor in floorStatistics)
|
||||||
|
{
|
||||||
|
if (_territoryStatistics.TryGetValue((ETerritoryType)floor.TerritoryType, out TerritoryStatistics territoryStatistics))
|
||||||
|
{
|
||||||
|
territoryStatistics.TrapCount = floor.TrapCount;
|
||||||
|
territoryStatistics.HoardCofferCount = floor.HoardCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TerritoryStatistics
|
||||||
|
{
|
||||||
|
public string TerritoryName { get; set; }
|
||||||
|
public uint? TrapCount { get; set; }
|
||||||
|
public uint? HoardCofferCount { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
37
Pal.Common/ETerritoryType.cs
Normal file
37
Pal.Common/ETerritoryType.cs
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
namespace Pal.Common
|
||||||
|
{
|
||||||
|
public enum ETerritoryType : ushort
|
||||||
|
{
|
||||||
|
Palace_1_10 = 561,
|
||||||
|
Palace_11_20,
|
||||||
|
Palace_21_30,
|
||||||
|
Palace_31_40,
|
||||||
|
Palace_41_50,
|
||||||
|
Palace_51_60 = 593,
|
||||||
|
Palace_61_70,
|
||||||
|
Palace_71_80,
|
||||||
|
Palace_81_90,
|
||||||
|
Palace_91_100,
|
||||||
|
Palace_101_110,
|
||||||
|
Palace_111_120,
|
||||||
|
Palace_121_130,
|
||||||
|
Palace_131_140,
|
||||||
|
Palace_141_150,
|
||||||
|
Palace_151_160,
|
||||||
|
Palace_161_170,
|
||||||
|
Palace_171_180,
|
||||||
|
Palace_181_190,
|
||||||
|
Palace_191_200,
|
||||||
|
|
||||||
|
HeavenOnHigh_1_10 = 770,
|
||||||
|
HeavenOnHigh_11_20,
|
||||||
|
HeavenOnHigh_21_30,
|
||||||
|
HeavenOnHigh_31_40,
|
||||||
|
HeavenOnHigh_41_50,
|
||||||
|
HeavenOnHigh_51_60,
|
||||||
|
HeavenOnHigh_61_70 = 782,
|
||||||
|
HeavenOnHigh_71_80,
|
||||||
|
HeavenOnHigh_81_90,
|
||||||
|
HeavenOnHigh_91_100
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,7 @@ package palace;
|
|||||||
service PalaceService {
|
service PalaceService {
|
||||||
rpc DownloadFloors(DownloadFloorsRequest) returns (DownloadFloorsReply);
|
rpc DownloadFloors(DownloadFloorsRequest) returns (DownloadFloorsReply);
|
||||||
rpc UploadFloors(UploadFloorsRequest) returns (UploadFloorsReply);
|
rpc UploadFloors(UploadFloorsRequest) returns (UploadFloorsReply);
|
||||||
|
rpc FetchStatistics(StatisticsRequest) returns (StatisticsReply);
|
||||||
}
|
}
|
||||||
|
|
||||||
message DownloadFloorsRequest {
|
message DownloadFloorsRequest {
|
||||||
@ -25,6 +26,20 @@ message UploadFloorsReply {
|
|||||||
bool success = 1;
|
bool success = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message StatisticsRequest {
|
||||||
|
}
|
||||||
|
|
||||||
|
message StatisticsReply {
|
||||||
|
bool success = 1;
|
||||||
|
repeated FloorStatistics floorStatistics = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message FloorStatistics {
|
||||||
|
uint32 territoryType = 1;
|
||||||
|
uint32 trapCount = 2;
|
||||||
|
uint32 hoardCount = 3;
|
||||||
|
}
|
||||||
|
|
||||||
message PalaceObject {
|
message PalaceObject {
|
||||||
ObjectType type = 1;
|
ObjectType type = 1;
|
||||||
float x = 2;
|
float x = 2;
|
||||||
|
Loading…
Reference in New Issue
Block a user