From d8cfb75c33ca8974e4bb43615f65a769d072885a Mon Sep 17 00:00:00 2001 From: Liza Carvelli Date: Tue, 10 Oct 2023 16:27:10 +0200 Subject: [PATCH] (2.x) Pick appropriate venture for new list types/priorities --- ARControl/ARControl.csproj | 4 + ARControl/AutoRetainerControlPlugin.Sync.cs | 15 +- ARControl/AutoRetainerControlPlugin.cs | 243 ++++++++++++++++---- ARControl/Configuration.cs | 1 + ARControl/GameData/VentureResolver.cs | 10 +- 5 files changed, 226 insertions(+), 47 deletions(-) diff --git a/ARControl/ARControl.csproj b/ARControl/ARControl.csproj index a5a4607..7134b01 100644 --- a/ARControl/ARControl.csproj +++ b/ARControl/ARControl.csproj @@ -48,6 +48,10 @@ $(DalamudLibPath)Newtonsoft.Json.dll false + + $(DalamudLibPath)FFXIVClientStructs.dll + false + $(AutoRetainerLibPath)AutoRetainerAPI.dll diff --git a/ARControl/AutoRetainerControlPlugin.Sync.cs b/ARControl/AutoRetainerControlPlugin.Sync.cs index 3f1c16b..51398fc 100644 --- a/ARControl/AutoRetainerControlPlugin.Sync.cs +++ b/ARControl/AutoRetainerControlPlugin.Sync.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System.Collections.Generic; +using System.Linq; namespace ARControl; @@ -37,6 +38,7 @@ partial class AutoRetainerControlPlugin save = true; } + List seenRetainers = new(); foreach (var retainerData in offlineCharacterData.RetainerData) { var retainer = character.Retainers.SingleOrDefault(x => x.Name == retainerData.Name); @@ -52,6 +54,8 @@ partial class AutoRetainerControlPlugin character.Retainers.Add(retainer); } + seenRetainers.Add(retainer.Name); + if (retainer.DisplayOrder != retainerData.DisplayOrder) { retainer.DisplayOrder = retainerData.DisplayOrder; @@ -70,6 +74,12 @@ partial class AutoRetainerControlPlugin save = true; } + if (retainer.HasVenture != retainerData.HasVenture) + { + retainer.HasVenture = retainerData.HasVenture; + save = true; + } + if (retainer.LastVenture != retainerData.VentureID) { retainer.LastVenture = retainerData.VentureID; @@ -96,6 +106,9 @@ partial class AutoRetainerControlPlugin save = true; } } + + if (character.Retainers.RemoveAll(x => !seenRetainers.Contains(x.Name)) > 0) + save = true; } if (save) diff --git a/ARControl/AutoRetainerControlPlugin.cs b/ARControl/AutoRetainerControlPlugin.cs index 3b141df..2f1ea32 100644 --- a/ARControl/AutoRetainerControlPlugin.cs +++ b/ARControl/AutoRetainerControlPlugin.cs @@ -1,6 +1,8 @@ using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Runtime.CompilerServices; using ARControl.GameData; using ARControl.Windows; using AutoRetainerAPI; @@ -10,6 +12,7 @@ using Dalamud.Interface.Windowing; using Dalamud.Plugin; using Dalamud.Plugin.Services; using ECommons; +using FFXIVClientStructs.FFXIV.Client.Game; using ImGuiNET; namespace ARControl; @@ -17,6 +20,7 @@ namespace ARControl; [SuppressMessage("ReSharper", "UnusedType.Global")] public sealed partial class AutoRetainerControlPlugin : IDalamudPlugin { + private const int QuickVentureId = 395; private readonly WindowSystem _windowSystem = new(nameof(AutoRetainerControlPlugin)); private readonly DalamudPluginInterface _pluginInterface; @@ -70,72 +74,199 @@ public sealed partial class AutoRetainerControlPlugin : IDalamudPlugin } private void SendRetainerToVenture(string retainerName) + { + var venture = GetNextVenture(retainerName, false); + if (venture == QuickVentureId) + _autoRetainerApi.SetVenture(0); + else if (venture.HasValue) + _autoRetainerApi.SetVenture(venture.Value); + } + + private unsafe uint? GetNextVenture(string retainerName, bool dryRun) { var ch = _configuration.Characters.SingleOrDefault(x => x.LocalContentId == _clientState.LocalContentId); if (ch == null) { _pluginLog.Information("No character information found"); + return null; } - else if (ch.Type == Configuration.CharacterType.NotManaged) + + if (ch.Type == Configuration.CharacterType.NotManaged) { _pluginLog.Information("Character is not managed"); + return null; } + + var retainer = ch.Retainers.SingleOrDefault(x => x.Name == retainerName); + if (retainer == null) + { + _pluginLog.Information("No retainer information found"); + return null; + } + + if (!retainer.Managed) + { + _pluginLog.Information("Retainer is not managed"); + return null; + } + + _pluginLog.Information("Checking tasks..."); + Sync(); + var venturesInProgress = CalculateVenturesInProgress(ch); + foreach (var inpr in venturesInProgress) + { + _pluginLog.Information($"In Progress: {inpr.Key} → {inpr.Value}"); + } + + IReadOnlyList itemListIds; + if (ch.Type == Configuration.CharacterType.Standalone) + itemListIds = ch.ItemListIds; else { - var retainer = ch.Retainers.SingleOrDefault(x => x.Name == retainerName); - if (retainer == null) + var group = _configuration.CharacterGroups.SingleOrDefault(x => x.Id == ch.CharacterGroupId); + if (group == null) { - _pluginLog.Information("No retainer information found"); + _pluginLog.Error($"Unable to resolve character group {ch.CharacterGroupId}."); + return null; } - else if (!retainer.Managed) + + itemListIds = group.ItemListIds; + } + + var itemLists = itemListIds.Where(listId => listId != Guid.Empty) + .Select(listId => _configuration.ItemLists.SingleOrDefault(x => x.Id == listId)) + .Where(list => list != null) + .Cast() + .ToList(); + InventoryManager* inventoryManager = InventoryManager.Instance(); + foreach (var list in itemLists) + { + _pluginLog.Information($"Checking ventures in list '{list.Name}'"); + IReadOnlyList itemsOnList; + if (list.Type == Configuration.ListType.CollectOneTime) { - _pluginLog.Information("Retainer is not managed"); + itemsOnList = list.Items + .Select(x => new StockedItem + { + QueuedItem = x, + InventoryCount = 0, + }) + .Where(x => x.RequestedCount > 0) + .ToList() + .AsReadOnly(); } else { - _pluginLog.Information("Checking tasks..."); - Sync(); - /* FIXME - foreach (var queuedItem in _configuration.QueuedItems.Where(x => x.RemainingQuantity > 0)) - { - _pluginLog.Information($"Checking venture info for itemId {queuedItem.ItemId}"); - - var (venture, reward) = _ventureResolver.ResolveVenture(ch, retainer, queuedItem); - if (reward == null) + itemsOnList = list.Items + .Select(x => new StockedItem { - _pluginLog.Information("Retainer can't complete venture"); - } - else - { - _chatGui.Print( - $"[ARC] Sending retainer {retainerName} to collect {reward.Quantity}x {venture!.Name}."); - _pluginLog.Information( - $"Setting AR to use venture {venture.RowId}, which should retrieve {reward.Quantity}x {venture.Name}"); - _autoRetainerApi.SetVenture(venture.RowId); + QueuedItem = x, + InventoryCount = inventoryManager->GetInventoryItemCount(x.ItemId) + + (venturesInProgress.TryGetValue(x.ItemId, out int inProgress) ? inProgress : 0), + }) + .Where(x => x.InventoryCount <= x.RequestedCount) + .ToList() + .AsReadOnly(); - retainer.LastVenture = venture.RowId; - queuedItem.RemainingQuantity = - Math.Max(0, queuedItem.RemainingQuantity - reward.Quantity); - _pluginInterface.SavePluginConfig(_configuration); - return; - } - } - */ + // collect items with the least current inventory first + if (list.Priority == Configuration.ListPriority.Balanced) + itemsOnList = itemsOnList.OrderBy(x => x.InventoryCount).ToList().AsReadOnly(); + } - // fallback: managed but no venture found - if (retainer.LastVenture != 395) + _pluginLog.Information($"Found {itemsOnList.Count} items on current list"); + if (itemsOnList.Count == 0) + continue; + + foreach (var itemOnList in itemsOnList) + { + _pluginLog.Information($"Checking venture info for itemId {itemOnList.ItemId}"); + + var (venture, reward) = _ventureResolver.ResolveVenture(ch, retainer, itemOnList.ItemId); + if (venture == null || reward == null) { - _chatGui.Print($"[ARC] No tasks left for retainer {retainerName}, sending to Quick Venture."); - _pluginLog.Information($"No tasks left (previous venture = {retainer.LastVenture}), using QC"); - _autoRetainerApi.SetVenture(395); - - retainer.LastVenture = 395; - _pluginInterface.SavePluginConfig(_configuration); + _pluginLog.Information($"Retainer can't complete venture '{venture?.Name}'"); } else - _pluginLog.Information("Not changing venture plan, already 395"); + { + _chatGui.Print( + $"[ARC] Sending retainer {retainerName} to collect {reward.Quantity}x {venture.Name}."); + _pluginLog.Information( + $"Setting AR to use venture {venture.RowId}, which should retrieve {reward.Quantity}x {venture.Name}"); + + if (!dryRun) + { + retainer.HasVenture = true; + retainer.LastVenture = venture.RowId; + + if (list.Type == Configuration.ListType.CollectOneTime) + { + itemOnList.RequestedCount = + Math.Max(0, itemOnList.RequestedCount - reward.Quantity); + } + + _pluginInterface.SavePluginConfig(_configuration); + } + + return venture.RowId; + } + } + + } + + // fallback: managed but no venture found + if (retainer.LastVenture != QuickVentureId) + { + _chatGui.Print($"[ARC] No tasks left for retainer {retainerName}, sending to Quick Venture."); + _pluginLog.Information($"No tasks left (previous venture = {retainer.LastVenture}), using QC"); + + if (!dryRun) + { + retainer.HasVenture = true; + retainer.LastVenture = QuickVentureId; + _pluginInterface.SavePluginConfig(_configuration); + } + + return QuickVentureId; + } + else + { + _pluginLog.Information("Not changing venture, already a quick venture"); + return null; + } + } + + /// + /// This treats the retainer who is currently doing the venture as 'in-progress', since I believe the + /// relevant event is fired BEFORE the venture rewards are collected. + /// + private Dictionary CalculateVenturesInProgress(Configuration.CharacterConfiguration character) + { + Dictionary inProgress = new Dictionary(); + foreach (var retainer in character.Retainers) + { + if (retainer.Managed && retainer.HasVenture && retainer.LastVenture != 0) + { + uint ventureId = retainer.LastVenture; + if (ventureId == 0) + continue; + + var ventureForId = _gameCache.Ventures.SingleOrDefault(x => x.RowId == ventureId); + if (ventureForId == null) + continue; + + uint itemId = ventureForId.ItemId; + var (venture, reward) = _ventureResolver.ResolveVenture(character, retainer, itemId); + if (venture == null || reward == null) + continue; + + if (inProgress.TryGetValue(itemId, out int existingQuantity)) + inProgress[itemId] = reward.Quantity + existingQuantity; + else + inProgress[itemId] = reward.Quantity; } } + + return inProgress; } private void RetainerTaskButtonDraw(ulong characterId, string retainerName) @@ -174,6 +305,24 @@ public sealed partial class AutoRetainerControlPlugin : IDalamudPlugin { if (arguments == "sync") Sync(); + else if (arguments == "d") + { + var ch = _configuration.Characters.SingleOrDefault(x => x.LocalContentId == _clientState.LocalContentId); + if (ch == null || ch.Type == Configuration.CharacterType.NotManaged || ch.Retainers.Count == 0) + { + _chatGui.PrintError("No character to debug."); + return; + } + + string retainerName = ch.Retainers.OrderBy(x => x.DisplayOrder).First().Name; + var venture = GetNextVenture(retainerName, true); + if (venture == QuickVentureId) + _chatGui.Print($"Next venture for {retainerName} is Quick Venture."); + else if (venture.HasValue) + _chatGui.Print($"Next venture for {retainerName} is {_gameCache.Ventures.First(x => x.RowId == venture.Value).Name}."); + else + _chatGui.Print($"Next venture for {retainerName} is (none)."); + } else _configWindow.Toggle(); } @@ -191,4 +340,16 @@ public sealed partial class AutoRetainerControlPlugin : IDalamudPlugin _autoRetainerApi.Dispose(); ECommonsMain.Dispose(); } + + private sealed class StockedItem + { + public required Configuration.QueuedItem QueuedItem { get; set; } + public required int InventoryCount { get; set; } + public uint ItemId => QueuedItem.ItemId; + public int RequestedCount + { + get => QueuedItem.RemainingQuantity; + set => QueuedItem.RemainingQuantity = value; + } + } } diff --git a/ARControl/Configuration.cs b/ARControl/Configuration.cs index 9105f7b..b97fe9e 100644 --- a/ARControl/Configuration.cs +++ b/ARControl/Configuration.cs @@ -97,6 +97,7 @@ internal sealed class Configuration : IPluginConfiguration public int DisplayOrder { get; set; } public int Level { get; set; } public uint Job { get; set; } + public bool HasVenture { get; set; } public uint LastVenture { get; set; } public int ItemLevel { get; set; } public int Gathering { get; set; } diff --git a/ARControl/GameData/VentureResolver.cs b/ARControl/GameData/VentureResolver.cs index b7fa025..5b6802a 100644 --- a/ARControl/GameData/VentureResolver.cs +++ b/ARControl/GameData/VentureResolver.cs @@ -15,18 +15,18 @@ internal sealed class VentureResolver } public (Venture?, VentureReward?) ResolveVenture(Configuration.CharacterConfiguration character, - Configuration.RetainerConfiguration retainer, Configuration.QueuedItem queuedItem) + Configuration.RetainerConfiguration retainer, uint itemId) { var venture = _gameCache.Ventures .Where(x => retainer.Level >= x.Level) - .FirstOrDefault(x => x.ItemId == queuedItem.ItemId && x.MatchesJob(retainer.Job)); + .FirstOrDefault(x => x.ItemId == itemId && x.MatchesJob(retainer.Job)); if (venture == null) { - _pluginLog.Information($"No applicable venture found for itemId {queuedItem.ItemId}"); + _pluginLog.Information($"No applicable venture found for itemId {itemId}"); return (null, null); } - var itemToGather = _gameCache.ItemsToGather.FirstOrDefault(x => x.ItemId == queuedItem.ItemId); + var itemToGather = _gameCache.ItemsToGather.FirstOrDefault(x => x.ItemId == itemId); if (itemToGather != null && !character.GatheredItems.Contains(itemToGather.GatheredItemId)) { _pluginLog.Information($"Character hasn't gathered {venture.Name} yet"); @@ -34,7 +34,7 @@ internal sealed class VentureResolver } _pluginLog.Information( - $"Found venture {venture.Name}, row = {venture.RowId}, checking if it is suitable"); + $"Found venture {venture.Name}, row = {venture.RowId}, checking if we have high enough stats"); VentureReward? reward = null; if (venture.CategoryName is "MIN" or "BTN") {