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 int Version { get; set; } = 1;
|
||||||
|
|
||||||
public List<uint> ItemsAvailableForPurchase { get; set; } = new();
|
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 int ReservedSealCount { get; set; } = 0;
|
||||||
public ItemFilterType ItemFilter { get; set; } = ItemFilterType.HideGearSetItems;
|
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
|
public enum ItemFilterType
|
||||||
{
|
{
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using Dalamud.Game.ClientState.Objects.Types;
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
using Dalamud.Logging;
|
using Dalamud.Logging;
|
||||||
|
using Deliveroo.GameData;
|
||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType;
|
using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType;
|
||||||
|
|
||||||
@ -23,16 +24,38 @@ partial class DeliverooPlugin
|
|||||||
CurrentStage = Stage.SelectRewardTier;
|
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()
|
private unsafe void SelectRewardTier()
|
||||||
{
|
{
|
||||||
|
PurchaseItemRequest? item = GetNextItemToPurchase();
|
||||||
|
if (item == null)
|
||||||
|
{
|
||||||
|
CurrentStage = Stage.CloseGcExchange;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (TryGetAddonByName<AtkUnitBase>("GrandCompanyExchange", out var addonExchange) &&
|
if (TryGetAddonByName<AtkUnitBase>("GrandCompanyExchange", out var addonExchange) &&
|
||||||
IsAddonReady(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[]
|
var selectRank = stackalloc AtkValue[]
|
||||||
{
|
{
|
||||||
new() { Type = ValueType.Int, Int = 1 },
|
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 },
|
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()
|
private unsafe void SelectRewardSubCategory()
|
||||||
{
|
{
|
||||||
|
PurchaseItemRequest? item = GetNextItemToPurchase();
|
||||||
|
if (item == null)
|
||||||
|
{
|
||||||
|
CurrentStage = Stage.CloseGcExchange;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (TryGetAddonByName<AtkUnitBase>("GrandCompanyExchange", out var addonExchange) &&
|
if (TryGetAddonByName<AtkUnitBase>("GrandCompanyExchange", out var addonExchange) &&
|
||||||
IsAddonReady(addonExchange))
|
IsAddonReady(addonExchange))
|
||||||
{
|
{
|
||||||
PluginLog.Information($"Selecting subcategory 2, {(int)_selectedRewardItem.SubCategory}");
|
PluginLog.Information($"Selecting subcategory 2, {(int)item.SubCategory}");
|
||||||
var selectType = stackalloc AtkValue[]
|
var selectType = stackalloc AtkValue[]
|
||||||
{
|
{
|
||||||
new() { Type = ValueType.Int, Int = 2 },
|
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 },
|
new() { Type = 0, Int = 0 },
|
||||||
new() { Type = 0, Int = 0 },
|
new() { Type = 0, Int = 0 },
|
||||||
@ -78,22 +108,78 @@ partial class DeliverooPlugin
|
|||||||
{
|
{
|
||||||
if (SelectRewardItem(addonExchange))
|
if (SelectRewardItem(addonExchange))
|
||||||
{
|
{
|
||||||
_continueAt = DateTime.Now.AddSeconds(0.5);
|
_continueAt = DateTime.Now.AddSeconds(0.2);
|
||||||
CurrentStage = Stage.ConfirmReward;
|
CurrentStage = Stage.ConfirmReward;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
PluginLog.Warning("Could not find selected reward item");
|
_continueAt = DateTime.Now.AddSeconds(0.2);
|
||||||
_continueAt = DateTime.Now.AddSeconds(0.5);
|
|
||||||
CurrentStage = Stage.CloseGcExchange;
|
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()
|
private void ConfirmReward()
|
||||||
{
|
{
|
||||||
|
PurchaseItemRequest? item = GetNextItemToPurchase();
|
||||||
|
if (item == null)
|
||||||
|
{
|
||||||
|
CurrentStage = Stage.CloseGcExchange;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (SelectSelectYesno(0, s => s.StartsWith("Exchange ")))
|
if (SelectSelectYesno(0, s => s.StartsWith("Exchange ")))
|
||||||
{
|
{
|
||||||
|
if (GetNextItemToPurchase(item) != null)
|
||||||
|
CurrentStage = Stage.SelectRewardTier;
|
||||||
|
else
|
||||||
CurrentStage = Stage.CloseGcExchange;
|
CurrentStage = Stage.CloseGcExchange;
|
||||||
_continueAt = DateTime.Now.AddSeconds(0.5);
|
_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;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using Dalamud.Game.ClientState.Objects.Types;
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
using Dalamud.Logging;
|
using Dalamud.Logging;
|
||||||
using Dalamud.Memory;
|
|
||||||
using Deliveroo.GameData;
|
using Deliveroo.GameData;
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||||
@ -92,6 +92,7 @@ partial class DeliverooPlugin
|
|||||||
|
|
||||||
var agent = (AgentGrandCompanySupply*)agentInterface;
|
var agent = (AgentGrandCompanySupply*)agentInterface;
|
||||||
List<TurnInItem> items = BuildTurnInList(agent);
|
List<TurnInItem> items = BuildTurnInList(agent);
|
||||||
|
_turnInWindow.EstimatedGcSeals = GetCurrentSealCount() + items.Sum(x => x.SealsWithBonus);
|
||||||
if (items.Count == 0 || addon->UldManager.NodeList[20]->IsVisible)
|
if (items.Count == 0 || addon->UldManager.NodeList[20]->IsVisible)
|
||||||
{
|
{
|
||||||
CurrentStage = Stage.CloseGcSupplyThenStop;
|
CurrentStage = Stage.CloseGcSupplyThenStop;
|
||||||
@ -151,7 +152,7 @@ partial class DeliverooPlugin
|
|||||||
{
|
{
|
||||||
if (SelectSelectString(3))
|
if (SelectSelectString(3))
|
||||||
{
|
{
|
||||||
if (!_selectedRewardItem.IsValid())
|
if (GetNextItemToPurchase() == null)
|
||||||
{
|
{
|
||||||
_turnInWindow.State = false;
|
_turnInWindow.State = false;
|
||||||
CurrentStage = Stage.RequestStop;
|
CurrentStage = Stage.RequestStop;
|
||||||
@ -169,13 +170,13 @@ partial class DeliverooPlugin
|
|||||||
{
|
{
|
||||||
if (SelectSelectString(3))
|
if (SelectSelectString(3))
|
||||||
{
|
{
|
||||||
if (!_selectedRewardItem.IsValid())
|
if (GetNextItemToPurchase() == null)
|
||||||
{
|
{
|
||||||
_turnInWindow.State = false;
|
_turnInWindow.State = false;
|
||||||
CurrentStage = Stage.RequestStop;
|
CurrentStage = Stage.RequestStop;
|
||||||
}
|
}
|
||||||
else if (GetCurrentSealCount() <=
|
else if (GetCurrentSealCount() <=
|
||||||
_configuration.ReservedSealCount + _selectedRewardItem.SealCost)
|
_configuration.ReservedSealCount + GetNextItemToPurchase()!.SealCost)
|
||||||
{
|
{
|
||||||
_turnInWindow.State = false;
|
_turnInWindow.State = false;
|
||||||
CurrentStage = Stage.RequestStop;
|
CurrentStage = Stage.RequestStop;
|
||||||
|
@ -57,7 +57,7 @@ public sealed partial class DeliverooPlugin : IDalamudPlugin
|
|||||||
|
|
||||||
private Stage _currentStageInternal = Stage.Stopped;
|
private Stage _currentStageInternal = Stage.Stopped;
|
||||||
private DateTime _continueAt = DateTime.MinValue;
|
private DateTime _continueAt = DateTime.MinValue;
|
||||||
private GcRewardItem _selectedRewardItem = GcRewardItem.None;
|
private List<PurchaseItemRequest> _itemsToPurchaseNow = new();
|
||||||
private (bool Saved, bool? PreviousState) _yesAlreadyState = (false, null);
|
private (bool Saved, bool? PreviousState) _yesAlreadyState = (false, null);
|
||||||
|
|
||||||
public DeliverooPlugin(DalamudPluginInterface pluginInterface, ChatGui chatGui, GameGui gameGui,
|
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)
|
else if (_turnInWindow.State && CurrentStage == Stage.Stopped)
|
||||||
{
|
{
|
||||||
CurrentStage = Stage.TargetPersonnelOfficer;
|
CurrentStage = Stage.TargetPersonnelOfficer;
|
||||||
_selectedRewardItem = _turnInWindow.SelectedItem;
|
_itemsToPurchaseNow = _turnInWindow.SelectedItems;
|
||||||
if (_selectedRewardItem.IsValid() && _selectedRewardItem.RequiredRank > GetGrandCompanyRank())
|
if (_itemsToPurchaseNow.Count > 0)
|
||||||
_selectedRewardItem = GcRewardItem.None;
|
{
|
||||||
|
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;
|
CurrentStage = Stage.TargetQuartermaster;
|
||||||
|
|
||||||
if (TryGetAddonByName<AddonGrandCompanySupplyList>("GrandCompanySupplyList", out var gcSupplyList) &&
|
if (TryGetAddonByName<AddonGrandCompanySupplyList>("GrandCompanySupplyList", out var gcSupplyList) &&
|
||||||
@ -150,7 +158,7 @@ public sealed partial class DeliverooPlugin : IDalamudPlugin
|
|||||||
|
|
||||||
if (TryGetAddonByName<AtkUnitBase>("GrandCompanyExchange", out var gcExchange) &&
|
if (TryGetAddonByName<AtkUnitBase>("GrandCompanyExchange", out var gcExchange) &&
|
||||||
IsAddonReady(gcExchange))
|
IsAddonReady(gcExchange))
|
||||||
CurrentStage = Stage.CloseGcExchange;
|
CurrentStage = Stage.SelectRewardTier;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (CurrentStage != Stage.Stopped && CurrentStage != Stage.RequestStop && !_yesAlreadyState.Saved)
|
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()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
@ -260,232 +246,6 @@ public sealed partial class DeliverooPlugin : IDalamudPlugin
|
|||||||
RestoreYesAlready();
|
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()
|
private void SaveYesAlready()
|
||||||
{
|
{
|
||||||
if (_yesAlreadyState.Saved)
|
if (_yesAlreadyState.Saved)
|
||||||
@ -509,26 +269,4 @@ public sealed partial class DeliverooPlugin : IDalamudPlugin
|
|||||||
|
|
||||||
_yesAlreadyState = (false, null);
|
_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.Collections.Generic;
|
||||||
using System.Data.SqlTypes;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Dalamud;
|
|
||||||
using Dalamud.Data;
|
using Dalamud.Data;
|
||||||
using Dalamud.Logging;
|
|
||||||
using Lumina.Excel.GeneratedSheets;
|
using Lumina.Excel.GeneratedSheets;
|
||||||
using GrandCompany = FFXIVClientStructs.FFXIV.Client.UI.Agent.GrandCompany;
|
using GrandCompany = FFXIVClientStructs.FFXIV.Client.UI.Agent.GrandCompany;
|
||||||
|
|
||||||
namespace Deliveroo.GameData;
|
namespace Deliveroo.GameData;
|
||||||
|
|
||||||
internal class GcRewardsCache
|
internal sealed class GcRewardsCache
|
||||||
{
|
{
|
||||||
public GcRewardsCache(DataManager dataManager)
|
public GcRewardsCache(DataManager dataManager)
|
||||||
{
|
{
|
||||||
@ -17,7 +14,7 @@ internal class GcRewardsCache
|
|||||||
.Where(x => x.RowId > 0)
|
.Where(x => x.RowId > 0)
|
||||||
.ToDictionary(x => x.RowId,
|
.ToDictionary(x => x.RowId,
|
||||||
x =>
|
x =>
|
||||||
(Gc: (GrandCompany)x.GrandCompany.Row,
|
(GrandCompany: (GrandCompany)x.GrandCompany.Row,
|
||||||
Tier: (RewardTier)x.Tier,
|
Tier: (RewardTier)x.Tier,
|
||||||
SubCategory: (RewardSubCategory)x.SubCategory));
|
SubCategory: (RewardSubCategory)x.SubCategory));
|
||||||
|
|
||||||
@ -28,11 +25,11 @@ internal class GcRewardsCache
|
|||||||
foreach (var item in items)
|
foreach (var item in items)
|
||||||
{
|
{
|
||||||
var category = categories[item.RowId];
|
var category = categories[item.RowId];
|
||||||
Rewards[category.Gc].Add(new GcRewardItem
|
Rewards[category.GrandCompany].Add(new GcRewardItem
|
||||||
{
|
{
|
||||||
ItemId = item.Item.Row,
|
ItemId = item.Item.Row,
|
||||||
Name = item.Item.Value!.Name.ToString(),
|
Name = item.Item.Value!.Name.ToString(),
|
||||||
GrandCompany = category.Gc,
|
GrandCompany = category.GrandCompany,
|
||||||
Tier = category.Tier,
|
Tier = category.Tier,
|
||||||
SubCategory = category.SubCategory,
|
SubCategory = category.SubCategory,
|
||||||
RequiredRank = item.RequiredGrandCompanyRank.Row,
|
RequiredRank = item.RequiredGrandCompanyRank.Row,
|
||||||
@ -48,4 +45,7 @@ internal class GcRewardsCache
|
|||||||
{ GrandCompany.TwinAdder, new() },
|
{ GrandCompany.TwinAdder, new() },
|
||||||
{ GrandCompany.ImmortalFlames, 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.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
@ -20,7 +21,6 @@ internal sealed class TurnInWindow : Window
|
|||||||
private readonly Configuration _configuration;
|
private readonly Configuration _configuration;
|
||||||
private readonly GcRewardsCache _gcRewardsCache;
|
private readonly GcRewardsCache _gcRewardsCache;
|
||||||
private readonly ConfigWindow _configWindow;
|
private readonly ConfigWindow _configWindow;
|
||||||
private int _selectedAutoBuyItem;
|
|
||||||
|
|
||||||
public TurnInWindow(DeliverooPlugin plugin, DalamudPluginInterface pluginInterface, Configuration configuration,
|
public TurnInWindow(DeliverooPlugin plugin, DalamudPluginInterface pluginInterface, Configuration configuration,
|
||||||
GcRewardsCache gcRewardsCache, ConfigWindow configWindow)
|
GcRewardsCache gcRewardsCache, ConfigWindow configWindow)
|
||||||
@ -35,50 +35,51 @@ internal sealed class TurnInWindow : Window
|
|||||||
Position = new Vector2(100, 100);
|
Position = new Vector2(100, 100);
|
||||||
PositionCondition = ImGuiCond.FirstUseEver;
|
PositionCondition = ImGuiCond.FirstUseEver;
|
||||||
|
|
||||||
|
SizeConstraints = new WindowSizeConstraints
|
||||||
|
{
|
||||||
|
MinimumSize = new Vector2(330, 50),
|
||||||
|
MaximumSize = new Vector2(500, 999),
|
||||||
|
};
|
||||||
|
|
||||||
Flags = ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoCollapse;
|
Flags = ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoCollapse;
|
||||||
ShowCloseButton = false;
|
ShowCloseButton = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool State { get; set; }
|
public bool State { get; set; }
|
||||||
public decimal Multiplier { private get; set; }
|
public decimal Multiplier { private get; set; }
|
||||||
|
public int EstimatedGcSeals { private get; set; }
|
||||||
public string Error { private get; set; } = string.Empty;
|
public string Error { private get; set; } = string.Empty;
|
||||||
|
|
||||||
private uint SelectedItemId
|
public List<PurchaseItemRequest> SelectedItems
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (_selectedAutoBuyItem == 0 || _selectedAutoBuyItem > _configuration.ItemsAvailableForPurchase.Count)
|
GrandCompany grandCompany = _plugin.GetGrandCompany();
|
||||||
return 0;
|
if (grandCompany == GrandCompany.None)
|
||||||
|
return new List<PurchaseItemRequest>();
|
||||||
|
|
||||||
return _configuration.ItemsAvailableForPurchase[_selectedAutoBuyItem - 1];
|
var rank = _plugin.GetGrandCompanyRank();
|
||||||
}
|
return _configuration.ItemsToPurchase
|
||||||
set
|
.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
|
||||||
{
|
{
|
||||||
int index = _configuration.ItemsAvailableForPurchase.IndexOf(value);
|
ItemId = x.Item.ItemId,
|
||||||
if (index >= 0)
|
Name = x.Reward.Name,
|
||||||
_selectedAutoBuyItem = index + 1;
|
EffectiveLimit = CalculateEffectiveLimit(
|
||||||
else
|
x.Item.ItemId,
|
||||||
_selectedAutoBuyItem = 0;
|
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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()
|
public override void Draw()
|
||||||
{
|
{
|
||||||
GrandCompany grandCompany = _plugin.GetGrandCompany();
|
GrandCompany grandCompany = _plugin.GetGrandCompany();
|
||||||
@ -110,7 +111,9 @@ internal sealed class TurnInWindow : Window
|
|||||||
if (!string.IsNullOrEmpty(Error))
|
if (!string.IsNullOrEmpty(Error))
|
||||||
{
|
{
|
||||||
ImGui.TextColored(ImGuiColors.DalamudRed, Error);
|
ImGui.TextColored(ImGuiColors.DalamudRed, Error);
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
if (Multiplier == 1m)
|
if (Multiplier == 1m)
|
||||||
{
|
{
|
||||||
ImGui.TextColored(ImGuiColors.DalamudYellow, "You do not have an active seal buff.");
|
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.TextColored(ImGuiColors.HealerGreen, $"Current Buff: {(Multiplier - 1m) * 100:N0}%%");
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.Spacing();
|
ImGui.Unindent(27);
|
||||||
|
ImGui.Separator();
|
||||||
ImGui.BeginDisabled(state);
|
ImGui.BeginDisabled(state);
|
||||||
|
|
||||||
List<string> comboValues = new() { GcRewardItem.None.Name };
|
ImGui.Text("Items to buy:");
|
||||||
foreach (var itemId in _configuration.ItemsAvailableForPurchase)
|
DrawItemsToBuy(grandCompany);
|
||||||
{
|
|
||||||
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.EndDisabled();
|
ImGui.EndDisabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.Unindent(27);
|
|
||||||
|
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
ImGui.Text($"Debug (State): {_plugin.CurrentStage}");
|
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)
|
||||||
{
|
{
|
||||||
|
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();
|
InventoryManager* inventoryManager = InventoryManager.Instance();
|
||||||
return inventoryManager->GetInventoryItemCount(itemId, false, false, false);
|
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