forked from liza/Deliveroo
Option to buy multiple items
This commit is contained in:
parent
db93d36c06
commit
601c928f18
@ -8,10 +8,17 @@ internal sealed class Configuration : IPluginConfiguration
|
||||
public int Version { get; set; } = 1;
|
||||
|
||||
public List<uint> ItemsAvailableForPurchase { get; set; } = new();
|
||||
public uint SelectedPurchaseItemId { get; set; } = 0;
|
||||
public List<PurchasePriority> ItemsToPurchase { get; set; } = new();
|
||||
|
||||
public int ReservedSealCount { get; set; } = 0;
|
||||
public ItemFilterType ItemFilter { get; set; } = ItemFilterType.HideGearSetItems;
|
||||
public bool IgnoreCertainLimitations { get; set; } = false;
|
||||
|
||||
internal sealed class PurchasePriority
|
||||
{
|
||||
public uint ItemId { get; set; }
|
||||
public int Limit { get; set; }
|
||||
}
|
||||
|
||||
public enum ItemFilterType
|
||||
{
|
||||
|
@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Logging;
|
||||
using Deliveroo.GameData;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType;
|
||||
|
||||
@ -23,16 +24,38 @@ partial class DeliverooPlugin
|
||||
CurrentStage = Stage.SelectRewardTier;
|
||||
}
|
||||
|
||||
private PurchaseItemRequest? GetNextItemToPurchase(PurchaseItemRequest? previousRequest = null)
|
||||
{
|
||||
foreach (PurchaseItemRequest request in _itemsToPurchaseNow)
|
||||
{
|
||||
int offset = 0;
|
||||
if (request == previousRequest)
|
||||
offset = (int)request.StackSize;
|
||||
|
||||
if (GetItemCount(request.ItemId) + offset < request.EffectiveLimit)
|
||||
return request;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private unsafe void SelectRewardTier()
|
||||
{
|
||||
PurchaseItemRequest? item = GetNextItemToPurchase();
|
||||
if (item == null)
|
||||
{
|
||||
CurrentStage = Stage.CloseGcExchange;
|
||||
return;
|
||||
}
|
||||
|
||||
if (TryGetAddonByName<AtkUnitBase>("GrandCompanyExchange", out var addonExchange) &&
|
||||
IsAddonReady(addonExchange))
|
||||
{
|
||||
PluginLog.Information($"Selecting tier 1, {(int)_selectedRewardItem.Tier - 1}");
|
||||
PluginLog.Information($"Selecting tier 1, {(int)item.Tier - 1}");
|
||||
var selectRank = stackalloc AtkValue[]
|
||||
{
|
||||
new() { Type = ValueType.Int, Int = 1 },
|
||||
new() { Type = ValueType.Int, Int = (int)_selectedRewardItem.Tier - 1 },
|
||||
new() { Type = ValueType.Int, Int = (int)item.Tier - 1 },
|
||||
new() { Type = 0, Int = 0 },
|
||||
new() { Type = 0, Int = 0 },
|
||||
new() { Type = 0, Int = 0 },
|
||||
@ -49,14 +72,21 @@ partial class DeliverooPlugin
|
||||
|
||||
private unsafe void SelectRewardSubCategory()
|
||||
{
|
||||
PurchaseItemRequest? item = GetNextItemToPurchase();
|
||||
if (item == null)
|
||||
{
|
||||
CurrentStage = Stage.CloseGcExchange;
|
||||
return;
|
||||
}
|
||||
|
||||
if (TryGetAddonByName<AtkUnitBase>("GrandCompanyExchange", out var addonExchange) &&
|
||||
IsAddonReady(addonExchange))
|
||||
{
|
||||
PluginLog.Information($"Selecting subcategory 2, {(int)_selectedRewardItem.SubCategory}");
|
||||
PluginLog.Information($"Selecting subcategory 2, {(int)item.SubCategory}");
|
||||
var selectType = stackalloc AtkValue[]
|
||||
{
|
||||
new() { Type = ValueType.Int, Int = 2 },
|
||||
new() { Type = ValueType.Int, Int = (int)_selectedRewardItem.SubCategory },
|
||||
new() { Type = ValueType.Int, Int = (int)item.SubCategory },
|
||||
new() { Type = 0, Int = 0 },
|
||||
new() { Type = 0, Int = 0 },
|
||||
new() { Type = 0, Int = 0 },
|
||||
@ -78,23 +108,79 @@ partial class DeliverooPlugin
|
||||
{
|
||||
if (SelectRewardItem(addonExchange))
|
||||
{
|
||||
_continueAt = DateTime.Now.AddSeconds(0.5);
|
||||
_continueAt = DateTime.Now.AddSeconds(0.2);
|
||||
CurrentStage = Stage.ConfirmReward;
|
||||
}
|
||||
else
|
||||
{
|
||||
PluginLog.Warning("Could not find selected reward item");
|
||||
_continueAt = DateTime.Now.AddSeconds(0.5);
|
||||
_continueAt = DateTime.Now.AddSeconds(0.2);
|
||||
CurrentStage = Stage.CloseGcExchange;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe bool SelectRewardItem(AtkUnitBase* addonExchange)
|
||||
{
|
||||
PurchaseItemRequest? item = GetNextItemToPurchase();
|
||||
if (item == null)
|
||||
return false;
|
||||
|
||||
uint itemsOnCurrentPage = addonExchange->AtkValues[1].UInt;
|
||||
for (uint i = 0; i < itemsOnCurrentPage; ++i)
|
||||
{
|
||||
uint itemId = addonExchange->AtkValues[317 + i].UInt;
|
||||
if (itemId == item.ItemId)
|
||||
{
|
||||
PluginLog.Information($"Selecting item {itemId}, {i}");
|
||||
long toBuy = (GetCurrentSealCount() - _configuration.ReservedSealCount) / item.SealCost;
|
||||
toBuy = Math.Min(toBuy, item.EffectiveLimit - GetItemCount(item.ItemId));
|
||||
|
||||
if (item.ItemId != ItemIds.Venture && !_configuration.IgnoreCertainLimitations)
|
||||
toBuy = Math.Min(toBuy, 99);
|
||||
|
||||
if (toBuy <= 0)
|
||||
{
|
||||
PluginLog.Information($"Items to buy = {toBuy}");
|
||||
return false;
|
||||
}
|
||||
|
||||
_chatGui.Print($"Buying {toBuy}x {item.Name}...");
|
||||
var selectReward = stackalloc AtkValue[]
|
||||
{
|
||||
new() { Type = ValueType.Int, Int = 0 },
|
||||
new() { Type = ValueType.Int, Int = (int)i },
|
||||
new() { Type = ValueType.Int, Int = (int)toBuy },
|
||||
new() { Type = 0, Int = 0 },
|
||||
new() { Type = ValueType.Bool, Byte = 1 },
|
||||
new() { Type = ValueType.Bool, Byte = 0 },
|
||||
new() { Type = 0, Int = 0 },
|
||||
new() { Type = 0, Int = 0 },
|
||||
new() { Type = 0, Int = 0 }
|
||||
};
|
||||
addonExchange->FireCallback(9, selectReward);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
PluginLog.Warning("Could not find selected reward item");
|
||||
return false;
|
||||
}
|
||||
|
||||
private void ConfirmReward()
|
||||
{
|
||||
if (SelectSelectYesno(0, s => s.StartsWith("Exchange ")))
|
||||
PurchaseItemRequest? item = GetNextItemToPurchase();
|
||||
if (item == null)
|
||||
{
|
||||
CurrentStage = Stage.CloseGcExchange;
|
||||
return;
|
||||
}
|
||||
|
||||
if (SelectSelectYesno(0, s => s.StartsWith("Exchange ")))
|
||||
{
|
||||
if (GetNextItemToPurchase(item) != null)
|
||||
CurrentStage = Stage.SelectRewardTier;
|
||||
else
|
||||
CurrentStage = Stage.CloseGcExchange;
|
||||
_continueAt = DateTime.Now.AddSeconds(0.5);
|
||||
}
|
||||
}
|
||||
|
228
Deliveroo/DeliverooPlugin.GameFunctions.cs
Normal file
228
Deliveroo/DeliverooPlugin.GameFunctions.cs
Normal file
@ -0,0 +1,228 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using Dalamud.Game.ClientState.Objects.Enums;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Logging;
|
||||
using Dalamud.Memory;
|
||||
using Deliveroo.GameData;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Control;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||
using FFXIVClientStructs.FFXIV.Common.Math;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
|
||||
namespace Deliveroo;
|
||||
|
||||
partial class DeliverooPlugin
|
||||
{
|
||||
private unsafe void InteractWithTarget(GameObject obj)
|
||||
{
|
||||
PluginLog.Information($"Setting target to {obj}");
|
||||
if (_targetManager.Target == null || _targetManager.Target != obj)
|
||||
{
|
||||
_targetManager.Target = obj;
|
||||
}
|
||||
|
||||
TargetSystem.Instance()->InteractWithObject(
|
||||
(FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)obj.Address, false);
|
||||
}
|
||||
|
||||
private unsafe int GetCurrentSealCount()
|
||||
{
|
||||
InventoryManager* inventoryManager = InventoryManager.Instance();
|
||||
switch ((GrandCompany)PlayerState.Instance()->GrandCompany)
|
||||
{
|
||||
case GrandCompany.Maelstrom:
|
||||
return inventoryManager->GetInventoryItemCount(20, false, false, false);
|
||||
case GrandCompany.TwinAdder:
|
||||
return inventoryManager->GetInventoryItemCount(21, false, false, false);
|
||||
case GrandCompany.ImmortalFlames:
|
||||
return inventoryManager->GetInventoryItemCount(22, false, false, false);
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
internal unsafe GrandCompany GetGrandCompany() => (GrandCompany)PlayerState.Instance()->GrandCompany;
|
||||
|
||||
internal unsafe byte GetGrandCompanyRank() => PlayerState.Instance()->GetGrandCompanyRank();
|
||||
|
||||
private float GetDistanceToNpc(int npcId, out GameObject? o)
|
||||
{
|
||||
foreach (var obj in _objectTable)
|
||||
{
|
||||
if (obj.ObjectKind == ObjectKind.EventNpc && obj is Character c)
|
||||
{
|
||||
if (GetNpcId(obj) == npcId)
|
||||
{
|
||||
o = obj;
|
||||
return Vector3.Distance(_clientState.LocalPlayer!.Position, c.Position);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
o = null;
|
||||
return float.MaxValue;
|
||||
}
|
||||
|
||||
private int GetNpcId(GameObject obj)
|
||||
{
|
||||
return Marshal.ReadInt32(obj.Address + 128);
|
||||
}
|
||||
|
||||
private int GetPersonnelOfficerId()
|
||||
{
|
||||
return GetGrandCompany() switch
|
||||
{
|
||||
GrandCompany.Maelstrom => 0xF4B94,
|
||||
GrandCompany.ImmortalFlames => 0xF4B97,
|
||||
GrandCompany.TwinAdder => 0xF4B9A,
|
||||
_ => int.MaxValue,
|
||||
};
|
||||
}
|
||||
|
||||
private int GetQuartermasterId()
|
||||
{
|
||||
return GetGrandCompany() switch
|
||||
{
|
||||
GrandCompany.Maelstrom => 0xF4B93,
|
||||
GrandCompany.ImmortalFlames => 0xF4B96,
|
||||
GrandCompany.TwinAdder => 0xF4B99,
|
||||
_ => int.MaxValue,
|
||||
};
|
||||
}
|
||||
|
||||
private uint GetSealCap() => _sealCaps.TryGetValue(GetGrandCompanyRank(), out var cap) ? cap : 0;
|
||||
|
||||
public unsafe int GetItemCount(uint itemId)
|
||||
{
|
||||
InventoryManager* inventoryManager = InventoryManager.Instance();
|
||||
return inventoryManager->GetInventoryItemCount(itemId, false, false, false);
|
||||
}
|
||||
|
||||
private decimal GetSealMultiplier()
|
||||
{
|
||||
// priority seal allowance
|
||||
if (_clientState.LocalPlayer!.StatusList.Any(x => x.StatusId == 1078))
|
||||
return 1.15m;
|
||||
|
||||
// seal sweetener 1/2
|
||||
var fcStatus = _clientState.LocalPlayer!.StatusList.FirstOrDefault(x => x.StatusId == 414);
|
||||
if (fcStatus != null)
|
||||
{
|
||||
return 1m + fcStatus.StackCount / 100m;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
private unsafe List<TurnInItem> BuildTurnInList(AgentGrandCompanySupply* agent)
|
||||
{
|
||||
List<TurnInItem> list = new();
|
||||
for (int i = 11 /* skip over provisioning items */; i < agent->NumItems; ++i)
|
||||
{
|
||||
GrandCompanyItem item = agent->ItemArray[i];
|
||||
|
||||
// this includes all items, even if they don't match the filter
|
||||
list.Add(new TurnInItem
|
||||
{
|
||||
ItemId = Marshal.ReadInt32(new nint(&item) + 132),
|
||||
Name = MemoryHelper.ReadSeString(&item.ItemName).ToString(),
|
||||
SealsWithBonus = (int)Math.Round(item.SealReward * GetSealMultiplier(), MidpointRounding.AwayFromZero),
|
||||
SealsWithoutBonus = item.SealReward,
|
||||
ItemUiCategory = Marshal.ReadByte(new nint(&item) + 150),
|
||||
});
|
||||
|
||||
// GrandCompanyItem + 104 = [int] InventoryType
|
||||
// GrandCompanyItem + 108 = [int] ??
|
||||
// GrandCompanyItem + 124 = [int] <Item's Column 19 in the sheet, but that has no name>
|
||||
// GrandCompanyItem + 132 = [int] itemId
|
||||
// GrandCompanyItem + 136 = [int] 0 (always)?
|
||||
// GrandCompanyItem + 140 = [int] i (item's own position within the unsorted list)
|
||||
// GrandCompanyItem + 148 = [short] ilvl
|
||||
// GrandCompanyItem + 150 = [byte] ItemUICategory
|
||||
// GrandCompanyItem + 151 = [byte] (unchecked) inventory slot in container
|
||||
// GrandCompanyItem + 152 = [short] 512 (always)?
|
||||
// int itemId = Marshal.ReadInt32(new nint(&item) + 132);
|
||||
// PluginLog.Verbose($" {Marshal.ReadInt32(new nint(&item) + 132)};;;; {MemoryHelper.ReadSeString(&item.ItemName)}, {new nint(&agent->ItemArray[i]):X8}, {item.SealReward}, {item.IsTurnInAvailable}");
|
||||
}
|
||||
|
||||
return list.OrderByDescending(x => x.SealsWithBonus)
|
||||
.ThenBy(x => x.ItemUiCategory)
|
||||
.ThenBy(x => x.ItemId)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private unsafe AtkUnitBase* GetAddonById(uint id)
|
||||
{
|
||||
var unitManagers = &AtkStage.GetSingleton()->RaptureAtkUnitManager->AtkUnitManager.DepthLayerOneList;
|
||||
for (var i = 0; i < 18; i++)
|
||||
{
|
||||
var unitManager = &unitManagers[i];
|
||||
var unitBaseArray = &(unitManager->AtkUnitEntries);
|
||||
for (var j = 0; j < unitManager->Count; j++)
|
||||
{
|
||||
var unitBase = unitBaseArray[j];
|
||||
if (unitBase->ID == id)
|
||||
{
|
||||
return unitBase;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private unsafe bool TryGetAddonByName<T>(string addonName, out T* addonPtr)
|
||||
where T : unmanaged
|
||||
{
|
||||
var a = _gameGui.GetAddonByName(addonName);
|
||||
if (a != IntPtr.Zero)
|
||||
{
|
||||
addonPtr = (T*)a;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
addonPtr = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe bool IsAddonReady(AtkUnitBase* addon)
|
||||
{
|
||||
return addon->IsVisible && addon->UldManager.LoadedState == AtkLoadState.Loaded;
|
||||
}
|
||||
|
||||
private unsafe bool SelectSelectString(int choice)
|
||||
{
|
||||
if (TryGetAddonByName<AddonSelectString>("SelectString", out var addonSelectString) &&
|
||||
IsAddonReady(&addonSelectString->AtkUnitBase))
|
||||
{
|
||||
addonSelectString->AtkUnitBase.FireCallbackInt(choice);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private unsafe bool SelectSelectYesno(int choice, Predicate<string> predicate)
|
||||
{
|
||||
if (TryGetAddonByName<AddonSelectYesno>("SelectYesno", out var addonSelectYesno) &&
|
||||
IsAddonReady(&addonSelectYesno->AtkUnitBase) &&
|
||||
predicate(MemoryHelper.ReadSeString(&addonSelectYesno->PromptText->NodeText).ToString()))
|
||||
{
|
||||
PluginLog.Information(
|
||||
$"Selecting choice={choice} for '{MemoryHelper.ReadSeString(&addonSelectYesno->PromptText->NodeText)}'");
|
||||
|
||||
addonSelectYesno->AtkUnitBase.FireCallbackInt(choice);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Logging;
|
||||
using Dalamud.Memory;
|
||||
using Deliveroo.GameData;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||
@ -92,6 +92,7 @@ partial class DeliverooPlugin
|
||||
|
||||
var agent = (AgentGrandCompanySupply*)agentInterface;
|
||||
List<TurnInItem> items = BuildTurnInList(agent);
|
||||
_turnInWindow.EstimatedGcSeals = GetCurrentSealCount() + items.Sum(x => x.SealsWithBonus);
|
||||
if (items.Count == 0 || addon->UldManager.NodeList[20]->IsVisible)
|
||||
{
|
||||
CurrentStage = Stage.CloseGcSupplyThenStop;
|
||||
@ -151,7 +152,7 @@ partial class DeliverooPlugin
|
||||
{
|
||||
if (SelectSelectString(3))
|
||||
{
|
||||
if (!_selectedRewardItem.IsValid())
|
||||
if (GetNextItemToPurchase() == null)
|
||||
{
|
||||
_turnInWindow.State = false;
|
||||
CurrentStage = Stage.RequestStop;
|
||||
@ -169,13 +170,13 @@ partial class DeliverooPlugin
|
||||
{
|
||||
if (SelectSelectString(3))
|
||||
{
|
||||
if (!_selectedRewardItem.IsValid())
|
||||
if (GetNextItemToPurchase() == null)
|
||||
{
|
||||
_turnInWindow.State = false;
|
||||
CurrentStage = Stage.RequestStop;
|
||||
}
|
||||
else if (GetCurrentSealCount() <=
|
||||
_configuration.ReservedSealCount + _selectedRewardItem.SealCost)
|
||||
_configuration.ReservedSealCount + GetNextItemToPurchase()!.SealCost)
|
||||
{
|
||||
_turnInWindow.State = false;
|
||||
CurrentStage = Stage.RequestStop;
|
||||
|
@ -57,7 +57,7 @@ public sealed partial class DeliverooPlugin : IDalamudPlugin
|
||||
|
||||
private Stage _currentStageInternal = Stage.Stopped;
|
||||
private DateTime _continueAt = DateTime.MinValue;
|
||||
private GcRewardItem _selectedRewardItem = GcRewardItem.None;
|
||||
private List<PurchaseItemRequest> _itemsToPurchaseNow = new();
|
||||
private (bool Saved, bool? PreviousState) _yesAlreadyState = (false, null);
|
||||
|
||||
public DeliverooPlugin(DalamudPluginInterface pluginInterface, ChatGui chatGui, GameGui gameGui,
|
||||
@ -137,11 +137,19 @@ public sealed partial class DeliverooPlugin : IDalamudPlugin
|
||||
else if (_turnInWindow.State && CurrentStage == Stage.Stopped)
|
||||
{
|
||||
CurrentStage = Stage.TargetPersonnelOfficer;
|
||||
_selectedRewardItem = _turnInWindow.SelectedItem;
|
||||
if (_selectedRewardItem.IsValid() && _selectedRewardItem.RequiredRank > GetGrandCompanyRank())
|
||||
_selectedRewardItem = GcRewardItem.None;
|
||||
_itemsToPurchaseNow = _turnInWindow.SelectedItems;
|
||||
if (_itemsToPurchaseNow.Count > 0)
|
||||
{
|
||||
PluginLog.Information("Items to purchase:");
|
||||
foreach (var item in _itemsToPurchaseNow)
|
||||
PluginLog.Information($" {item.Name} (limit = {item.EffectiveLimit})");
|
||||
}
|
||||
else
|
||||
PluginLog.Information("No items to purchase configured or available");
|
||||
|
||||
if (_selectedRewardItem.IsValid() && GetCurrentSealCount() > GetSealCap() / 2)
|
||||
|
||||
var nextItem = GetNextItemToPurchase();
|
||||
if (nextItem != null && GetCurrentSealCount() >= _configuration.ReservedSealCount + nextItem.SealCost)
|
||||
CurrentStage = Stage.TargetQuartermaster;
|
||||
|
||||
if (TryGetAddonByName<AddonGrandCompanySupplyList>("GrandCompanySupplyList", out var gcSupplyList) &&
|
||||
@ -150,7 +158,7 @@ public sealed partial class DeliverooPlugin : IDalamudPlugin
|
||||
|
||||
if (TryGetAddonByName<AtkUnitBase>("GrandCompanyExchange", out var gcExchange) &&
|
||||
IsAddonReady(gcExchange))
|
||||
CurrentStage = Stage.CloseGcExchange;
|
||||
CurrentStage = Stage.SelectRewardTier;
|
||||
}
|
||||
|
||||
if (CurrentStage != Stage.Stopped && CurrentStage != Stage.RequestStop && !_yesAlreadyState.Saved)
|
||||
@ -228,28 +236,6 @@ public sealed partial class DeliverooPlugin : IDalamudPlugin
|
||||
}
|
||||
}
|
||||
|
||||
private float GetDistanceToNpc(int npcId, out GameObject? o)
|
||||
{
|
||||
foreach (var obj in _objectTable)
|
||||
{
|
||||
if (obj.ObjectKind == ObjectKind.EventNpc && obj is Character c)
|
||||
{
|
||||
if (GetNpcId(obj) == npcId)
|
||||
{
|
||||
o = obj;
|
||||
return Vector3.Distance(_clientState.LocalPlayer!.Position, c.Position);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
o = null;
|
||||
return float.MaxValue;
|
||||
}
|
||||
|
||||
private int GetNpcId(GameObject obj)
|
||||
{
|
||||
return Marshal.ReadInt32(obj.Address + 128);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
@ -260,232 +246,6 @@ public sealed partial class DeliverooPlugin : IDalamudPlugin
|
||||
RestoreYesAlready();
|
||||
}
|
||||
|
||||
private unsafe List<TurnInItem> BuildTurnInList(AgentGrandCompanySupply* agent)
|
||||
{
|
||||
List<TurnInItem> list = new();
|
||||
for (int i = 11 /* skip over provisioning items */; i < agent->NumItems; ++i)
|
||||
{
|
||||
GrandCompanyItem item = agent->ItemArray[i];
|
||||
|
||||
// this includes all items, even if they don't match the filter
|
||||
list.Add(new TurnInItem
|
||||
{
|
||||
ItemId = Marshal.ReadInt32(new nint(&item) + 132),
|
||||
Name = MemoryHelper.ReadSeString(&item.ItemName).ToString(),
|
||||
SealsWithBonus = (int)Math.Round(item.SealReward * GetSealMultiplier(), MidpointRounding.AwayFromZero),
|
||||
SealsWithoutBonus = item.SealReward,
|
||||
ItemUiCategory = Marshal.ReadByte(new nint(&item) + 150),
|
||||
});
|
||||
|
||||
// GrandCompanyItem + 104 = [int] InventoryType
|
||||
// GrandCompanyItem + 108 = [int] ??
|
||||
// GrandCompanyItem + 124 = [int] <Item's Column 19 in the sheet, but that has no name>
|
||||
// GrandCompanyItem + 132 = [int] itemId
|
||||
// GrandCompanyItem + 136 = [int] 0 (always)?
|
||||
// GrandCompanyItem + 140 = [int] i (item's own position within the unsorted list)
|
||||
// GrandCompanyItem + 148 = [short] ilvl
|
||||
// GrandCompanyItem + 150 = [byte] ItemUICategory
|
||||
// GrandCompanyItem + 151 = [byte] (unchecked) inventory slot in container
|
||||
// GrandCompanyItem + 152 = [short] 512 (always)?
|
||||
// int itemId = Marshal.ReadInt32(new nint(&item) + 132);
|
||||
// PluginLog.Verbose($" {Marshal.ReadInt32(new nint(&item) + 132)};;;; {MemoryHelper.ReadSeString(&item.ItemName)}, {new nint(&agent->ItemArray[i]):X8}, {item.SealReward}, {item.IsTurnInAvailable}");
|
||||
}
|
||||
|
||||
return list.OrderByDescending(x => x.SealsWithBonus)
|
||||
.ThenBy(x => x.ItemUiCategory)
|
||||
.ThenBy(x => x.ItemId)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private unsafe AtkUnitBase* GetAddonById(uint id)
|
||||
{
|
||||
var unitManagers = &AtkStage.GetSingleton()->RaptureAtkUnitManager->AtkUnitManager.DepthLayerOneList;
|
||||
for (var i = 0; i < 18; i++)
|
||||
{
|
||||
var unitManager = &unitManagers[i];
|
||||
var unitBaseArray = &(unitManager->AtkUnitEntries);
|
||||
for (var j = 0; j < unitManager->Count; j++)
|
||||
{
|
||||
var unitBase = unitBaseArray[j];
|
||||
if (unitBase->ID == id)
|
||||
{
|
||||
return unitBase;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private unsafe bool TryGetAddonByName<T>(string addonName, out T* addonPtr)
|
||||
where T : unmanaged
|
||||
{
|
||||
var a = _gameGui.GetAddonByName(addonName);
|
||||
if (a != IntPtr.Zero)
|
||||
{
|
||||
addonPtr = (T*)a;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
addonPtr = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe bool IsAddonReady(AtkUnitBase* addon)
|
||||
{
|
||||
return addon->IsVisible && addon->UldManager.LoadedState == AtkLoadState.Loaded;
|
||||
}
|
||||
|
||||
private unsafe bool SelectRewardItem(AtkUnitBase* addonExchange)
|
||||
{
|
||||
uint itemsOnCurrentPage = addonExchange->AtkValues[1].UInt;
|
||||
for (uint i = 0; i < itemsOnCurrentPage; ++i)
|
||||
{
|
||||
uint itemId = addonExchange->AtkValues[317 + i].UInt;
|
||||
if (itemId == _selectedRewardItem.ItemId)
|
||||
{
|
||||
long toBuy = (GetCurrentSealCount() - _configuration.ReservedSealCount) / _selectedRewardItem.SealCost;
|
||||
bool isVenture = _selectedRewardItem.ItemId == ItemIds.Venture;
|
||||
if (isVenture)
|
||||
toBuy = Math.Min(toBuy, 65000 - GetCurrentVentureCount());
|
||||
|
||||
if (toBuy == 0)
|
||||
{
|
||||
_turnInWindow.State = false;
|
||||
CurrentStage = Stage.RequestStop;
|
||||
break;
|
||||
}
|
||||
|
||||
PluginLog.Information($"Selecting item {itemId}, {i}");
|
||||
_chatGui.Print($"Buying {toBuy}x {_selectedRewardItem.Name}...");
|
||||
var selectReward = stackalloc AtkValue[]
|
||||
{
|
||||
new() { Type = ValueType.Int, Int = 0 },
|
||||
new() { Type = ValueType.Int, Int = (int)i },
|
||||
new() { Type = ValueType.Int, Int = (int)toBuy },
|
||||
new() { Type = 0, Int = 0 },
|
||||
new() { Type = ValueType.Bool, Byte = 1 },
|
||||
new() { Type = ValueType.Bool, Byte = 0 },
|
||||
new() { Type = 0, Int = 0 },
|
||||
new() { Type = 0, Int = 0 },
|
||||
new() { Type = 0, Int = 0 }
|
||||
};
|
||||
addonExchange->FireCallback(9, selectReward);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private unsafe int GetCurrentSealCount()
|
||||
{
|
||||
InventoryManager* inventoryManager = InventoryManager.Instance();
|
||||
switch ((GrandCompany)PlayerState.Instance()->GrandCompany)
|
||||
{
|
||||
case GrandCompany.Maelstrom:
|
||||
return inventoryManager->GetInventoryItemCount(20, false, false, false);
|
||||
case GrandCompany.TwinAdder:
|
||||
return inventoryManager->GetInventoryItemCount(21, false, false, false);
|
||||
case GrandCompany.ImmortalFlames:
|
||||
return inventoryManager->GetInventoryItemCount(22, false, false, false);
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
internal unsafe GrandCompany GetGrandCompany() => (GrandCompany)PlayerState.Instance()->GrandCompany;
|
||||
|
||||
internal unsafe byte GetGrandCompanyRank() => PlayerState.Instance()->GetGrandCompanyRank();
|
||||
|
||||
private int GetPersonnelOfficerId()
|
||||
{
|
||||
return GetGrandCompany() switch
|
||||
{
|
||||
GrandCompany.Maelstrom => 0xF4B94,
|
||||
GrandCompany.ImmortalFlames => 0xF4B97,
|
||||
GrandCompany.TwinAdder => 0xF4B9A,
|
||||
_ => int.MaxValue,
|
||||
};
|
||||
}
|
||||
|
||||
private int GetQuartermasterId()
|
||||
{
|
||||
return GetGrandCompany() switch
|
||||
{
|
||||
GrandCompany.Maelstrom => 0xF4B93,
|
||||
GrandCompany.ImmortalFlames => 0xF4B96,
|
||||
GrandCompany.TwinAdder => 0xF4B99,
|
||||
_ => int.MaxValue,
|
||||
};
|
||||
}
|
||||
|
||||
private uint GetSealCap() => _sealCaps.TryGetValue(GetGrandCompanyRank(), out var cap) ? cap : 0;
|
||||
|
||||
private unsafe int GetCurrentVentureCount()
|
||||
{
|
||||
InventoryManager* inventoryManager = InventoryManager.Instance();
|
||||
return inventoryManager->GetInventoryItemCount(ItemIds.Venture, false, false, false);
|
||||
}
|
||||
|
||||
private unsafe bool SelectSelectString(int choice)
|
||||
{
|
||||
if (TryGetAddonByName<AddonSelectString>("SelectString", out var addonSelectString) &&
|
||||
IsAddonReady(&addonSelectString->AtkUnitBase))
|
||||
{
|
||||
addonSelectString->AtkUnitBase.FireCallbackInt(choice);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private unsafe bool SelectSelectYesno(int choice, Predicate<string> predicate)
|
||||
{
|
||||
if (TryGetAddonByName<AddonSelectYesno>("SelectYesno", out var addonSelectYesno) &&
|
||||
IsAddonReady(&addonSelectYesno->AtkUnitBase) &&
|
||||
predicate(MemoryHelper.ReadSeString(&addonSelectYesno->PromptText->NodeText).ToString()))
|
||||
{
|
||||
PluginLog.Information(
|
||||
$"Selecting choice={choice} for '{MemoryHelper.ReadSeString(&addonSelectYesno->PromptText->NodeText)}'");
|
||||
|
||||
addonSelectYesno->AtkUnitBase.FireCallbackInt(choice);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private decimal GetSealMultiplier()
|
||||
{
|
||||
// priority seal allowance
|
||||
if (_clientState.LocalPlayer!.StatusList.Any(x => x.StatusId == 1078))
|
||||
return 1.15m;
|
||||
|
||||
// seal sweetener 1/2
|
||||
var fcStatus = _clientState.LocalPlayer!.StatusList.FirstOrDefault(x => x.StatusId == 414);
|
||||
if (fcStatus != null)
|
||||
{
|
||||
return 1m + fcStatus.StackCount / 100m;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
private unsafe void InteractWithTarget(GameObject obj)
|
||||
{
|
||||
PluginLog.Information($"Setting target to {obj}");
|
||||
if (_targetManager.Target == null || _targetManager.Target != obj)
|
||||
{
|
||||
_targetManager.Target = obj;
|
||||
}
|
||||
|
||||
TargetSystem.Instance()->InteractWithObject(
|
||||
(FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)obj.Address, false);
|
||||
}
|
||||
|
||||
private void SaveYesAlready()
|
||||
{
|
||||
if (_yesAlreadyState.Saved)
|
||||
@ -509,26 +269,4 @@ public sealed partial class DeliverooPlugin : IDalamudPlugin
|
||||
|
||||
_yesAlreadyState = (false, null);
|
||||
}
|
||||
|
||||
internal enum Stage
|
||||
{
|
||||
TargetPersonnelOfficer,
|
||||
OpenGcSupply,
|
||||
SelectExpertDeliveryTab,
|
||||
SelectItemToTurnIn,
|
||||
TurnInSelected,
|
||||
FinalizeTurnIn,
|
||||
CloseGcSupply,
|
||||
CloseGcSupplyThenStop,
|
||||
|
||||
TargetQuartermaster,
|
||||
SelectRewardTier,
|
||||
SelectRewardSubCategory,
|
||||
SelectReward,
|
||||
ConfirmReward,
|
||||
CloseGcExchange,
|
||||
|
||||
RequestStop,
|
||||
Stopped,
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,12 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Data.SqlTypes;
|
||||
using System.Linq;
|
||||
using Dalamud;
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Logging;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using GrandCompany = FFXIVClientStructs.FFXIV.Client.UI.Agent.GrandCompany;
|
||||
|
||||
namespace Deliveroo.GameData;
|
||||
|
||||
internal class GcRewardsCache
|
||||
internal sealed class GcRewardsCache
|
||||
{
|
||||
public GcRewardsCache(DataManager dataManager)
|
||||
{
|
||||
@ -17,7 +14,7 @@ internal class GcRewardsCache
|
||||
.Where(x => x.RowId > 0)
|
||||
.ToDictionary(x => x.RowId,
|
||||
x =>
|
||||
(Gc: (GrandCompany)x.GrandCompany.Row,
|
||||
(GrandCompany: (GrandCompany)x.GrandCompany.Row,
|
||||
Tier: (RewardTier)x.Tier,
|
||||
SubCategory: (RewardSubCategory)x.SubCategory));
|
||||
|
||||
@ -28,11 +25,11 @@ internal class GcRewardsCache
|
||||
foreach (var item in items)
|
||||
{
|
||||
var category = categories[item.RowId];
|
||||
Rewards[category.Gc].Add(new GcRewardItem
|
||||
Rewards[category.GrandCompany].Add(new GcRewardItem
|
||||
{
|
||||
ItemId = item.Item.Row,
|
||||
Name = item.Item.Value!.Name.ToString(),
|
||||
GrandCompany = category.Gc,
|
||||
GrandCompany = category.GrandCompany,
|
||||
Tier = category.Tier,
|
||||
SubCategory = category.SubCategory,
|
||||
RequiredRank = item.RequiredGrandCompanyRank.Row,
|
||||
@ -48,4 +45,7 @@ internal class GcRewardsCache
|
||||
{ GrandCompany.TwinAdder, new() },
|
||||
{ GrandCompany.ImmortalFlames, new() }
|
||||
};
|
||||
|
||||
public GcRewardItem GetReward(GrandCompany grandCompany, uint itemId)
|
||||
=> Rewards[grandCompany].Single(x => x.ItemId == itemId);
|
||||
}
|
||||
|
14
Deliveroo/PurchaseItemRequest.cs
Normal file
14
Deliveroo/PurchaseItemRequest.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using Deliveroo.GameData;
|
||||
|
||||
namespace Deliveroo;
|
||||
|
||||
internal sealed class PurchaseItemRequest
|
||||
{
|
||||
public required uint ItemId { get; init; }
|
||||
public required string Name { get; set; }
|
||||
public required uint EffectiveLimit { get; init; }
|
||||
public required uint SealCost { get; init; }
|
||||
public required RewardTier Tier { get; init; }
|
||||
public required RewardSubCategory SubCategory { get; init; }
|
||||
public required uint StackSize { get; init; }
|
||||
}
|
23
Deliveroo/Stage.cs
Normal file
23
Deliveroo/Stage.cs
Normal file
@ -0,0 +1,23 @@
|
||||
namespace Deliveroo;
|
||||
|
||||
internal enum Stage
|
||||
{
|
||||
TargetPersonnelOfficer,
|
||||
OpenGcSupply,
|
||||
SelectExpertDeliveryTab,
|
||||
SelectItemToTurnIn,
|
||||
TurnInSelected,
|
||||
FinalizeTurnIn,
|
||||
CloseGcSupply,
|
||||
CloseGcSupplyThenStop,
|
||||
|
||||
TargetQuartermaster,
|
||||
SelectRewardTier,
|
||||
SelectRewardSubCategory,
|
||||
SelectReward,
|
||||
ConfirmReward,
|
||||
CloseGcExchange,
|
||||
|
||||
RequestStop,
|
||||
Stopped,
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Dalamud.Interface;
|
||||
@ -20,7 +21,6 @@ internal sealed class TurnInWindow : Window
|
||||
private readonly Configuration _configuration;
|
||||
private readonly GcRewardsCache _gcRewardsCache;
|
||||
private readonly ConfigWindow _configWindow;
|
||||
private int _selectedAutoBuyItem;
|
||||
|
||||
public TurnInWindow(DeliverooPlugin plugin, DalamudPluginInterface pluginInterface, Configuration configuration,
|
||||
GcRewardsCache gcRewardsCache, ConfigWindow configWindow)
|
||||
@ -35,48 +35,49 @@ internal sealed class TurnInWindow : Window
|
||||
Position = new Vector2(100, 100);
|
||||
PositionCondition = ImGuiCond.FirstUseEver;
|
||||
|
||||
SizeConstraints = new WindowSizeConstraints
|
||||
{
|
||||
MinimumSize = new Vector2(330, 50),
|
||||
MaximumSize = new Vector2(500, 999),
|
||||
};
|
||||
|
||||
Flags = ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoCollapse;
|
||||
ShowCloseButton = false;
|
||||
}
|
||||
|
||||
public bool State { get; set; }
|
||||
public decimal Multiplier { private get; set; }
|
||||
public int EstimatedGcSeals { private get; set; }
|
||||
public string Error { private get; set; } = string.Empty;
|
||||
|
||||
private uint SelectedItemId
|
||||
public List<PurchaseItemRequest> SelectedItems
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_selectedAutoBuyItem == 0 || _selectedAutoBuyItem > _configuration.ItemsAvailableForPurchase.Count)
|
||||
return 0;
|
||||
GrandCompany grandCompany = _plugin.GetGrandCompany();
|
||||
if (grandCompany == GrandCompany.None)
|
||||
return new List<PurchaseItemRequest>();
|
||||
|
||||
return _configuration.ItemsAvailableForPurchase[_selectedAutoBuyItem - 1];
|
||||
var rank = _plugin.GetGrandCompanyRank();
|
||||
return _configuration.ItemsToPurchase
|
||||
.Where(x => x.ItemId != 0)
|
||||
.Select(x => new { Item = x, Reward = _gcRewardsCache.GetReward(grandCompany, x.ItemId) })
|
||||
.Where(x => x.Reward.RequiredRank <= rank)
|
||||
.Select(x => new PurchaseItemRequest
|
||||
{
|
||||
ItemId = x.Item.ItemId,
|
||||
Name = x.Reward.Name,
|
||||
EffectiveLimit = CalculateEffectiveLimit(
|
||||
x.Item.ItemId,
|
||||
x.Item.Limit <= 0 ? uint.MaxValue : (uint)x.Item.Limit,
|
||||
x.Reward.StackSize),
|
||||
SealCost = x.Reward.SealCost,
|
||||
Tier = x.Reward.Tier,
|
||||
SubCategory = x.Reward.SubCategory,
|
||||
StackSize = x.Reward.StackSize,
|
||||
})
|
||||
.ToList();
|
||||
}
|
||||
set
|
||||
{
|
||||
int index = _configuration.ItemsAvailableForPurchase.IndexOf(value);
|
||||
if (index >= 0)
|
||||
_selectedAutoBuyItem = index + 1;
|
||||
else
|
||||
_selectedAutoBuyItem = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public GcRewardItem SelectedItem
|
||||
{
|
||||
get
|
||||
{
|
||||
uint selectedItemId = SelectedItemId;
|
||||
if (selectedItemId == 0)
|
||||
return GcRewardItem.None;
|
||||
|
||||
return _gcRewardsCache.Rewards[_plugin.GetGrandCompany()].Single(x => x.ItemId == selectedItemId);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnOpen()
|
||||
{
|
||||
SelectedItemId = _configuration.SelectedPurchaseItemId;
|
||||
}
|
||||
|
||||
public override void Draw()
|
||||
@ -110,7 +111,9 @@ internal sealed class TurnInWindow : Window
|
||||
if (!string.IsNullOrEmpty(Error))
|
||||
{
|
||||
ImGui.TextColored(ImGuiColors.DalamudRed, Error);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Multiplier == 1m)
|
||||
{
|
||||
ImGui.TextColored(ImGuiColors.DalamudYellow, "You do not have an active seal buff.");
|
||||
@ -120,41 +123,149 @@ internal sealed class TurnInWindow : Window
|
||||
ImGui.TextColored(ImGuiColors.HealerGreen, $"Current Buff: {(Multiplier - 1m) * 100:N0}%%");
|
||||
}
|
||||
|
||||
ImGui.Spacing();
|
||||
ImGui.Unindent(27);
|
||||
ImGui.Separator();
|
||||
ImGui.BeginDisabled(state);
|
||||
|
||||
List<string> comboValues = new() { GcRewardItem.None.Name };
|
||||
foreach (var itemId in _configuration.ItemsAvailableForPurchase)
|
||||
{
|
||||
var name = _gcRewardsCache.Rewards[grandCompany].First(x => x.ItemId == itemId).Name;
|
||||
int itemCount = GetItemCount(itemId);
|
||||
if (itemCount > 0)
|
||||
comboValues.Add($"{name} ({itemCount:N0})");
|
||||
else
|
||||
comboValues.Add(name);
|
||||
}
|
||||
|
||||
if (ImGui.Combo("", ref _selectedAutoBuyItem, comboValues.ToArray(), comboValues.Count))
|
||||
{
|
||||
_configuration.SelectedPurchaseItemId = SelectedItemId;
|
||||
_pluginInterface.SavePluginConfig(_configuration);
|
||||
}
|
||||
|
||||
if (SelectedItem.IsValid() && SelectedItem.RequiredRank > _plugin.GetGrandCompanyRank())
|
||||
ImGui.TextColored(ImGuiColors.DalamudRed, "Your rank isn't high enough to buy this item.");
|
||||
ImGui.Text("Items to buy:");
|
||||
DrawItemsToBuy(grandCompany);
|
||||
|
||||
ImGui.EndDisabled();
|
||||
}
|
||||
|
||||
ImGui.Unindent(27);
|
||||
|
||||
ImGui.Separator();
|
||||
ImGui.Text($"Debug (State): {_plugin.CurrentStage}");
|
||||
switch (_plugin.CurrentStage)
|
||||
{
|
||||
case Stage.SelectItemToTurnIn:
|
||||
case Stage.TurnInSelected:
|
||||
case Stage.FinalizeTurnIn:
|
||||
case Stage.CloseGcSupply:
|
||||
ImGui.Text($"Estimated Total Seal Count: {EstimatedGcSeals:N0}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe int GetItemCount(uint itemId)
|
||||
private void DrawItemsToBuy(GrandCompany grandCompany)
|
||||
{
|
||||
InventoryManager* inventoryManager = InventoryManager.Instance();
|
||||
return inventoryManager->GetInventoryItemCount(itemId, false, false, false);
|
||||
List<(uint ItemId, string Name, uint Rank)> comboValues = new()
|
||||
{ (GcRewardItem.None.ItemId, GcRewardItem.None.Name, GcRewardItem.None.RequiredRank) };
|
||||
foreach (uint itemId in _configuration.ItemsAvailableForPurchase)
|
||||
{
|
||||
var gcReward = _gcRewardsCache.GetReward(grandCompany, itemId);
|
||||
int itemCount = _plugin.GetItemCount(itemId);
|
||||
if (itemCount > 0)
|
||||
comboValues.Add((itemId, $"{gcReward.Name} ({itemCount:N0})", gcReward.RequiredRank));
|
||||
else
|
||||
comboValues.Add((itemId, gcReward.Name, gcReward.RequiredRank));
|
||||
}
|
||||
|
||||
if (_configuration.ItemsToPurchase.Count == 0)
|
||||
_configuration.ItemsToPurchase.Add(new Configuration.PurchasePriority
|
||||
{ ItemId = GcRewardItem.None.ItemId, Limit = 0 });
|
||||
|
||||
int? itemToRemove = null;
|
||||
for (int i = 0; i < _configuration.ItemsToPurchase.Count; ++i)
|
||||
{
|
||||
ImGui.PushID($"ItemToBuy{i}");
|
||||
var item = _configuration.ItemsToPurchase[i];
|
||||
int comboValueIndex = comboValues.FindIndex(x => x.ItemId == item.ItemId);
|
||||
if (comboValueIndex < 0)
|
||||
{
|
||||
item.ItemId = 0;
|
||||
item.Limit = 0;
|
||||
_pluginInterface.SavePluginConfig(_configuration);
|
||||
|
||||
comboValueIndex = 0;
|
||||
}
|
||||
|
||||
if (ImGui.Combo("", ref comboValueIndex, comboValues.Select(x => x.Name).ToArray(), comboValues.Count))
|
||||
{
|
||||
item.ItemId = comboValues[comboValueIndex].ItemId;
|
||||
_pluginInterface.SavePluginConfig(_configuration);
|
||||
}
|
||||
|
||||
if (_configuration.ItemsToPurchase.Count >= 2)
|
||||
{
|
||||
ImGui.SameLine();
|
||||
if (ImGuiComponents.IconButton($"###Remove{i}", FontAwesomeIcon.Times))
|
||||
itemToRemove = i;
|
||||
}
|
||||
|
||||
ImGui.Indent(27);
|
||||
if (comboValueIndex > 0)
|
||||
{
|
||||
ImGui.SetNextItemWidth(ImGuiHelpers.GlobalScale * 130);
|
||||
int limit = item.Limit;
|
||||
if (item.ItemId == ItemIds.Venture)
|
||||
limit = Math.Min(limit, 65_000);
|
||||
|
||||
if (ImGui.InputInt("Maximum items to buy", ref limit, 50, 500))
|
||||
{
|
||||
item.Limit = Math.Max(0, limit);
|
||||
if (item.ItemId == ItemIds.Venture)
|
||||
item.Limit = Math.Min(item.Limit, 65_000);
|
||||
|
||||
_pluginInterface.SavePluginConfig(_configuration);
|
||||
}
|
||||
}
|
||||
else if (item.Limit != 0)
|
||||
{
|
||||
item.Limit = 0;
|
||||
_pluginInterface.SavePluginConfig(_configuration);
|
||||
}
|
||||
|
||||
if (comboValueIndex > 0 && comboValues[comboValueIndex].Rank > _plugin.GetGrandCompanyRank())
|
||||
{
|
||||
ImGui.TextColored(ImGuiColors.DalamudRed,
|
||||
"This item will be skipped, your rank isn't high enough to buy it.");
|
||||
}
|
||||
|
||||
ImGui.Unindent(27);
|
||||
ImGui.PopID();
|
||||
}
|
||||
|
||||
if (itemToRemove != null)
|
||||
{
|
||||
_configuration.ItemsToPurchase.RemoveAt(itemToRemove.Value);
|
||||
_pluginInterface.SavePluginConfig(_configuration);
|
||||
}
|
||||
|
||||
if (_configuration.ItemsAvailableForPurchase.Any(x => _configuration.ItemsToPurchase.All(y => x != y.ItemId)))
|
||||
{
|
||||
if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Plus, "Add Item"))
|
||||
{
|
||||
_configuration.ItemsToPurchase.Add(new Configuration.PurchasePriority
|
||||
{ ItemId = GcRewardItem.None.ItemId, Limit = 0 });
|
||||
_pluginInterface.SavePluginConfig(_configuration);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe uint CalculateEffectiveLimit(uint itemId, uint limit, uint stackSize)
|
||||
{
|
||||
if (itemId == ItemIds.Venture)
|
||||
return Math.Min(limit, 65_000);
|
||||
else
|
||||
{
|
||||
uint slotsThatCanBeUsed = 0;
|
||||
InventoryManager* inventoryManager = InventoryManager.Instance();
|
||||
for (InventoryType inventoryType = InventoryType.Inventory1;
|
||||
inventoryType <= InventoryType.Inventory4;
|
||||
++inventoryType)
|
||||
{
|
||||
var container = inventoryManager->GetInventoryContainer(inventoryType);
|
||||
for (int i = 0; i < container->Size; ++i)
|
||||
{
|
||||
var item = container->GetInventorySlot(i);
|
||||
if (item == null || item->ItemID == 0 || item->ItemID == itemId)
|
||||
{
|
||||
slotsThatCanBeUsed++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Math.Min(limit, slotsThatCanBeUsed * stackSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user