1
0
forked from liza/Deliveroo

Split main file, add item filter config, remove haseltweaks requirement

This commit is contained in:
Liza 2023-09-22 22:42:18 +02:00
parent c663c03b10
commit 37adccad11
Signed by: liza
GPG Key ID: 7199F8D727D55F67
6 changed files with 398 additions and 240 deletions

View File

@ -11,4 +11,12 @@ internal sealed class Configuration : IPluginConfiguration
public uint SelectedPurchaseItemId { get; set; } = 0;
public int ReservedSealCount { get; set; } = 0;
public ItemFilterType ItemFilter { get; set; } = ItemFilterType.HideGearSetItems;
public enum ItemFilterType
{
ShowAllItems = 0,
HideGearSetItems = 1,
HideArmouryChestItems = 2,
}
}

View File

@ -0,0 +1,111 @@
using System;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Logging;
using FFXIVClientStructs.FFXIV.Component.GUI;
using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType;
namespace Deliveroo;
partial class DeliverooPlugin
{
private void InteractWithQuartermaster(GameObject personnelOfficer, GameObject quartermaster)
{
if (GetCurrentSealCount() < _configuration.ReservedSealCount)
{
CurrentStage = Stage.RequestStop;
return;
}
if (_targetManager.Target == personnelOfficer)
return;
InteractWithTarget(quartermaster);
CurrentStage = Stage.SelectRewardTier;
}
private unsafe void SelectRewardTier()
{
if (TryGetAddonByName<AtkUnitBase>("GrandCompanyExchange", out var addonExchange) &&
IsAddonReady(addonExchange))
{
PluginLog.Information($"Selecting tier 1, {(int)_selectedRewardItem.Tier - 1}");
var selectRank = stackalloc AtkValue[]
{
new() { Type = ValueType.Int, Int = 1 },
new() { Type = ValueType.Int, Int = (int)_selectedRewardItem.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 },
new() { Type = 0, Int = 0 }
};
addonExchange->FireCallback(9, selectRank);
_continueAt = DateTime.Now.AddSeconds(0.5);
CurrentStage = Stage.SelectRewardSubCategory;
}
}
private unsafe void SelectRewardSubCategory()
{
if (TryGetAddonByName<AtkUnitBase>("GrandCompanyExchange", out var addonExchange) &&
IsAddonReady(addonExchange))
{
PluginLog.Information($"Selecting subcategory 2, {(int)_selectedRewardItem.SubCategory}");
var selectType = stackalloc AtkValue[]
{
new() { Type = ValueType.Int, Int = 2 },
new() { Type = ValueType.Int, Int = (int)_selectedRewardItem.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 },
new() { Type = 0, Int = 0 }
};
addonExchange->FireCallback(9, selectType);
_continueAt = DateTime.Now.AddSeconds(0.5);
CurrentStage = Stage.SelectReward;
}
}
private unsafe void SelectReward()
{
if (TryGetAddonByName<AtkUnitBase>("GrandCompanyExchange", out var addonExchange) &&
IsAddonReady(addonExchange))
{
if (SelectRewardItem(addonExchange))
{
_continueAt = DateTime.Now.AddSeconds(0.5);
CurrentStage = Stage.ConfirmReward;
}
else
{
PluginLog.Warning("Could not find selected reward item");
_continueAt = DateTime.Now.AddSeconds(0.5);
CurrentStage = Stage.CloseGcExchange;
}
}
}
private void ConfirmReward()
{
if (SelectSelectYesno(0))
{
CurrentStage = Stage.CloseGcExchange;
_continueAt = DateTime.Now.AddSeconds(0.5);
}
}
private unsafe void CloseGcExchange()
{
if (TryGetAddonByName<AtkUnitBase>("GrandCompanyExchange", out var addonExchange) &&
IsAddonReady(addonExchange))
{
addonExchange->FireCallbackInt(-1);
CurrentStage = Stage.TargetPersonnelOfficer;
}
}
}

View File

