diff --git a/Influx/AllaganTools/AllaganToolsIpc.cs b/Influx/AllaganTools/AllaganToolsIpc.cs index 72dcfa9..fce55e7 100644 --- a/Influx/AllaganTools/AllaganToolsIpc.cs +++ b/Influx/AllaganTools/AllaganToolsIpc.cs @@ -89,7 +89,6 @@ internal sealed class AllaganToolsIpc : IDisposable GcSealsMaelstrom = inv.Sum(20), GcSealsTwinAdders = inv.Sum(21), GcSealsImmortalFlames = inv.Sum(22), - FcCredits = inv.Sum(80), Ventures = inv.Sum(21072), CeruleumTanks = inv.Sum(10155), RepairKits = inv.Sum(10373), diff --git a/Influx/Influx.csproj b/Influx/Influx.csproj index 12da2a4..f0ef35e 100644 --- a/Influx/Influx.csproj +++ b/Influx/Influx.csproj @@ -1,7 +1,7 @@ net7.0-windows - 0.1 + 0.2 11.0 enable true diff --git a/Influx/Influx/InfluxStatisticsClient.cs b/Influx/Influx/InfluxStatisticsClient.cs index be8151a..a4f2b36 100644 --- a/Influx/Influx/InfluxStatisticsClient.cs +++ b/Influx/Influx/InfluxStatisticsClient.cs @@ -186,12 +186,14 @@ internal sealed class InfluxStatisticsClient : IDisposable else if (character.CharacterType == CharacterType.FreeCompanyChest && validFcIds.Contains(character.CharacterId)) { + update.FcStats.TryGetValue(character.CharacterId, out FcStats? fcStats); + values.Add(PointData.Measurement("currency") .Tag("id", character.CharacterId.ToString()) .Tag("fc_name", character.Name) .Tag("type", character.CharacterType.ToString()) .Field("gil", currencies.Gil) - .Field("fccredit", currencies.FcCredits) + .Field("fccredit", fcStats?.FcCredits ?? 0) .Field("ceruleum_tanks", currencies.CeruleumTanks) .Field("repair_kits", currencies.RepairKits) .Timestamp(date, WritePrecision.S)); diff --git a/Influx/InfluxPlugin.cs b/Influx/InfluxPlugin.cs index 229d2a3..e31a072 100644 --- a/Influx/InfluxPlugin.cs +++ b/Influx/InfluxPlugin.cs @@ -1,12 +1,14 @@ using System; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Runtime.InteropServices; using System.Threading; using AutoRetainerAPI; using Dalamud.Game.Addon.Lifecycle; using Dalamud.Game.Addon.Lifecycle.AddonArgTypes; using Dalamud.Game.Command; using Dalamud.Interface.Windowing; +using Dalamud.Memory; using Dalamud.Plugin; using Dalamud.Plugin.Services; using ECommons; @@ -32,19 +34,15 @@ public class InfluxPlugin : IDalamudPlugin private readonly IClientState _clientState; private readonly ICommandManager _commandManager; private readonly IPluginLog _pluginLog; - private readonly IAddonLifecycle _addonLifecycle; - private readonly IGameGui _gameGui; private readonly AllaganToolsIpc _allaganToolsIpc; private readonly SubmarineTrackerIpc _submarineTrackerIpc; private readonly LocalStatsCalculator _localStatsCalculator; + private readonly FcStatsCalculator _fcStatsCalculator; private readonly InfluxStatisticsClient _influxStatisticsClient; private readonly WindowSystem _windowSystem; private readonly StatisticsWindow _statisticsWindow; private readonly ConfigurationWindow _configurationWindow; private readonly Timer _timer; - private readonly AutoRetainerApi _autoRetainerApi; - - private bool closeFcWindow = false; public InfluxPlugin(DalamudPluginInterface pluginInterface, IClientState clientState, IPluginLog pluginLog, ICommandManager commandManager, IChatGui chatGui, IDataManager dataManager, IFramework framework, @@ -55,12 +53,11 @@ public class InfluxPlugin : IDalamudPlugin _clientState = clientState; _commandManager = commandManager; _pluginLog = pluginLog; - _addonLifecycle = addonLifecycle; - _gameGui = gameGui; DalamudReflector dalamudReflector = new DalamudReflector(pluginInterface, framework, pluginLog); _allaganToolsIpc = new AllaganToolsIpc(pluginInterface, chatGui, dalamudReflector, framework, _pluginLog); _submarineTrackerIpc = new SubmarineTrackerIpc(dalamudReflector); _localStatsCalculator = new LocalStatsCalculator(pluginInterface, clientState, addonLifecycle, pluginLog, dataManager); + _fcStatsCalculator = new FcStatsCalculator(this, pluginInterface, clientState, addonLifecycle, gameGui, framework, pluginLog); _influxStatisticsClient = new InfluxStatisticsClient(chatGui, _configuration, dataManager, clientState); _windowSystem = new WindowSystem(typeof(InfluxPlugin).FullName); @@ -73,12 +70,6 @@ public class InfluxPlugin : IDalamudPlugin _timer = new Timer(_ => UpdateStatistics(), null, TimeSpan.Zero, TimeSpan.FromMinutes(1)); _pluginInterface.UiBuilder.Draw += _windowSystem.Draw; _pluginInterface.UiBuilder.OpenConfigUi += _configurationWindow.Toggle; - - ECommonsMain.Init(_pluginInterface, this); - _autoRetainerApi = new(); - _autoRetainerApi.OnCharacterPostprocessStep += CheckCharacterPostProcess; - _autoRetainerApi.OnCharacterReadyToPostProcess += DoCharacterPostProcess; - _addonLifecycle.RegisterListener(AddonEvent.PostSetup ,"FreeCompany", CloseFcWindow); } private Configuration LoadConfig() @@ -132,6 +123,9 @@ public class InfluxPlugin : IDalamudPlugin y.LocalContentId == x.Key.OwnerId || characters.Any(z => y.LocalContentId == z.CharacterId && z.FreeCompanyId == x.Key.CharacterId))) .ToDictionary(x => x.Key, x => x.Value), + FcStats = _fcStatsCalculator.GetAllFcStats() + .Where(x => characters.Any(y => y.FreeCompanyId == x.Key)) + .ToDictionary(x => x.Key, x => x.Value), }; _statisticsWindow.OnStatisticsUpdate(update); _influxStatisticsClient.OnStatisticsUpdate(update); @@ -142,69 +136,15 @@ public class InfluxPlugin : IDalamudPlugin } } - private unsafe void CheckCharacterPostProcess() - { - - var infoProxy = Framework.Instance()->UIModule->GetInfoModule()->GetInfoProxyById(InfoProxyId.FreeCompany); - if (infoProxy != null) - { - var fcProxy = (InfoProxyFreeCompany*)infoProxy; - if (fcProxy->ID != 0) - { - _pluginLog.Information($"Requesting post-process, FC is {fcProxy->ID}"); - _autoRetainerApi.RequestCharacterPostprocess(); - } - else - _pluginLog.Information("No FC id"); - } - else - _pluginLog.Information("No FreeCompany info proxy"); - } - - private void DoCharacterPostProcess() - { - closeFcWindow = true; - Chat.Instance.SendMessage("/freecompanycmd"); - } - - private void CloseFcWindow(AddonEvent type, AddonArgs args) - { - if (closeFcWindow) - { - Task.Run(async () => - { - // this runs every 500ms - // https://github.com/Critical-Impact/CriticalCommonLib/blob/7b3814e703dd5b2981cd4334524b4b301c23e639/Services/InventoryScanner.cs#L436 - await Task.Delay(550); - - _pluginLog.Information("Closing FC window..."); - unsafe - { - AtkUnitBase* addon = (AtkUnitBase*)_gameGui.GetAddonByName("FreeCompany"); - if (addon->IsVisible) - addon->FireCallbackInt(-1); - } - - closeFcWindow = false; - _autoRetainerApi.FinishCharacterPostProcess(); - }); - } - } - public void Dispose() { - _addonLifecycle.UnregisterListener(AddonEvent.PostSetup ,"FreeCompany", CloseFcWindow); - _autoRetainerApi.OnCharacterPostprocessStep -= CheckCharacterPostProcess; - _autoRetainerApi.OnCharacterReadyToPostProcess -= DoCharacterPostProcess; - _autoRetainerApi.Dispose(); - ECommonsMain.Dispose(); - _pluginInterface.UiBuilder.OpenConfigUi -= _configurationWindow.Toggle; _pluginInterface.UiBuilder.Draw -= _windowSystem.Draw; _timer.Dispose(); _windowSystem.RemoveAllWindows(); _commandManager.RemoveHandler("/influx"); _influxStatisticsClient.Dispose(); + _fcStatsCalculator.Dispose(); _localStatsCalculator.Dispose(); _allaganToolsIpc.Dispose(); } diff --git a/Influx/LocalStatistics/FcStats.cs b/Influx/LocalStatistics/FcStats.cs new file mode 100644 index 0000000..9f71d28 --- /dev/null +++ b/Influx/LocalStatistics/FcStats.cs @@ -0,0 +1,7 @@ +namespace Influx.LocalStatistics; + +public sealed record FcStats +{ + public ulong ContentId { get; init; } + public int FcCredits { get; init; } +} diff --git a/Influx/LocalStatistics/FcStatsCalculator.cs b/Influx/LocalStatistics/FcStatsCalculator.cs new file mode 100644 index 0000000..df5c0d6 --- /dev/null +++ b/Influx/LocalStatistics/FcStatsCalculator.cs @@ -0,0 +1,200 @@ +using System; +using System.Collections.Generic; +using System.IO; +using AutoRetainerAPI; +using Dalamud.Game.Addon.Lifecycle; +using Dalamud.Game.Addon.Lifecycle.AddonArgTypes; +using Dalamud.Plugin; +using Dalamud.Plugin.Services; +using ECommons; +using ECommons.Automation; +using FFXIVClientStructs.FFXIV.Client.System.Framework; +using FFXIVClientStructs.FFXIV.Client.UI.Info; +using FFXIVClientStructs.FFXIV.Component.GUI; +using Newtonsoft.Json; +using Task = System.Threading.Tasks.Task; +namespace Influx.LocalStatistics; + +public class FcStatsCalculator : IDisposable +{ + private readonly DalamudPluginInterface _pluginInterface; + private readonly IClientState _clientState; + private readonly IAddonLifecycle _addonLifecycle; + private readonly IGameGui _gameGui; + private readonly IFramework _framework; + private readonly IPluginLog _pluginLog; + private readonly AutoRetainerApi _autoRetainerApi; + + private readonly Dictionary _cache = new(); + + private bool closeFcWindow = false; + + public FcStatsCalculator( + IDalamudPlugin plugin, + DalamudPluginInterface pluginInterface, + IClientState clientState, + IAddonLifecycle addonLifecycle, + IGameGui gameGui, + IFramework framework, + IPluginLog pluginLog) + { + _pluginInterface = pluginInterface; + _clientState = clientState; + _addonLifecycle = addonLifecycle; + _gameGui = gameGui; + _framework = framework; + _pluginLog = pluginLog; + + ECommonsMain.Init(_pluginInterface, plugin); + _autoRetainerApi = new(); + _autoRetainerApi.OnCharacterPostprocessStep += CheckCharacterPostProcess; + _autoRetainerApi.OnCharacterReadyToPostProcess += DoCharacterPostProcess; + _addonLifecycle.RegisterListener(AddonEvent.PostReceiveEvent ,"FreeCompany", CloseFcWindow); + + foreach (var file in _pluginInterface.ConfigDirectory.GetFiles("f.*.json")) + { + try + { + var stats = JsonConvert.DeserializeObject(File.ReadAllText(file.FullName)); + if (stats == null) + continue; + + _cache[stats.ContentId] = stats; + } + catch (Exception e) + { + _pluginLog.Warning(e, $"Could not parse file {file.FullName}"); + } + } + } + + private unsafe void CheckCharacterPostProcess() + { + var infoProxy = Framework.Instance()->UIModule->GetInfoModule()->GetInfoProxyById(InfoProxyId.FreeCompany); + if (infoProxy != null) + { + var fcProxy = (InfoProxyFreeCompany*)infoProxy; + if (fcProxy->ID != 0) + { + _pluginLog.Information($"Requesting post-process, FC is {fcProxy->ID}"); + _autoRetainerApi.RequestCharacterPostprocess(); + } + else + _pluginLog.Information("No FC id"); + } + else + _pluginLog.Information("No FreeCompany info proxy"); + } + + private void DoCharacterPostProcess() + { + closeFcWindow = true; + Chat.Instance.SendMessage("/freecompanycmd"); + } + + private void CloseFcWindow(AddonEvent type, AddonArgs args) + { + _framework.RunOnTick(() => UpdateOnTick(0), TimeSpan.FromMilliseconds(100)); + } + + private void UpdateOnTick(int counter) + { + bool finalAttempt = ++counter >= 10; + if (UpdateFcCredits() || finalAttempt) + { + if (closeFcWindow) + { + unsafe + { + AtkUnitBase* addon = (AtkUnitBase*)_gameGui.GetAddonByName("FreeCompany"); + if (addon != null && addon->IsVisible) + addon->FireCallbackInt(-1); + } + + closeFcWindow = false; + _autoRetainerApi.FinishCharacterPostProcess(); + } + + return; + } + + _framework.RunOnTick(() => UpdateOnTick(counter + 1), TimeSpan.FromMilliseconds(100)); + } + + // ideally we'd hook the update to the number array, but #effort + private unsafe bool UpdateFcCredits() + { + try + { + var infoProxy = + Framework.Instance()->UIModule->GetInfoModule()->GetInfoProxyById(InfoProxyId.FreeCompany); + if (infoProxy != null) + { + var fcProxy = (InfoProxyFreeCompany*)infoProxy; + ulong localContentId = fcProxy->ID; + if (localContentId != 0) + { + var atkArrays = Framework.Instance()->GetUiModule()->GetRaptureAtkModule()->AtkModule + .AtkArrayDataHolder; + if (atkArrays.NumberArrayCount > 50) + { + var fcArrayData = atkArrays.GetNumberArrayData(50); + FcStats fcStats = new FcStats + { + ContentId = localContentId, + FcCredits = fcArrayData->IntArray[9] + }; + + _pluginLog.Verbose($"Current FC credits: {fcStats.FcCredits:N0}"); + if (fcStats.FcCredits > 0) + { + if (_cache.TryGetValue(localContentId, out var existingStats)) + { + if (existingStats != fcStats) + { + _cache[localContentId] = fcStats; + File.WriteAllText( + Path.Join(_pluginInterface.GetPluginConfigDirectory(), + $"f.{localContentId:X8}.json"), + JsonConvert.SerializeObject(fcStats)); + } + } + else + { + _cache[localContentId] = fcStats; + File.WriteAllText( + Path.Join(_pluginInterface.GetPluginConfigDirectory(), + $"f.{localContentId:X8}.json"), + JsonConvert.SerializeObject(fcStats)); + } + + return true; + } + } + + return false; + } + else + // no point updating if no fc id + return true; + } + } + catch (Exception e) + { + _pluginLog.Warning(e, "Unable to update fc credits"); + } + + return false; + } + + public IReadOnlyDictionary GetAllFcStats() => _cache.AsReadOnly(); + + public void Dispose() + { + _addonLifecycle.UnregisterListener(AddonEvent.PostReceiveEvent ,"FreeCompany", CloseFcWindow); + _autoRetainerApi.OnCharacterPostprocessStep -= CheckCharacterPostProcess; + _autoRetainerApi.OnCharacterReadyToPostProcess -= DoCharacterPostProcess; + _autoRetainerApi.Dispose(); + ECommonsMain.Dispose(); + } +} diff --git a/Influx/LocalStatistics/LocalStats.cs b/Influx/LocalStatistics/LocalStats.cs index 1d459ec..f10f183 100644 --- a/Influx/LocalStatistics/LocalStats.cs +++ b/Influx/LocalStatistics/LocalStats.cs @@ -2,7 +2,7 @@ namespace Influx.LocalStatistics; -public record LocalStats +public sealed record LocalStats { public ulong ContentId { get; init; } public byte GrandCompany { get; init; } diff --git a/Influx/LocalStatistics/LocalStatsCalculator.cs b/Influx/LocalStatistics/LocalStatsCalculator.cs index a8ce130..fe590d3 100644 --- a/Influx/LocalStatistics/LocalStatsCalculator.cs +++ b/Influx/LocalStatistics/LocalStatsCalculator.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Threading.Tasks; @@ -191,7 +192,9 @@ internal sealed class LocalStatsCalculator : IDisposable ContentId = localContentId, GrandCompany = playerState->GrandCompany, GcRank = playerState->GetGrandCompanyRank(), - SquadronUnlocked = playerState->GetGrandCompanyRank() >= 9 && (QuestManager.IsQuestComplete(67925) || QuestManager.IsQuestComplete(67926) || QuestManager.IsQuestComplete(67927)), + SquadronUnlocked = playerState->GetGrandCompanyRank() >= 9 && (QuestManager.IsQuestComplete(67925) || + QuestManager.IsQuestComplete(67926) || + QuestManager.IsQuestComplete(67927)), MaxLevel = playerState->MaxLevel, ClassJobLevels = ExtractClassJobLevels(playerState), StartingTown = playerState->StartTown, diff --git a/Influx/StatisticsUpdate.cs b/Influx/StatisticsUpdate.cs index 74e3c4c..b55f28d 100644 --- a/Influx/StatisticsUpdate.cs +++ b/Influx/StatisticsUpdate.cs @@ -10,4 +10,5 @@ internal sealed class StatisticsUpdate public required IReadOnlyDictionary Currencies { get; init; } public required Dictionary> Submarines { get; init; } public required Dictionary LocalStats { get; init; } + public required Dictionary FcStats { get; init; } }