Statistics view

This commit is contained in:
Liza 2022-10-26 20:43:24 +02:00
parent c5d3c90b85
commit 4bd37bdefd
8 changed files with 213 additions and 24 deletions

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.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>

View File

@ -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;
} }
switch (arguments)
{
case "stats":
Task.Run(async () => await FetchFloorStatistics());
break;
default:
Service.WindowSystem.GetWindow<ConfigWindow>()?.Toggle(); Service.WindowSystem.GetWindow<ConfigWindow>()?.Toggle();
break;
}
} }
#region IDisposable Support #region IDisposable Support
@ -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))

View File

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

View File

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

View File

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

View 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; }
}
}
}

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

View File

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