@ -0,0 +1,197 @@
using System;
using System.Collections.Generic;
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;
using FFXIVClientStructs.FFXIV.Component.GUI;
using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType;
namespace Deliveroo;
partial class DeliverooPlugin
{
private void InteractWithPersonnelOfficer(GameObject personnelOfficer, GameObject quartermaster)
{
if (_targetManager.Target == quartermaster)
return;
InteractWithTarget(personnelOfficer);
CurrentStage = Stage.OpenGcSupply;
}
private void OpenGcSupply()
{
if (SelectSelectString(0))
CurrentStage = Stage.SelectExpertDeliveryTab;
}
private unsafe void SelectExpertDeliveryTab()
{
var agentInterface = AgentModule.Instance()->GetAgentByInternalId(AgentId.GrandCompanySupply);
if (agentInterface != null && agentInterface->IsAgentActive())
{
var addonId = agentInterface->GetAddonID();
if (addonId == 0)
return;
AtkUnitBase* addon = GetAddonById(addonId);
if (addon == null || !IsAddonReady(addon))
return;
// if using haseltweaks, this *can* be the default
var addonGc = (AddonGrandCompanySupplyList*)addon;
if (addonGc->SelectedTab == 2)
{
PluginLog.Information("Tab already selected, probably due to haseltweaks");
CurrentStage = Stage.SelectItemToTurnIn;
return;
}
PluginLog.Information("Switching to expert deliveries");
var selectExpertDeliveryTab = stackalloc AtkValue[]
{
new() { Type = ValueType.Int, Int = 0 },
new() { Type = ValueType.Int, Int = 2 },
new() { Type = 0, Int = 0 }
};
addon->FireCallback(3, selectExpertDeliveryTab);
}
}
private unsafe void SelectItemToTurnIn()
{
var agentInterface = AgentModule.Instance()->GetAgentByInternalId(AgentId.GrandCompanySupply);
if (agentInterface != null && agentInterface->IsAgentActive())
{
var addonId = agentInterface->GetAddonID();
if (addonId == 0)
return;
AtkUnitBase* addon = GetAddonById(addonId);
if (addon == null || !IsAddonReady(addon) || addon->UldManager.NodeListCount <= 20 ||
!addon->UldManager.NodeList[5]->IsVisible)
return;
var addonGc = (AddonGrandCompanySupplyList*)addon;
if (addonGc->SelectedTab != 2)
{
_turnInWindow.Error = "Wrong tab selected";
return;
}
if (addonGc->SelectedFilter == 0 || addonGc->SelectedFilter != (int)_configuration.ItemFilter)
{
_turnInWindow.Error =
$"Wrong filter selected (expected {_configuration.ItemFilter}, but is {(Configuration.ItemFilterType)addonGc->SelectedFilter})";
return;
}
var agent = (AgentGrandCompanySupply*)agentInterface;
List<TurnInItem> items = BuildTurnInList(agent);
if (items.Count == 0 || addon->UldManager.NodeList[20]->IsVisible)
{
CurrentStage = Stage.CloseGcSupplyThenStop;
addon->FireCallbackInt(-1);
return;
}
if (GetCurrentSealCount() + items[0].SealsWithBonus > GetSealCap())
{
CurrentStage = Stage.CloseGcSupply;
addon->FireCallbackInt(-1);
return;
}
var selectFirstItem = stackalloc AtkValue[]
{
new() { Type = ValueType.Int, Int = 1 },
new() { Type = ValueType.Int, Int = 0 /* position within list */ },
new() { Type = 0, Int = 0 }
};
addon->FireCallback(3, selectFirstItem);
CurrentStage = Stage.TurnInSelected;
}
}
private unsafe void TurnInSelectedItem()
{
if (TryGetAddonByName<AddonSelectYesno>("SelectYesno", out var addonSelectYesno) &&
IsAddonReady(&addonSelectYesno->AtkUnitBase))
{
if (MemoryHelper.ReadSeString(&addonSelectYesno->PromptText->NodeText).ToString()
.StartsWith("Do you really want to trade a high-quality item?"))
{
addonSelectYesno->AtkUnitBase.FireCallbackInt(0);
return;
}
}
if (TryGetAddonByName<AddonGrandCompanySupplyReward>("GrandCompanySupplyReward",
out var addonSupplyReward) && IsAddonReady(&addonSupplyReward->AtkUnitBase))
{
addonSupplyReward->AtkUnitBase.FireCallbackInt(0);
_continueAt = DateTime.Now.AddSeconds(0.58);
CurrentStage = Stage.FinalizeTurnIn;
}
}
private unsafe void FinalizeTurnInItem()
{
if (TryGetAddonByName<AddonGrandCompanySupplyList>("GrandCompanySupplyList",
out var addonSupplyList) && IsAddonReady(&addonSupplyList->AtkUnitBase))
{
var updateFilter = stackalloc AtkValue[]
{
new() { Type = ValueType.Int, Int = 5 },
new() { Type = ValueType.Int, Int = (int)_configuration.ItemFilter },
new() { Type = 0, Int = 0 }
};
addonSupplyList->AtkUnitBase.FireCallback(3, updateFilter);
CurrentStage = Stage.SelectItemToTurnIn;
}
}
private void CloseGcSupply()
{
if (SelectSelectString(3))
{
if (!_selectedRewardItem.IsValid())
{
_turnInWindow.State = false;
CurrentStage = Stage.RequestStop;
}
else
{
// you can occasionally get a 'not enough seals' warning lol
_continueAt = DateTime.Now.AddSeconds(1);
CurrentStage = Stage.TargetQuartermaster;
}
}
}
private void CloseGcSupplyThenStop()
{
if (SelectSelectString(3))
{
if (!_selectedRewardItem.IsValid())
{
_turnInWindow.State = false;
CurrentStage = Stage.RequestStop;
}
else if (GetCurrentSealCount() <=
_configuration.ReservedSealCount + _selectedRewardItem.SealCost)
{
_turnInWindow.State = false;
CurrentStage = Stage.RequestStop;
}
else
{
_continueAt = DateTime.Now.AddSeconds(1);
CurrentStage = Stage.TargetQuartermaster;
}
}
}
}

View File

@ -30,7 +30,7 @@ using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType;
namespace Deliveroo;
public sealed class DeliverooPlugin : IDalamudPlugin
public sealed partial class DeliverooPlugin : IDalamudPlugin
{
private readonly WindowSystem _windowSystem = new(typeof(DeliverooPlugin).AssemblyQualifiedName);
@ -78,7 +78,7 @@ public sealed class DeliverooPlugin : IDalamudPlugin
_gcRewardsCache = new GcRewardsCache(dataManager);
_configWindow = new ConfigWindow(_pluginInterface, this, _configuration, _gcRewardsCache);
_windowSystem.AddWindow(_configWindow);
_turnInWindow = new TurnInWindow(this, _pluginInterface, _configuration, _gcRewardsCache);
_turnInWindow = new TurnInWindow(this, _pluginInterface, _configuration, _gcRewardsCache, _configWindow);
_windowSystem.AddWindow(_turnInWindow);
_sealCaps = dataManager.GetExcelSheet<GrandCompanyRank>()!.Where(x => x.RowId > 0)
.ToDictionary(x => x.RowId, x => x.MaxSeals);
@ -105,6 +105,7 @@ public sealed class DeliverooPlugin : IDalamudPlugin
private unsafe void FrameworkUpdate(Framework f)
{
_turnInWindow.Error = string.Empty;
if (!_clientState.IsLoggedIn || _clientState.TerritoryType is not 128 and not 130 and not 132 ||
GetDistanceToNpc(GetQuartermasterId(), out GameObject? quartermaster) >= 7f ||
GetDistanceToNpc(GetPersonnelOfficerId(), out GameObject? personnelOfficer) >= 7f ||
@ -145,7 +146,7 @@ public sealed class DeliverooPlugin : IDalamudPlugin
if (TryGetAddonByName<AddonGrandCompanySupplyList>("GrandCompanySupplyList", out var gcSupplyList) &&
IsAddonReady(&gcSupplyList->AtkUnitBase))
CurrentStage = Stage.SelectItemToTurnIn;
CurrentStage = Stage.SelectExpertDeliveryTab;
if (TryGetAddonByName<AtkUnitBase>("GrandCompanyExchange", out var gcExchange) &&
IsAddonReady(gcExchange))
@ -158,247 +159,60 @@ public sealed class DeliverooPlugin : IDalamudPlugin
switch (CurrentStage)
{
case Stage.TargetPersonnelOfficer:
if (_targetManager.Target == quartermaster!)
InteractWithPersonnelOfficer(personnelOfficer!, quartermaster!);
break;
InteractWithTarget(personnelOfficer!);
CurrentStage = Stage.OpenGcSupply;
break;
case Stage.OpenGcSupply:
if (SelectSelectString(0))
CurrentStage = Stage.SelectItemToTurnIn;
OpenGcSupply();
break;
case Stage.SelectExpertDeliveryTab:
SelectExpertDeliveryTab();
break;
case Stage.SelectItemToTurnIn:
var agentInterface = AgentModule.Instance()->GetAgentByInternalId(AgentId.GrandCompanySupply);
if (agentInterface != null && agentInterface->IsAgentActive())
{
var addonId = agentInterface->GetAddonID();
if (addonId == 0)
SelectItemToTurnIn();
break;
AtkUnitBase* addon = GetAddonById(addonId);
if (addon == null || !IsAddonReady(addon) || addon->UldManager.NodeListCount <= 20 ||
!addon->UldManager.NodeList[5]->IsVisible)
break;
var addonGc = (AddonGrandCompanySupplyList*)addon;
if (addonGc->SelectedTab != 2 || addonGc->SelectedFilter != 1)
break;
var agent = (AgentGrandCompanySupply*)agentInterface;
List<TurnInItem> items = BuildTurnInList(agent);
if (items.Count == 0 || addon->UldManager.NodeList[20]->IsVisible)
{
CurrentStage = Stage.CloseGcSupplyThenStop;
addon->FireCallbackInt(-1);
break;
}
if (GetCurrentSealCount() + items[0].SealsWithBonus > GetSealCap())
{
CurrentStage = Stage.CloseGcSupply;
addon->FireCallbackInt(-1);
break;
}
var selectFirstItem = stackalloc AtkValue[]
{
new() { Type = ValueType.Int, Int = 1 },
new() { Type = ValueType.Int, Int = 0 /* position within list */ },
new() { Type = 0, Int = 0 }
};
addon->FireCallback(3, selectFirstItem);
CurrentStage = Stage.TurnInSelected;
}
break;
case Stage.TurnInSelected:
if (TryGetAddonByName<AddonSelectYesno>("SelectYesno", out var addonSelectYesno) &&
IsAddonReady(&addonSelectYesno->AtkUnitBase))
{
if (MemoryHelper.ReadSeString(&addonSelectYesno->PromptText->NodeText).ToString()
.StartsWith("Do you really want to trade a high-quality item?"))
{
addonSelectYesno->AtkUnitBase.FireCallbackInt(0);
break;
}
}
if (TryGetAddonByName<AddonGrandCompanySupplyReward>("GrandCompanySupplyReward",
out var addonSupplyReward) && IsAddonReady(&addonSupplyReward->AtkUnitBase))
{
addonSupplyReward->AtkUnitBase.FireCallbackInt(0);
_continueAt = DateTime.Now.AddSeconds(0.58);
CurrentStage = Stage.FinalizeTurnIn;
}
TurnInSelectedItem();
break;
case Stage.FinalizeTurnIn:
if (TryGetAddonByName<AddonGrandCompanySupplyList>("GrandCompanySupplyList",
out var addonSupplyList) && IsAddonReady(&addonSupplyList->AtkUnitBase))
{
var updateFilter = stackalloc AtkValue[]
{
new() { Type = ValueType.Int, Int = 5 },
new() { Type = ValueType.Int, Int = addonSupplyList->SelectedFilter },
new() { Type = 0, Int = 0 }
};
addonSupplyList->AtkUnitBase.FireCallback(3, updateFilter);
CurrentStage = Stage.SelectItemToTurnIn;
}
FinalizeTurnInItem();
break;
case Stage.CloseGcSupply:
if (SelectSelectString(3))
{
if (!_selectedRewardItem.IsValid())
{
_turnInWindow.State = false;
CurrentStage = Stage.RequestStop;
}
else
{
// you can occasionally get a 'not enough seals' warning lol
_continueAt = DateTime.Now.AddSeconds(1);
CurrentStage = Stage.TargetQuartermaster;
}
}
CloseGcSupply();
break;
case Stage.CloseGcSupplyThenStop:
if (SelectSelectString(3))
{
if (!_selectedRewardItem.IsValid())
{
_turnInWindow.State = false;
CurrentStage = Stage.RequestStop;
}
else if (GetCurrentSealCount() <=
_configuration.ReservedSealCount + _selectedRewardItem.SealCost)
{
_turnInWindow.State = false;
CurrentStage = Stage.RequestStop;
}
else
{
_continueAt = DateTime.Now.AddSeconds(1);
CurrentStage = Stage.TargetQuartermaster;
}
}
CloseGcSupplyThenStop();
break;
case Stage.TargetQuartermaster:
if (GetCurrentSealCount() < _configuration.ReservedSealCount)
{
CurrentStage = Stage.RequestStop;
break;
}
if (_targetManager.Target == personnelOfficer!)
break;
InteractWithTarget(quartermaster!);
CurrentStage = Stage.SelectRewardTier;
InteractWithQuartermaster(personnelOfficer!, quartermaster!);
break;
case Stage.SelectRewardTier:
{
if (TryGetAddonByName<AtkUnitBase>("GrandCompanyExchange", out var addonExchange) &&
IsAddonReady(addonExchange))
{
PluginLog.Information($"Selecting tier 1, {(int)_selectedRewardItem.Tier - 1}");
var selectRank = stackalloc AtkValue[]
{
new() { Type = ValueType.Int, Int = 1 },
new() { Type = ValueType.Int, Int = (int)_selectedRewardItem.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 },
new() { Type = 0, Int = 0 }
};
addonExchange->FireCallback(9, selectRank);
_continueAt = DateTime.Now.AddSeconds(0.5);
CurrentStage = Stage.SelectRewardSubCategory;
}
SelectRewardTier();
break;
}
case Stage.SelectRewardSubCategory:
{
if (TryGetAddonByName<AtkUnitBase>("GrandCompanyExchange", out var addonExchange) &&
IsAddonReady(addonExchange))
{
PluginLog.Information($"Selecting subcategory 2, {(int)_selectedRewardItem.SubCategory}");
var selectType = stackalloc AtkValue[]
{
new() { Type = ValueType.Int, Int = 2 },
new() { Type = ValueType.Int, Int = (int)_selectedRewardItem.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 },
new() { Type = 0, Int = 0 }
};
addonExchange->FireCallback(9, selectType);
_continueAt = DateTime.Now.AddSeconds(0.5);
CurrentStage = Stage.SelectReward;
}
SelectRewardSubCategory();
break;
}
case Stage.SelectReward:
{
if (TryGetAddonByName<AtkUnitBase>("GrandCompanyExchange", out var addonExchange) &&
IsAddonReady(addonExchange))
{
if (SelectRewardItem(addonExchange))
{
_continueAt = DateTime.Now.AddSeconds(0.5);
CurrentStage = Stage.ConfirmReward;
}
else
{
PluginLog.Warning("Could not find selected reward item");
_continueAt = DateTime.Now.AddSeconds(0.5);
CurrentStage = Stage.CloseGcExchange;
}
}
SelectReward();
break;
}
case Stage.ConfirmReward:
if (SelectSelectYesno(0))
{
CurrentStage = Stage.CloseGcExchange;
_continueAt = DateTime.Now.AddSeconds(0.5);
}
ConfirmReward();
break;
case Stage.CloseGcExchange:
{
if (TryGetAddonByName<AtkUnitBase>("GrandCompanyExchange", out var addonExchange) &&
IsAddonReady(addonExchange))
{
addonExchange->FireCallbackInt(-1);
CurrentStage = Stage.TargetPersonnelOfficer;
}
CloseGcExchange();
break;
}
case Stage.RequestStop:
RestoreYesAlready();
@ -699,6 +513,7 @@ public sealed class DeliverooPlugin : IDalamudPlugin
{
TargetPersonnelOfficer,
OpenGcSupply,
SelectExpertDeliveryTab,
SelectItemToTurnIn,
TurnInSelected,
FinalizeTurnIn,

View File

@ -13,6 +13,8 @@ namespace Deliveroo.Windows;
internal sealed class ConfigWindow : Window
{
private static string[] _itemFilterValues = { "Hide Gear Set Items", "Hide Armoury Chest Items" };
private readonly DalamudPluginInterface _pluginInterface;
private readonly DeliverooPlugin _plugin;
private readonly Configuration _configuration;
@ -170,6 +172,16 @@ internal sealed class ConfigWindow : Window
_configuration.ReservedSealCount = Math.Max(0, Math.Min(90_000, reservedSealCount));
Save();
}
ImGui.Separator();
int selectedItemFilter = Math.Max(0, (int)_configuration.ItemFilter - 1);
if (ImGui.Combo("Item Filter", ref selectedItemFilter, _itemFilterValues, _itemFilterValues.Length))
{
_configuration.ItemFilter = (Configuration.ItemFilterType)(selectedItemFilter + 1);
Save();
}
ImGui.EndTabItem();
}
}

View File

@ -1,7 +1,9 @@
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Dalamud.Interface;
using Dalamud.Interface.Colors;
using Dalamud.Interface.Components;
using Dalamud.Interface.Windowing;
using Dalamud.Plugin;
using Deliveroo.GameData;
@ -17,16 +19,18 @@ internal sealed class TurnInWindow : Window
private readonly DalamudPluginInterface _pluginInterface;
private readonly Configuration _configuration;
private readonly GcRewardsCache _gcRewardsCache;
private readonly ConfigWindow _configWindow;
private int _selectedAutoBuyItem = 0;
public TurnInWindow(DeliverooPlugin plugin, DalamudPluginInterface pluginInterface, Configuration configuration,
GcRewardsCache gcRewardsCache)
GcRewardsCache gcRewardsCache, ConfigWindow configWindow)
: base("GC Delivery###DeliverooTurnIn")
{
_plugin = plugin;
_pluginInterface = pluginInterface;
_configuration = configuration;
_gcRewardsCache = gcRewardsCache;
_configWindow = configWindow;
Position = new Vector2(100, 100);
PositionCondition = ImGuiCond.FirstUseEver;
@ -37,6 +41,7 @@ internal sealed class TurnInWindow : Window
public bool State { get; set; }
public decimal Multiplier { private get; set; }
public string Error { private get; set; }
public uint SelectedItemId
{
@ -97,10 +102,18 @@ internal sealed class TurnInWindow : Window
State = state;
}
ImGui.SameLine();
if (ImGuiComponents.IconButton("###OpenConfig", FontAwesomeIcon.Cog))
_configWindow.IsOpen = true;
ImGui.Indent(27);
if (!string.IsNullOrEmpty(Error))
{
ImGui.TextColored(ImGuiColors.DalamudRed, Error);
} else {
if (Multiplier == 1m)
{
ImGui.TextColored(ImGuiColors.DalamudRed, "You do not have an active seal buff.");
ImGui.TextColored(ImGuiColors.DalamudYellow, "You do not have an active seal buff.");
}
else
{
@ -131,6 +144,8 @@ internal sealed class TurnInWindow : Window
ImGui.TextColored(ImGuiColors.DalamudRed, "Your rank isn't high enough to buy this item.");
ImGui.EndDisabled();
}
ImGui.Unindent(27);
ImGui.Separator();