Compare commits

..

21 Commits
v4.4 ... master

Author SHA1 Message Date
0cbde57b79
Add 'compact' turn-in UI 2024-12-22 16:07:55 +01:00
03aec55fbe
Rename debug PackagePlugin target 2024-12-19 15:18:08 +01:00
64551c033b
Fix all other confirmations being broken in non-JP 2024-11-23 14:35:03 +01:00
265c5d7d99
Fix HQ confirmation being broken in JP (fixes #4) 2024-11-23 14:18:23 +01:00
7492760d4f
Fix code formatting 2024-11-19 16:01:29 +01:00
31a9aeaece
API 11 2024-11-16 14:37:45 +01:00
c04b9e2ac5
Fix 'Add Item' not being visible when it should be 2024-08-31 21:11:45 +02:00
4b598ed485
Add an option for global limits to override char-specifc ones 2024-08-30 18:21:33 +02:00
780a0cbdcd
Update drag & drop functionality 2024-08-30 14:23:51 +02:00
26991aa59b
Version bump 2024-08-29 08:47:38 +02:00
1b54ba9c41 [F] Fix null safety in GetItemIdFromItemName (#3)
Reviewed-on: #3
2024-08-29 06:46:52 +00:00
Azalea
d5d2247531 [F] Fix null safety in GetItemIdFromItemName 2024-08-28 17:59:14 -04:00
2ab7ae3c06
Fix right-click menu for 'items for auto-buy' 2024-07-17 02:50:40 +02:00
92f6c9bc8a
Fix KeyState checks 2024-07-12 19:54:03 +02:00
70b69b786e
API 10 2024-07-02 21:28:51 +02:00
2be09089f5
Add quick turn-in option for manual turn-ins 2024-06-06 22:03:42 +02:00
4290e1432d
Allow starting Deliveroo from SelectString window 2024-05-31 10:10:29 +02:00
45423c0cc3
Add code to save dalamud's window options (pinned/clickthrough/alpha) 2024-05-11 00:27:33 +02:00
6a76b259bb
Add chat message when turn in is finished 2024-05-09 01:08:01 +02:00
2ad03c86ef
Move 'uncapped FPS' to a separate setting 2024-05-02 11:40:34 +02:00
e0e1de103e
Disable FPS limiter while turning in items 2024-04-30 19:35:56 +02:00
24 changed files with 930 additions and 479 deletions

View File

@ -17,10 +17,10 @@ internal sealed class CharacterConfiguration
public bool OverrideItemsToPurchase { get; set; }
public List<Configuration.PurchasePriority> ItemsToPurchase { get; set; } = new();
public static string ResolveFilePath(DalamudPluginInterface pluginInterface, ulong localContentId)
public static string ResolveFilePath(IDalamudPluginInterface pluginInterface, ulong localContentId)
=> Path.Join(pluginInterface.GetPluginConfigDirectory(), $"char.{localContentId:X}.json");
public static CharacterConfiguration? Load(DalamudPluginInterface pluginInterface, ulong localContentId)
public static CharacterConfiguration? Load(IDalamudPluginInterface pluginInterface, ulong localContentId)
{
string path = ResolveFilePath(pluginInterface, localContentId);
if (!File.Exists(path))
@ -29,11 +29,11 @@ internal sealed class CharacterConfiguration
return JsonConvert.DeserializeObject<CharacterConfiguration>(File.ReadAllText(path));
}
public void Save(DalamudPluginInterface pluginInterface)
public void Save(IDalamudPluginInterface pluginInterface)
{
File.WriteAllText(ResolveFilePath(pluginInterface, LocalContentId), JsonConvert.SerializeObject(this, Formatting.Indented));
}
public void Delete(DalamudPluginInterface pluginInterface) =>
public void Delete(IDalamudPluginInterface pluginInterface) =>
File.Delete(ResolveFilePath(pluginInterface, LocalContentId));
}

View File

@ -1,25 +1,49 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
using Dalamud.Configuration;
using Dalamud.Game.ClientState.Keys;
using Dalamud.Game.Text;
using Deliveroo.GameData;
using LLib.ImGui;
namespace Deliveroo;
internal sealed class Configuration : IPluginConfiguration
{
public int Version { get; set; } = 1;
public int Version { get; set; } = 2;
public List<uint> ItemsAvailableForPurchase { get; set; } = new();
public List<PurchasePriority> ItemsToPurchase { get; set; } = new();
[Obsolete]
public List<uint> ItemsAvailableForPurchase { get; set; } = [];
public List<PurchaseOption> ItemsAvailableToPurchase { get; set; } = [];
public List<PurchasePriority> ItemsToPurchase { get; set; } = [];
public int ReservedSealCount { get; set; }
public bool ReserveDifferentSealCountAtMaxRank { get; set; }
public int ReservedSealCountAtMaxRank { get; set; }
public XivChatType ChatType { get; set; } = XivChatType.Debug;
public int PauseAtRank { get; set; }
public EBehaviorOnOtherWorld BehaviorOnOtherWorld { get; set; } = EBehaviorOnOtherWorld.Warning;
public bool DisableFrameLimiter { get; set; } = true;
public bool UncapFrameRate { get; set; }
public VirtualKey QuickTurnInKey { get; set; } = VirtualKey.SHIFT;
public MinimizableWindowConfig TurnInWindowConfig { get; } = new();
public WindowConfig ConfigWindowConfig { get; } = new();
internal sealed class PurchaseOption
{
public uint ItemId { get; set; }
public bool SameQuantityForAllLists { get; set; }
public int GlobalLimit { get; set; }
}
internal sealed class PurchasePriority
{
[JsonIgnore]
public Guid InternalId { get; } = Guid.NewGuid();
public uint ItemId { get; set; }
public int Limit { get; set; }
public bool Enabled { get; set; } = true;
@ -45,9 +69,9 @@ internal sealed class Configuration : IPluginConfiguration
public bool AddVentureIfNoItemToPurchaseSelected()
{
if (ItemsAvailableForPurchase.Count == 0)
if (ItemsAvailableToPurchase.Count == 0)
{
ItemsAvailableForPurchase.Add(ItemIds.Venture);
ItemsAvailableToPurchase.Add(new PurchaseOption { ItemId = ItemIds.Venture });
return true;
}
@ -60,4 +84,9 @@ internal sealed class Configuration : IPluginConfiguration
Warning,
DisableTurnIn,
}
internal sealed class MinimizableWindowConfig : WindowConfig
{
public bool IsMinimized { get; set; }
}
}

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Project>
<Target Name="PackagePlugin" AfterTargets="Build" Condition="'$(Configuration)' == 'Debug'">
<Target Name="PackagePluginDebug" AfterTargets="Build" Condition="'$(Configuration)' == 'Debug'">
<DalamudPackager
ProjectDir="$(ProjectDir)"
OutputPath="$(OutputPath)"

View File

@ -1,63 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Dalamud.NET.Sdk/11.0.0">
<PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework>
<Version>4.4</Version>
<LangVersion>12</LangVersion>
<Nullable>enable</Nullable>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<Version>6.3</Version>
<OutputPath>dist</OutputPath>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
<DebugType>portable</DebugType>
<PathMap Condition="$(SolutionDir) != ''">$(SolutionDir)=X:\</PathMap>
</PropertyGroup>
<PropertyGroup>
<DalamudLibPath>$(appdata)\XIVLauncher\addon\Hooks\dev\</DalamudLibPath>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))'">
<DalamudLibPath>$(DALAMUD_HOME)/</DalamudLibPath>
</PropertyGroup>
<Import Project="..\LLib\LLib.targets"/>
<Import Project="..\LLib\RenameZip.targets"/>
<ItemGroup>
<ProjectReference Include="..\LLib\LLib.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="DalamudPackager" Version="2.1.12"/>
</ItemGroup>
<ItemGroup>
<Reference Include="Dalamud">
<HintPath>$(DalamudLibPath)Dalamud.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="ImGui.NET">
<HintPath>$(DalamudLibPath)ImGui.NET.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="Lumina">
<HintPath>$(DalamudLibPath)Lumina.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="Lumina.Excel">
<HintPath>$(DalamudLibPath)Lumina.Excel.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="Newtonsoft.Json">
<HintPath>$(DalamudLibPath)Newtonsoft.Json.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="FFXIVClientStructs">
<HintPath>$(DalamudLibPath)FFXIVClientStructs.dll</HintPath>
<Private>false</Private>
</Reference>
</ItemGroup>
<Target Name="RenameLatestZip" AfterTargets="PackagePlugin">
<Exec Command="rename $(OutDir)$(AssemblyName)\latest.zip $(AssemblyName)-$(Version).zip"/>
</Target>
</Project>

View File

@ -0,0 +1,56 @@
using System;
using System.Linq;
using Dalamud.Game.Addon.Lifecycle;
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
using Dalamud.Game.ClientState.Keys;
using Dalamud.Game.Text.SeStringHandling;
using Deliveroo.GameData;
using FFXIVClientStructs.FFXIV.Client.UI;
using LLib.GameUI;
namespace Deliveroo;
partial class DeliverooPlugin
{
private unsafe void GrandCompanySupplyRewardPostSetup(AddonEvent type, AddonArgs args)
{
bool quickTurnIn = CurrentStage == Stage.Stopped && _configuration.QuickTurnInKey != VirtualKey.NO_KEY && _keyState[_configuration.QuickTurnInKey];
if (CurrentStage == Stage.TurnInSelected || quickTurnIn)
{
AddonGrandCompanySupplyReward* addonSupplyReward = (AddonGrandCompanySupplyReward*)args.Addon;
string? itemName = addonSupplyReward->AtkUnitBase.AtkValues[4].ReadAtkString();
if (itemName != null && _itemCache.GetItemIdFromItemName(itemName)
.Any(itemId => InternalConfiguration.QuickVentureExclusiveItems.Contains(itemId)))
{
DeliveryResult = new MessageDeliveryResult
{
Message = new SeStringBuilder()
.Append("Won't turn in ")
.AddItemLink(_itemCache.GetItemIdFromItemName(itemName).First())
.Append(", as it can only be obtained through Quick Ventures.")
.Build(),
};
addonSupplyReward->AtkUnitBase.FireCallbackInt(1);
if (quickTurnIn)
CurrentStage = Stage.RequestStop;
else
CurrentStage = Stage.CloseGcSupplyWindowThenStop;
return;
}
_pluginLog.Information($"Turning in '{itemName}'");
addonSupplyReward->AtkUnitBase.FireCallbackInt(0);
ContinueAt = DateTime.Now.AddSeconds(0.58);
if (quickTurnIn)
{
DeliveryResult = new NoDeliveryResult();
CurrentStage = Stage.SingleFinalizeTurnIn;
}
else
CurrentStage = Stage.FinalizeTurnIn;
}
}
}

View File

@ -9,31 +9,36 @@ namespace Deliveroo;
partial class DeliverooPlugin
{
private unsafe void SelectStringPostSetup(AddonEvent type, AddonArgs args)
{
AddonSelectString* addonSelectString = (AddonSelectString*)args.Addon;
SelectStringPostSetup(addonSelectString, CurrentStage);
}
private unsafe bool SelectStringPostSetup(AddonSelectString* addonSelectString, Stage stage)
{
_pluginLog.Verbose("SelectString post-setup");
string desiredText;
Action followUp;
if (CurrentStage == Stage.OpenGcSupply)
if (stage == Stage.OpenGcSupply)
{
desiredText = _gameStrings.UndertakeSupplyAndProvisioningMission;
followUp = OpenGcSupplySelectStringFollowUp;
}
else if (CurrentStage == Stage.CloseGcSupplySelectString)
else if (stage == Stage.CloseGcSupplySelectString)
{
desiredText = _gameStrings.ClosePersonnelOfficerTalk;
followUp = CloseGcSupplySelectStringFollowUp;
}
else if (CurrentStage == Stage.CloseGcSupplySelectStringThenStop)
else if (stage == Stage.CloseGcSupplySelectStringThenStop)
{
desiredText = _gameStrings.ClosePersonnelOfficerTalk;
followUp = CloseGcSupplySelectStringThenStopFollowUp;
}
else
return;
return false;
_pluginLog.Verbose($"Looking for '{desiredText}' in prompt");
AddonSelectString* addonSelectString = (AddonSelectString*)args.Addon;
int entries = addonSelectString->PopupMenu.PopupMenu.EntryCount;
for (int i = 0; i < entries; ++i)
@ -50,11 +55,12 @@ partial class DeliverooPlugin
addonSelectString->AtkUnitBase.FireCallbackInt(i);
followUp();
return;
return true;
}
}
_pluginLog.Verbose($"Text '{desiredText}' was not found in prompt.");
return false;
}
private void OpenGcSupplySelectStringFollowUp()

View File

@ -1,6 +1,7 @@
using System;
using Dalamud.Game.Addon.Lifecycle;
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
using Dalamud.Game.ClientState.Keys;
using Dalamud.Memory;
using FFXIVClientStructs.FFXIV.Client.UI;
@ -40,7 +41,7 @@ partial class DeliverooPlugin
CurrentStage = Stage.CloseGcExchange;
ContinueAt = DateTime.Now.AddSeconds(0.5);
}
else if (CurrentStage == Stage.TurnInSelected &&
else if ((CurrentStage == Stage.TurnInSelected || (_configuration.QuickTurnInKey != VirtualKey.NO_KEY && _keyState[_configuration.QuickTurnInKey])) &&
_gameStrings.TradeHighQualityItem == text)
{
_pluginLog.Information($"Selecting 'yes' ({text})");

View File

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Dalamud.Game.Addon.Lifecycle;
using Dalamud.Game.ClientState.Conditions;
@ -27,7 +26,7 @@ public sealed partial class DeliverooPlugin : IDalamudPlugin
{
private readonly WindowSystem _windowSystem = new(typeof(DeliverooPlugin).AssemblyQualifiedName);
private readonly DalamudPluginInterface _pluginInterface;
private readonly IDalamudPluginInterface _pluginInterface;
private readonly IChatGui _chatGui;
private readonly IGameGui _gameGui;
private readonly IFramework _framework;
@ -36,6 +35,7 @@ public sealed partial class DeliverooPlugin : IDalamudPlugin
private readonly ICommandManager _commandManager;
private readonly IPluginLog _pluginLog;
private readonly IAddonLifecycle _addonLifecycle;
private readonly IKeyState _keyState;
// ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable
private readonly Configuration _configuration;
@ -48,6 +48,7 @@ public sealed partial class DeliverooPlugin : IDalamudPlugin
private readonly GcRewardsCache _gcRewardsCache;
private readonly IconCache _iconCache;
private readonly ItemCache _itemCache;
private readonly ExchangeHandler _exchangeHandler;
private readonly SupplyHandler _supplyHandler;
private readonly ConfigWindow _configWindow;
@ -55,10 +56,10 @@ public sealed partial class DeliverooPlugin : IDalamudPlugin
private Stage _currentStageInternal = Stage.Stopped;
public DeliverooPlugin(DalamudPluginInterface pluginInterface, IChatGui chatGui, IGameGui gameGui,
public DeliverooPlugin(IDalamudPluginInterface pluginInterface, IChatGui chatGui, IGameGui gameGui,
IFramework framework, IClientState clientState, IObjectTable objectTable, ITargetManager targetManager,
IDataManager dataManager, ICondition condition, ICommandManager commandManager, IPluginLog pluginLog,
IAddonLifecycle addonLifecycle, ITextureProvider textureProvider)
IAddonLifecycle addonLifecycle, ITextureProvider textureProvider, IGameConfig gameConfig, IKeyState keyState)
{
ArgumentNullException.ThrowIfNull(dataManager);
@ -67,30 +68,31 @@ public sealed partial class DeliverooPlugin : IDalamudPlugin
_gameGui = gameGui;
_framework = framework;
_clientState = clientState;
ITargetManager targetManager1 = targetManager;
_condition = condition;
_commandManager = commandManager;
_pluginLog = pluginLog;
_addonLifecycle = addonLifecycle;
_keyState = keyState;
_configuration = (Configuration?)_pluginInterface.GetPluginConfig() ?? new Configuration();
MigrateConfiguration();
_gameStrings = new GameStrings(dataManager, _pluginLog);
_externalPluginHandler = new ExternalPluginHandler(_pluginInterface, _pluginLog);
_externalPluginHandler = new ExternalPluginHandler(_pluginInterface, gameConfig, _configuration, _pluginLog);
_gameFunctions = new GameFunctions(objectTable, _clientState, targetManager, dataManager,
_externalPluginHandler, _pluginLog);
_configuration = (Configuration?)_pluginInterface.GetPluginConfig() ?? new Configuration();
_gcRewardsCache = new GcRewardsCache(dataManager);
_iconCache = new IconCache(textureProvider);
var itemCache = new ItemCache(dataManager);
_itemCache = new ItemCache(dataManager);
_exchangeHandler = new ExchangeHandler(this, _gameFunctions, targetManager1, _gameGui, _chatGui, _pluginLog);
_supplyHandler = new SupplyHandler(this, _gameFunctions, targetManager1, _gameGui, _chatGui, itemCache,
_pluginLog);
_exchangeHandler = new ExchangeHandler(this, _gameFunctions, targetManager, _gameGui, _chatGui, _pluginLog);
_supplyHandler = new SupplyHandler(this, _gameFunctions, targetManager, _gameGui, _pluginLog);
_configWindow = new ConfigWindow(_pluginInterface, this, _configuration, _gcRewardsCache, _clientState,
_pluginLog, _iconCache, _gameFunctions);
_windowSystem.AddWindow(_configWindow);
_turnInWindow = new TurnInWindow(this, _pluginInterface, _configuration, _condition, _clientState,
_gcRewardsCache, _configWindow, _iconCache, _gameFunctions);
_gcRewardsCache, _configWindow, _iconCache, _keyState, _gameFunctions);
_windowSystem.AddWindow(_turnInWindow);
_framework.Update += FrameworkUpdate;
@ -112,9 +114,27 @@ public sealed partial class DeliverooPlugin : IDalamudPlugin
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "SelectString", SelectStringPostSetup);
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "SelectYesno", SelectYesNoPostSetup);
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "GrandCompanySupplyReward", GrandCompanySupplyRewardPostSetup);
}
private void ChatMessage(XivChatType type, uint senderId, ref SeString sender, ref SeString message,
private void MigrateConfiguration()
{
#pragma warning disable CS0612 // Type or member is obsolete
if (_configuration.Version == 1)
{
_configuration.ItemsAvailableToPurchase = _configuration.ItemsAvailableForPurchase.Select(x =>
new Configuration.PurchaseOption
{
ItemId = x,
SameQuantityForAllLists = false,
}).ToList();
_configuration.Version = 2;
_pluginInterface.SavePluginConfig(_configuration);
}
#pragma warning restore CS0612 // Type or member is obsolete
}
private void ChatMessage(XivChatType type, int timestamp, ref SeString sender, ref SeString message,
ref bool isHandled)
{
if (_configuration.PauseAtRank <= 0)
@ -133,7 +153,12 @@ public sealed partial class DeliverooPlugin : IDalamudPlugin
{
_turnInWindow.State = false;
_pluginLog.Information($"Pausing GC delivery, FC reached rank {rank}");
_chatGui.Print($"Pausing Deliveroo, your FC reached rank {rank}.");
DeliveryResult = new MessageDeliveryResult
{
Message = new SeStringBuilder()
.Append($"Pausing Deliveroo, your FC reached rank {rank}.")
.Build(),
};
return;
}
}
@ -156,8 +181,9 @@ public sealed partial class DeliverooPlugin : IDalamudPlugin
}
internal DateTime ContinueAt { private get; set; } = DateTime.MinValue;
internal List<PurchaseItemRequest> ItemsToPurchaseNow { get; set; } = new();
internal List<PurchaseItemRequest> ItemsToPurchaseNow { get; private set; } = new();
internal int LastTurnInListSize { get; set; } = int.MaxValue;
internal IDeliveryResult? DeliveryResult { get; set; }
internal bool TurnInState
{
@ -192,11 +218,11 @@ public sealed partial class DeliverooPlugin : IDalamudPlugin
{
if (CharacterConfiguration.CachedPlayerName != _clientState.LocalPlayer!.Name.ToString() ||
CharacterConfiguration.CachedWorldName !=
_clientState.LocalPlayer.HomeWorld.GameData!.Name.ToString())
_clientState.LocalPlayer.HomeWorld.Value.Name.ToString())
{
CharacterConfiguration.CachedPlayerName = _clientState.LocalPlayer!.Name.ToString();
CharacterConfiguration.CachedWorldName =
_clientState.LocalPlayer.HomeWorld.GameData!.Name.ToString();
_clientState.LocalPlayer.HomeWorld.Value.Name.ToString();
CharacterConfiguration.Save(_pluginInterface);
}
@ -216,7 +242,7 @@ public sealed partial class DeliverooPlugin : IDalamudPlugin
}
}
private void Logout()
private void Logout(int type, int code)
{
CharacterConfiguration = null;
}
@ -227,19 +253,15 @@ public sealed partial class DeliverooPlugin : IDalamudPlugin
if (!_clientState.IsLoggedIn ||
_clientState.TerritoryType is not 128 and not 130 and not 132 ||
_condition[ConditionFlag.OccupiedInCutSceneEvent] ||
_gameFunctions.GetDistanceToNpc(_gameFunctions.GetQuartermasterId(), out GameObject? quartermaster) >= 7f ||
_gameFunctions.GetDistanceToNpc(_gameFunctions.GetPersonnelOfficerId(), out GameObject? personnelOfficer) >=
_gameFunctions.GetDistanceToNpc(_gameFunctions.GetQuartermasterId(), out IGameObject? quartermaster) >= 7f ||
_gameFunctions.GetDistanceToNpc(_gameFunctions.GetPersonnelOfficerId(), out IGameObject? personnelOfficer) >=
7f ||
CharacterConfiguration is { DisableForCharacter: true } ||
_configWindow.IsOpen)
{
_turnInWindow.IsOpen = false;
_turnInWindow.State = false;
if (CurrentStage != Stage.Stopped)
{
_externalPluginHandler.Restore();
CurrentStage = Stage.Stopped;
}
StopTurnIn();
}
else if (DateTime.Now > ContinueAt)
{
@ -248,18 +270,14 @@ public sealed partial class DeliverooPlugin : IDalamudPlugin
if (!_turnInWindow.State)
{
if (CurrentStage != Stage.Stopped)
{
_externalPluginHandler.Restore();
CurrentStage = Stage.Stopped;
}
StopTurnIn();
return;
}
else if (_turnInWindow.State && CurrentStage == Stage.Stopped)
{
CurrentStage = Stage.TargetPersonnelOfficer;
ItemsToPurchaseNow = _turnInWindow.SelectedItems;
DeliveryResult = new MessageDeliveryResult();
_supplyHandler.ResetTurnInErrorHandling();
if (ItemsToPurchaseNow.Count > 0)
{
@ -284,6 +302,13 @@ public sealed partial class DeliverooPlugin : IDalamudPlugin
if (_gameGui.TryGetAddonByName<AtkUnitBase>("GrandCompanyExchange", out var gcExchange) &&
LAddon.IsAddonReady(gcExchange))
CurrentStage = Stage.SelectRewardTier;
if (_gameGui.TryGetAddonByName<AddonSelectString>("SelectString", out var addonSelectString) &&
LAddon.IsAddonReady(&addonSelectString->AtkUnitBase))
{
if (SelectStringPostSetup(addonSelectString, Stage.OpenGcSupply))
return;
}
}
if (CurrentStage != Stage.Stopped && CurrentStage != Stage.RequestStop && !_externalPluginHandler.Saved)
@ -308,10 +333,11 @@ public sealed partial class DeliverooPlugin : IDalamudPlugin
break;
case Stage.TurnInSelected:
_supplyHandler.TurnInSelectedItem();
// see GrandCompanySupplyReward
break;
case Stage.FinalizeTurnIn:
case Stage.SingleFinalizeTurnIn:
_supplyHandler.FinalizeTurnInItem();
break;
@ -352,10 +378,9 @@ public sealed partial class DeliverooPlugin : IDalamudPlugin
break;
case Stage.RequestStop:
_externalPluginHandler.Restore();
CurrentStage = Stage.Stopped;
StopTurnIn();
break;
case Stage.Stopped:
break;
@ -366,9 +391,58 @@ public sealed partial class DeliverooPlugin : IDalamudPlugin
}
}
private void StopTurnIn()
{
if (CurrentStage != Stage.Stopped)
{
_externalPluginHandler.Restore();
CurrentStage = Stage.Stopped;
if (DeliveryResult is null or MessageDeliveryResult)
{
var text = (DeliveryResult as MessageDeliveryResult)?.Message ?? "Delivery completed.";
var message = _configuration.ChatType switch
{
XivChatType.Say
or XivChatType.Shout
or XivChatType.TellOutgoing
or XivChatType.TellIncoming
or XivChatType.Party
or XivChatType.Alliance
or (>= XivChatType.Ls1 and <= XivChatType.Ls8)
or XivChatType.FreeCompany
or XivChatType.NoviceNetwork
or XivChatType.Yell
or XivChatType.CrossParty
or XivChatType.PvPTeam
or XivChatType.CrossLinkShell1
or XivChatType.NPCDialogue
or XivChatType.NPCDialogueAnnouncements
or (>= XivChatType.CrossLinkShell2 and <= XivChatType.CrossLinkShell8)
=> new XivChatEntry
{
Message = text,
Type = _configuration.ChatType,
Name = new SeStringBuilder().AddUiForeground("Deliveroo", 52).Build(),
},
_ => new XivChatEntry
{
Message = new SeStringBuilder().AddUiForeground("[Deliveroo] ", 52)
.Append(text)
.Build(),
Type = _configuration.ChatType,
}
};
_chatGui.Print(message);
}
DeliveryResult = null;
}
}
public void Dispose()
{
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "GrandCompanySupplyReward", GrandCompanySupplyRewardPostSetup);
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "SelectYesno", SelectYesNoPostSetup);
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "SelectString", SelectStringPostSetup);

View File

@ -25,7 +25,7 @@ internal sealed class AllaganToolsIpc
private readonly ICallGateSubscriber<uint, bool, uint[], uint> _itemCountOwned;
public AllaganToolsIpc(DalamudPluginInterface pluginInterface, IPluginLog pluginLog)
public AllaganToolsIpc(IDalamudPluginInterface pluginInterface, IPluginLog pluginLog)
{
_pluginLog = pluginLog;
_itemCountOwned = pluginInterface.GetIpcSubscriber<uint, bool, uint[], uint>("AllaganTools.ItemCountOwned");

View File

@ -16,7 +16,7 @@ internal sealed class DeliverooIpc : IDisposable
private bool _running;
public DeliverooIpc(DalamudPluginInterface pluginInterface)
public DeliverooIpc(IDalamudPluginInterface pluginInterface)
{
_isTurnInRunning = pluginInterface.GetIpcProvider<bool>(IsTurnInRunning);
_turnInStarted = pluginInterface.GetIpcProvider<object>(TurnInStarted);

View File

@ -7,17 +7,24 @@ namespace Deliveroo.External;
internal sealed class ExternalPluginHandler : IDisposable
{
private readonly DalamudPluginInterface _pluginInterface;
private readonly IDalamudPluginInterface _pluginInterface;
private readonly IGameConfig _gameConfig;
private readonly Configuration _configuration;
private readonly IPluginLog _pluginLog;
private readonly DeliverooIpc _deliverooIpc;
private readonly PandoraIpc _pandoraIpc;
private readonly AllaganToolsIpc _allaganToolsIpc;
private bool? _pandoraState;
private SystemConfigState? _limitFrameRateWhenClientInactive;
private SystemConfigState? _uncapFrameRate;
public ExternalPluginHandler(DalamudPluginInterface pluginInterface, IPluginLog pluginLog)
public ExternalPluginHandler(IDalamudPluginInterface pluginInterface, IGameConfig gameConfig,
Configuration configuration, IPluginLog pluginLog)
{
_pluginInterface = pluginInterface;
_gameConfig = gameConfig;
_configuration = configuration;
_pluginLog = pluginLog;
_deliverooIpc = new DeliverooIpc(pluginInterface);
_pandoraIpc = new PandoraIpc(pluginInterface, pluginLog);
@ -38,6 +45,7 @@ internal sealed class ExternalPluginHandler : IDisposable
_deliverooIpc.StartTurnIn();
SaveYesAlreadyState();
SavePandoraState();
SaveGameConfig();
Saved = true;
}
@ -57,12 +65,25 @@ internal sealed class ExternalPluginHandler : IDisposable
_pluginLog.Info($"Previous pandora feature state: {_pandoraState}");
}
private void SaveGameConfig()
{
if (!_configuration.DisableFrameLimiter)
return;
_limitFrameRateWhenClientInactive ??=
new SystemConfigState(_gameConfig, SystemConfigState.ConfigFpsInactive, 0);
if (_configuration.UncapFrameRate)
_uncapFrameRate ??= new SystemConfigState(_gameConfig, SystemConfigState.ConfigFps, 0);
}
public void Restore()
{
if (Saved)
{
RestoreYesAlready();
RestorePandora();
RestoreGameConfig();
}
Saved = false;
@ -87,10 +108,36 @@ internal sealed class ExternalPluginHandler : IDisposable
_pandoraIpc.Enable();
}
private void RestoreGameConfig()
{
_uncapFrameRate?.Restore(_gameConfig);
_uncapFrameRate = null;
_limitFrameRateWhenClientInactive?.Restore(_gameConfig);
_limitFrameRateWhenClientInactive = null;
}
public void Dispose()
{
_deliverooIpc.Dispose();
}
public uint GetRetainerItemCount(uint itemId) => _allaganToolsIpc.GetRetainerItemCount(itemId);
private sealed record SystemConfigState(string Key, uint OldValue)
{
public const string ConfigFps = "Fps";
public const string ConfigFpsInactive = "FPSInActive";
public SystemConfigState(IGameConfig gameConfig, string key, uint newValue)
: this(key, gameConfig.System.GetUInt(key))
{
gameConfig.System.Set(key, newValue);
}
public void Restore(IGameConfig gameConfig)
{
gameConfig.System.Set(Key, OldValue);
}
}
}

View File

@ -13,7 +13,7 @@ internal sealed class PandoraIpc
private readonly ICallGateSubscriber<string, bool?> _getEnabled;
private readonly ICallGateSubscriber<string, bool, object?> _setEnabled;
public PandoraIpc(DalamudPluginInterface pluginInterface, IPluginLog pluginLog)
public PandoraIpc(IDalamudPluginInterface pluginInterface, IPluginLog pluginLog)
{
_pluginLog = pluginLog;
_getEnabled = pluginInterface.GetIpcSubscriber<string, bool?>("PandorasBox.GetFeatureEnabled");

View File

@ -1,13 +1,12 @@
using System;
using System.Data;
using System.Data;
using System.Diagnostics.CodeAnalysis;
using System.Text.RegularExpressions;
using Dalamud.Game.Text;
using Dalamud.Plugin.Services;
using LLib;
using Lumina.Excel;
using Lumina.Excel.CustomSheets;
using Lumina.Excel.GeneratedSheets;
using Lumina.Excel.Sheets;
using Lumina.Text.ReadOnly;
namespace Deliveroo.GameData;
@ -24,10 +23,10 @@ internal sealed class GameStrings
ExchangeItems = dataManager.GetRegex<Addon>(3290, addon => addon.Text, pluginLog)
?? throw new ConstraintException($"Unable to resolve {nameof(ExchangeItems)}");
TradeHighQualityItem =
dataManager.GetString<Addon>(102434, addon => addon.Text, pluginLog)?.ReplaceLineEndings("")
dataManager.GetString<Addon>(102434, addon => addon.Text, pluginLog)
?? throw new ConstraintException($"Unable to resolve {nameof(TradeHighQualityItem)}");
var rankUpFc = dataManager.GetExcelSheet<LogMessage>()!.GetRow(3123)!;
var rankUpFc = dataManager.GetExcelSheet<LogMessage>().GetRow(3123);
RankUpFc = rankUpFc.GetRegex(logMessage => logMessage.Text, pluginLog)
?? throw new ConstraintException($"Unable to resolve {nameof(RankUpFc)}");
RankUpFcType = (XivChatType)rankUpFc.LogKind;
@ -43,7 +42,16 @@ internal sealed class GameStrings
[Sheet("custom/000/ComDefGrandCompanyOfficer_00073")]
[SuppressMessage("Performance", "CA1812")]
private sealed class ComDefGrandCompanyOfficer : QuestDialogueText
private readonly struct ComDefGrandCompanyOfficer(ExcelPage page, uint offset, uint row)
: IQuestDialogueText, IExcelRow<ComDefGrandCompanyOfficer>
{
public uint RowId => row;
public ReadOnlySeString Key => page.ReadString(offset, offset);
public ReadOnlySeString Value => page.ReadString(offset + 4, offset);
static ComDefGrandCompanyOfficer IExcelRow<ComDefGrandCompanyOfficer>.Create(ExcelPage page, uint offset,
uint row) =>
new(page, offset, row);
}
}

View File

@ -1,7 +1,7 @@
using System.Collections.Generic;
using System.Linq;
using Dalamud.Plugin.Services;
using Lumina.Excel.GeneratedSheets;
using Lumina.Excel.Sheets;
using GrandCompany = FFXIVClientStructs.FFXIV.Client.UI.Agent.GrandCompany;
namespace Deliveroo.GameData;
@ -10,31 +10,32 @@ internal sealed class GcRewardsCache
{
public GcRewardsCache(IDataManager dataManager)
{
var categories = dataManager.GetExcelSheet<GCScripShopCategory>()!
var categories = dataManager.GetExcelSheet<GCScripShopCategory>()
.Where(x => x.RowId > 0)
.ToDictionary(x => x.RowId,
x =>
(GrandCompany: (GrandCompany)x.GrandCompany.Row,
(GrandCompany: (GrandCompany)x.GrandCompany.RowId,
Tier: (RewardTier)x.Tier,
SubCategory: (RewardSubCategory)x.SubCategory));
Rewards = dataManager.GetExcelSheet<GCScripShopItem>()!
.Where(x => x.RowId > 0 && x.Item.Row > 0)
Rewards = dataManager.GetSubrowExcelSheet<GCScripShopItem>()
.SelectMany(x => x)
.Where(x => x.RowId > 0 && x.Item.RowId > 0)
.GroupBy(item =>
{
var category = categories[item.RowId];
return new
{
ItemId = item.Item.Row,
Name = item.Item.Value!.Name.ToString(),
IconId = item.Item.Row == ItemIds.Venture ? 25917 : item.Item.Value!.Icon,
ItemId = item.Item.RowId,
Name = item.Item.Value.Name.ToString(),
IconId = item.Item.RowId == ItemIds.Venture ? 25917 : item.Item.Value.Icon,
category.Tier,
category.SubCategory,
RequiredRank = item.RequiredGrandCompanyRank.Row,
item.Item!.Value.StackSize,
RequiredRank = item.RequiredGrandCompanyRank.RowId,
item.Item.Value.StackSize,
SealCost = item.CostGCSeals,
InventoryLimit = item.Item.Value!.IsUnique ? 1
: item.Item.Row == ItemIds.Venture ? item.Item.Value!.StackSize
InventoryLimit = item.Item.Value.IsUnique ? 1
: item.Item.RowId == ItemIds.Venture ? item.Item.Value.StackSize
: int.MaxValue,
};
})

View File

@ -1,6 +1,6 @@
using System.Collections.Generic;
using Dalamud.Plugin.Services;
using Lumina.Excel.GeneratedSheets;
using Lumina.Excel.Sheets;
namespace Deliveroo.GameData;
@ -10,7 +10,7 @@ internal sealed class ItemCache
public ItemCache(IDataManager dataManager)
{
foreach (var item in dataManager.GetExcelSheet<Item>()!)
foreach (var item in dataManager.GetExcelSheet<Item>())
{
string name = item.Name.ToString();
if (string.IsNullOrWhiteSpace(name))
@ -19,9 +19,10 @@ internal sealed class ItemCache
if (_itemNamesToIds.TryGetValue(name, out HashSet<uint>? itemIds))
itemIds.Add(item.RowId);
else
_itemNamesToIds.Add(name, new HashSet<uint>{item.RowId});
_itemNamesToIds.Add(name, [item.RowId]);
}
}
public HashSet<uint> GetItemIdFromItemName(string name) => _itemNamesToIds[name];
public HashSet<uint> GetItemIdFromItemName(string name) =>
_itemNamesToIds.TryGetValue(name, out var itemIds) ? itemIds : [];
}

View File

@ -15,8 +15,10 @@ using FFXIVClientStructs.FFXIV.Client.Game.Control;
using FFXIVClientStructs.FFXIV.Client.Game.UI;
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
using FFXIVClientStructs.FFXIV.Common.Math;
using FFXIVClientStructs.FFXIV.Component.Excel;
using Lumina.Excel;
using Lumina.Excel.GeneratedSheets;
using Lumina.Excel.Sheets;
using Lumina.Text.ReadOnly;
using GrandCompany = FFXIVClientStructs.FFXIV.Client.UI.Agent.GrandCompany;
namespace Deliveroo;
@ -41,7 +43,7 @@ internal sealed class GameFunctions : IDisposable
_pluginLog = pluginLog;
_gcRankInfo = dataManager.GetExcelSheet<GrandCompanyRank>()!.Where(x => x.RowId > 0)
_gcRankInfo = dataManager.GetExcelSheet<GrandCompanyRank>().Where(x => x.RowId > 0)
.ToDictionary(x => x.RowId, x => new GcRankInfo
{
NameTwinAddersMale = ExtractRankName<GCRankGridaniaMaleText>(dataManager, x.RowId, r => r.Singular),
@ -53,7 +55,7 @@ internal sealed class GameFunctions : IDisposable
ExtractRankName<GCRankUldahFemaleText>(dataManager, x.RowId, r => r.Singular),
MaxSeals = x.MaxSeals,
RequiredSeals = x.RequiredSeals,
RequiredHuntingLog = x.Unknown10,
RequiredHuntingLog = x.Unknown0,
})
.AsReadOnly();
@ -61,14 +63,14 @@ internal sealed class GameFunctions : IDisposable
_clientState.TerritoryChanged += TerritoryChanged;
}
private static string ExtractRankName<T>(IDataManager dataManager, uint rankId, Func<T, Lumina.Text.SeString> func)
where T : ExcelRow
private static string ExtractRankName<T>(IDataManager dataManager, uint rankId, Func<T, ReadOnlySeString> func)
where T : struct, IExcelRow<T>
{
return func(dataManager.GetExcelSheet<T>()!.GetRow(rankId)!).ToString();
return func(dataManager.GetExcelSheet<T>().GetRow(rankId)).ToString();
}
private void Logout()
private void Logout(int type, int code)
{
_retainerItemCache.Clear();
}
@ -79,10 +81,10 @@ internal sealed class GameFunctions : IDisposable
_retainerItemCache.Clear();
}
public unsafe void InteractWithTarget(GameObject obj)
public unsafe void InteractWithTarget(IGameObject obj)
{
_pluginLog.Information($"Setting target to {obj}");
if (_targetManager.Target == null || _targetManager.Target != obj)
if (_targetManager.Target == null || _targetManager.Target.EntityId != obj.EntityId)
{
_targetManager.Target = obj;
}
@ -111,13 +113,13 @@ internal sealed class GameFunctions : IDisposable
public unsafe byte GetGrandCompanyRank() => PlayerState.Instance()->GetGrandCompanyRank();
public float GetDistanceToNpc(int npcId, out GameObject? o)
public float GetDistanceToNpc(int npcId, out IGameObject? o)
{
foreach (var obj in _objectTable)
{
if (obj.ObjectKind == ObjectKind.EventNpc && obj is Character c)
if (obj.ObjectKind == ObjectKind.EventNpc && obj is ICharacter c)
{
if (GetNpcId(obj) == npcId)
if (c.DataId == npcId)
{
o = obj;
return Vector3.Distance(_clientState.LocalPlayer?.Position ?? Vector3.Zero, c.Position);
@ -129,7 +131,7 @@ internal sealed class GameFunctions : IDisposable
return float.MaxValue;
}
public static int GetNpcId(GameObject obj)
public static int GetNpcId(IGameObject obj)
{
return Marshal.ReadInt32(obj.Address + 128);
}

View File

@ -31,7 +31,7 @@ internal sealed class ExchangeHandler
_pluginLog = pluginLog;
}
public void InteractWithQuartermaster(GameObject personnelOfficer, GameObject quartermaster)
public void InteractWithQuartermaster(IGameObject personnelOfficer, IGameObject quartermaster)
{
if (_gameFunctions.GetCurrentSealCount() < _plugin.EffectiveReservedSealCount)
{

View File

@ -1,19 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Plugin.Services;
using Deliveroo.GameData;
using FFXIVClientStructs.FFXIV.Client.UI;
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
using FFXIVClientStructs.FFXIV.Component.GUI;
using LLib;
using LLib.GameUI;
using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType;
namespace Deliveroo;
namespace Deliveroo.Handlers;
internal sealed class SupplyHandler
{
@ -21,27 +18,23 @@ internal sealed class SupplyHandler
private readonly GameFunctions _gameFunctions;
private readonly ITargetManager _targetManager;
private readonly IGameGui _gameGui;
private readonly IChatGui _chatGui;
private readonly ItemCache _itemCache;
private readonly IPluginLog _pluginLog;
private uint _turnInErrors;
public SupplyHandler(DeliverooPlugin plugin, GameFunctions gameFunctions, ITargetManager targetManager,
IGameGui gameGui, IChatGui chatGui, ItemCache itemCache, IPluginLog pluginLog)
IGameGui gameGui, IPluginLog pluginLog)
{
_plugin = plugin;
_gameFunctions = gameFunctions;
_targetManager = targetManager;
_gameGui = gameGui;
_chatGui = chatGui;
_itemCache = itemCache;
_pluginLog = pluginLog;
}
public void InteractWithPersonnelOfficer(GameObject personnelOfficer, GameObject quartermaster)
public void InteractWithPersonnelOfficer(IGameObject personnelOfficer, IGameObject quartermaster)
{
if (_targetManager.Target == quartermaster)
if (_targetManager.Target?.EntityId == quartermaster.EntityId)
return;
_gameFunctions.InteractWithTarget(personnelOfficer);
@ -53,7 +46,7 @@ internal sealed class SupplyHandler
var agentInterface = AgentModule.Instance()->GetAgentByInternalId(AgentId.GrandCompanySupply);
if (agentInterface != null && agentInterface->IsAgentActive())
{
var addonId = agentInterface->GetAddonID();
var addonId = agentInterface->GetAddonId();
if (addonId == 0)
return;
@ -96,7 +89,7 @@ internal sealed class SupplyHandler
var agentInterface = AgentModule.Instance()->GetAgentByInternalId(AgentId.GrandCompanySupply);
if (agentInterface != null && agentInterface->IsAgentActive())
{
var addonId = agentInterface->GetAddonID();
var addonId = agentInterface->GetAddonId();
if (addonId == 0)
return;
@ -106,7 +99,7 @@ internal sealed class SupplyHandler
var addonGc = (AddonGrandCompanySupplyList*)addon;
if (addonGc->ExpertDeliveryList == null ||
!addonGc->ExpertDeliveryList->AtkComponentBase.OwnerNode->AtkResNode.IsVisible)
!addonGc->ExpertDeliveryList->AtkComponentBase.OwnerNode->AtkResNode.IsVisible())
return;
if (addonGc->SelectedTab != 2)
@ -124,7 +117,7 @@ internal sealed class SupplyHandler
}
int currentListSize = addonGc->ExpertDeliveryList->ListLength;
if (addonGc->ListEmptyTextNode->AtkResNode.IsVisible || currentListSize == 0)
if (addonGc->ListEmptyTextNode->AtkResNode.IsVisible() || currentListSize == 0)
{
_pluginLog.Information(
$"No items to turn in ({addonGc->ListEmptyTextNode->AtkResNode.IsVisible}, {currentListSize})");
@ -201,33 +194,6 @@ internal sealed class SupplyHandler
}
}
public unsafe void TurnInSelectedItem()
{
if (_gameGui.TryGetAddonByName<AddonGrandCompanySupplyReward>("GrandCompanySupplyReward",
out var addonSupplyReward) && LAddon.IsAddonReady(&addonSupplyReward->AtkUnitBase))
{
string? itemName = addonSupplyReward->AtkUnitBase.AtkValues[4].ReadAtkString();
if (itemName != null && _itemCache.GetItemIdFromItemName(itemName)
.Any(itemId => InternalConfiguration.QuickVentureExclusiveItems.Contains(itemId)))
{
_chatGui.Print(new SeStringBuilder().Append("Won't turn in ")
.AddItemLink(_itemCache.GetItemIdFromItemName(itemName).First())
.Append(", as it can only be obtained through Quick Ventures.")
.Build());
addonSupplyReward->AtkUnitBase.FireCallbackInt(1);
_plugin.CurrentStage = Stage.CloseGcSupplyWindowThenStop;
return;
}
_pluginLog.Information($"Turning in '{itemName}'");
addonSupplyReward->AtkUnitBase.FireCallbackInt(0);
_plugin.ContinueAt = DateTime.Now.AddSeconds(0.58);
_plugin.CurrentStage = Stage.FinalizeTurnIn;
}
}
public unsafe void FinalizeTurnInItem()
{
if (_gameGui.TryGetAddonByName<AddonGrandCompanySupplyList>("GrandCompanySupplyList",
@ -240,7 +206,10 @@ internal sealed class SupplyHandler
new() { Type = 0, Int = 0 }
};
addonSupplyList->AtkUnitBase.FireCallback(3, updateFilter);
_plugin.CurrentStage = Stage.SelectItemToTurnIn;
if (_plugin.CurrentStage == Stage.FinalizeTurnIn)
_plugin.CurrentStage = Stage.SelectItemToTurnIn;
else
_plugin.CurrentStage = Stage.RequestStop;
}
}
@ -257,7 +226,7 @@ internal sealed class SupplyHandler
var agentInterface = AgentModule.Instance()->GetAgentByInternalId(AgentId.GrandCompanySupply);
if (agentInterface != null && agentInterface->IsAgentActive())
{
var addonId = agentInterface->GetAddonID();
var addonId = agentInterface->GetAddonId();
if (addonId == 0)
return;

View File

@ -0,0 +1,12 @@
using Dalamud.Game.Text.SeStringHandling;
namespace Deliveroo;
internal interface IDeliveryResult;
internal sealed class MessageDeliveryResult : IDeliveryResult
{
public SeString? Message { get; init; }
}
internal sealed class NoDeliveryResult : IDeliveryResult;

View File

@ -21,4 +21,6 @@ internal enum Stage
RequestStop,
Stopped,
SingleFinalizeTurnIn,
}

View File

@ -2,13 +2,15 @@
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Dalamud.Game.ClientState.Keys;
using Dalamud.Game.Text;
using Dalamud.Interface;
using Dalamud.Interface.Components;
using Dalamud.Interface.Internal;
using Dalamud.Interface.Textures.TextureWraps;
using Dalamud.Interface.Utility;
using Dalamud.Plugin;
using Dalamud.Plugin.Services;
using Dalamud.Utility;
using Deliveroo.GameData;
using ImGuiNET;
using LLib;
@ -16,9 +18,9 @@ using LLib.ImGui;
namespace Deliveroo.Windows;
internal sealed class ConfigWindow : LWindow
internal sealed class ConfigWindow : LWindow, IPersistableWindowConfig
{
private readonly DalamudPluginInterface _pluginInterface;
private readonly IDalamudPluginInterface _pluginInterface;
private readonly DeliverooPlugin _plugin;
private readonly Configuration _configuration;
private readonly GcRewardsCache _gcRewardsCache;
@ -28,10 +30,19 @@ internal sealed class ConfigWindow : LWindow
private readonly GameFunctions _gameFunctions;
private readonly IReadOnlyDictionary<uint, GcRewardItem> _itemLookup;
private string _searchString = string.Empty;
private uint _dragDropSource;
public ConfigWindow(DalamudPluginInterface pluginInterface, DeliverooPlugin plugin, Configuration configuration,
private readonly List<(VirtualKey Key, string Label)> _keyCodes =
[
(VirtualKey.NO_KEY, "Disabled"),
(VirtualKey.CONTROL, "CTRL"),
(VirtualKey.SHIFT, "SHIFT"),
(VirtualKey.MENU, "ALT"),
];
private string _searchString = string.Empty;
private Configuration.PurchaseOption? _dragDropSource;
public ConfigWindow(IDalamudPluginInterface pluginInterface, DeliverooPlugin plugin, Configuration configuration,
GcRewardsCache gcRewardsCache, IClientState clientState, IPluginLog pluginLog, IconCache iconCache,
GameFunctions gameFunctions)
: base("Deliveroo - Configuration###DeliverooConfig")
@ -57,6 +68,8 @@ internal sealed class ConfigWindow : LWindow
};
}
public WindowConfig WindowConfig => _configuration.ConfigWindowConfig;
public override void Draw()
{
if (_configuration.AddVentureIfNoItemToPurchaseSelected())
@ -76,68 +89,80 @@ internal sealed class ConfigWindow : LWindow
{
if (ImGui.BeginTabItem("Items for Auto-Buy"))
{
uint? itemToRemove = null;
uint? itemToAdd = null;
Configuration.PurchaseOption? itemToRemove = null;
Configuration.PurchaseOption? itemToAdd = null;
int indexToAdd = 0;
if (ImGui.BeginChild("Items", new Vector2(-1, -ImGui.GetFrameHeightWithSpacing()), true,
ImGuiWindowFlags.NoSavedSettings))
{
for (int i = 0; i < _configuration.ItemsAvailableForPurchase.Count; ++i)
for (int i = 0; i < _configuration.ItemsAvailableToPurchase.Count; ++i)
{
uint itemId = _configuration.ItemsAvailableForPurchase[i];
var purchaseOption = _configuration.ItemsAvailableToPurchase[i];
ImGui.PushID($"###Item{i}");
ImGui.BeginDisabled(
_configuration.ItemsAvailableForPurchase.Count == 1 && itemId == ItemIds.Venture);
_configuration.ItemsAvailableToPurchase.Count == 1 && purchaseOption.ItemId == ItemIds.Venture);
var item = _itemLookup[itemId];
IDalamudTextureWrap? icon = _iconCache.GetIcon(item.IconId);
Vector2 pos = ImGui.GetCursorPos();
Vector2 iconSize = new Vector2(ImGui.GetTextLineHeight() + ImGui.GetStyle().ItemSpacing.Y);
if (icon != null)
var item = _itemLookup[purchaseOption.ItemId];
using (var icon = _iconCache.GetIcon(item.IconId))
{
ImGui.SetCursorPos(pos + new Vector2(iconSize.X + ImGui.GetStyle().FramePadding.X,
ImGui.GetStyle().ItemSpacing.Y / 2));
}
Vector2 pos = ImGui.GetCursorPos();
Vector2 iconSize = new Vector2(ImGui.GetTextLineHeight() + ImGui.GetStyle().ItemSpacing.Y);
ImGui.Selectable($"{item.Name}{(item.Limited ? $" {SeIconChar.Hyadelyn.ToIconString()}" : "")}",
false, ImGuiSelectableFlags.SpanAllColumns);
if (icon != null)
{
ImGui.SameLine(0, 0);
ImGui.SetCursorPos(pos);
ImGui.Image(icon.ImGuiHandle, iconSize);
}
if (ImGui.BeginDragDropSource())
{
ImGui.SetDragDropPayload("DeliverooDragDrop", nint.Zero, 0);
_dragDropSource = itemId;
ImGui.EndDragDropSource();
}
if (ImGui.BeginDragDropTarget())
{
if (_dragDropSource > 0 &&
ImGui.AcceptDragDropPayload("DeliverooDragDrop").NativePtr != null)
if (icon != null)
{
itemToAdd = _dragDropSource;
indexToAdd = i;
_dragDropSource = 0;
ImGui.SetCursorPos(pos + new Vector2(iconSize.X + ImGui.GetStyle().FramePadding.X,
ImGui.GetStyle().ItemSpacing.Y / 2));
}
ImGui.EndDragDropTarget();
}
ImGui.Selectable(
$"{item.Name}{(item.Limited ? $" {SeIconChar.Hyadelyn.ToIconString()}" : "")}{(purchaseOption.SameQuantityForAllLists ? $" {((SeIconChar)57412).ToIconString()} (Limit: {purchaseOption.GlobalLimit:N0})" : "")}",
false, ImGuiSelectableFlags.SpanAllColumns);
ImGui.OpenPopupOnItemClick($"###ctx{i}", ImGuiPopupFlags.MouseButtonRight);
if (ImGui.BeginPopup($"###ctx{i}"))
{
if (ImGui.Selectable($"Remove {_itemLookup[itemId].Name}"))
itemToRemove = itemId;
if (ImGui.BeginDragDropSource())
{
ImGui.SetDragDropPayload("DeliverooDragDrop", nint.Zero, 0);
_dragDropSource = purchaseOption;
ImGui.EndPopup();
ImGui.EndDragDropSource();
}
if (ImGui.BeginDragDropTarget())
{
if (_dragDropSource != null &&
ImGui.AcceptDragDropPayload("DeliverooDragDrop").NativePtr != null)
{
itemToAdd = _dragDropSource;
indexToAdd = i;
_dragDropSource = null;
}
ImGui.EndDragDropTarget();
}
ImGui.OpenPopupOnItemClick($"###ctx{i}", ImGuiPopupFlags.MouseButtonRight);
if (ImGui.BeginPopup($"###ctx{i}"))
{
bool sameQuantityForAllLists = purchaseOption.SameQuantityForAllLists;
if (ImGui.MenuItem("Use same quantity for global and character-specific lists", null,
ref sameQuantityForAllLists))
{
purchaseOption.SameQuantityForAllLists = sameQuantityForAllLists;
Save();
}
if (ImGui.MenuItem($"Remove {_itemLookup[purchaseOption.ItemId].Name}"))
itemToRemove = purchaseOption;
ImGui.EndPopup();
}
if (icon != null)
{
ImGui.SameLine(0, 0);
ImGui.SetCursorPos(pos);
ImGui.Image(icon.ImGuiHandle, iconSize);
}
}
ImGui.EndDisabled();
@ -149,20 +174,20 @@ internal sealed class ConfigWindow : LWindow
if (itemToRemove != null)
{
_configuration.ItemsAvailableForPurchase.Remove(itemToRemove.Value);
_configuration.ItemsAvailableToPurchase.Remove(itemToRemove);
Save();
}
if (itemToAdd != null)
{
_configuration.ItemsAvailableForPurchase.Remove(itemToAdd.Value);
_configuration.ItemsAvailableForPurchase.Insert(indexToAdd, itemToAdd.Value);
_configuration.ItemsAvailableToPurchase.Remove(itemToAdd);
_configuration.ItemsAvailableToPurchase.Insert(indexToAdd, itemToAdd);
Save();
}
List<(uint ItemId, string Name, ushort IconId, bool Limited)> comboValues = _gcRewardsCache.Rewards
.Where(x => x.SubCategory is RewardSubCategory.Materials or RewardSubCategory.Materiel)
.Where(x => !_configuration.ItemsAvailableForPurchase.Contains(x.ItemId))
.Where(x => _configuration.ItemsAvailableToPurchase.All(y => y.ItemId != x.ItemId))
.Select(x => (x.ItemId, x.Name, x.IconId, x.Limited))
.OrderBy(x => x.Name)
.ThenBy(x => x.GetHashCode())
@ -177,12 +202,14 @@ internal sealed class ConfigWindow : LWindow
foreach (var item in comboValues.Where(x =>
x.Name.Contains(_searchString, StringComparison.OrdinalIgnoreCase)))
{
IDalamudTextureWrap? icon = _iconCache.GetIcon(item.IconId);
if (icon != null)
using (var icon = _iconCache.GetIcon(item.IconId))
{
ImGui.Image(icon.ImGuiHandle, new Vector2(ImGui.GetFrameHeight()));
ImGui.SameLine();
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + ImGui.GetStyle().FramePadding.X);
if (icon != null)
{
ImGui.Image(icon.ImGuiHandle, new Vector2(ImGui.GetFrameHeight()));
ImGui.SameLine();
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + ImGui.GetStyle().FramePadding.X);
}
}
bool addThis =
@ -190,7 +217,8 @@ internal sealed class ConfigWindow : LWindow
$"{item.Name}{(item.Limited ? $" {SeIconChar.Hyadelyn.ToIconString()}" : "")}##SelectVenture{item.IconId}");
if (addThis || addFirst)
{
_configuration.ItemsAvailableForPurchase.Add(item.ItemId);
_configuration.ItemsAvailableToPurchase.Add(new Configuration.PurchaseOption
{ ItemId = item.ItemId });
if (addFirst)
{
@ -216,7 +244,7 @@ internal sealed class ConfigWindow : LWindow
if (_clientState is { IsLoggedIn: true, LocalContentId: > 0 })
{
string currentCharacterName = _clientState.LocalPlayer!.Name.ToString();
string currentWorldName = _clientState.LocalPlayer.HomeWorld.GameData!.Name.ToString();
string currentWorldName = _clientState.LocalPlayer.HomeWorld.Value.Name.ToString();
ImGui.Text($"Current Character: {currentCharacterName} @ {currentWorldName}");
ImGui.Spacing();
ImGui.Separator();
@ -359,6 +387,30 @@ internal sealed class ConfigWindow : LWindow
ImGui.EndDisabled();
ImGui.SetNextItemWidth(ImGuiHelpers.GlobalScale * 120);
int selectedKey = Math.Max(0, _keyCodes.FindIndex(x => x.Key == _configuration.QuickTurnInKey));
if (ImGui.Combo("Quick Turn-In Key", ref selectedKey, _keyCodes.Select(x => x.Label).ToArray(),
_keyCodes.Count))
{
_configuration.QuickTurnInKey = _keyCodes[selectedKey].Key;
Save();
}
ImGui.SetNextItemWidth(ImGuiHelpers.GlobalScale * 120);
var xivChatTypes = Enum.GetValues<XivChatType>()
.Where(x => x != XivChatType.StandardEmote)
.ToArray();
var selectedChatType = Array.IndexOf(xivChatTypes, _configuration.ChatType);
string[] chatTypeNames = xivChatTypes
.Select(t => t.GetAttribute<XivChatTypeInfoAttribute>()?.FancyName ?? t.ToString())
.ToArray();
if (ImGui.Combo("Chat channel for status updates", ref selectedChatType, chatTypeNames,
chatTypeNames.Length))
{
_configuration.ChatType = xivChatTypes[selectedChatType];
Save();
}
ImGui.SetNextItemWidth(ImGuiHelpers.GlobalScale * 120);
List<(int Rank, string Name)> rankUpComboValues = Enumerable.Range(1, 30)
.Select(x => x == 1 ? (0, "---") : (x, $"Rank {x}"))
@ -382,10 +434,33 @@ internal sealed class ConfigWindow : LWindow
Save();
}
ImGui.Separator();
bool disableFrameLimiter = _configuration.DisableFrameLimiter;
if (ImGui.Checkbox("Disable the game setting 'Limit frame rate when client is inactive'",
ref disableFrameLimiter))
{
_configuration.DisableFrameLimiter = disableFrameLimiter;
Save();
}
ImGui.Indent();
ImGui.BeginDisabled(!disableFrameLimiter);
bool uncapFrameRate = _configuration.UncapFrameRate;
if (ImGui.Checkbox("Set frame rate to uncapped", ref uncapFrameRate))
{
_configuration.UncapFrameRate = uncapFrameRate;
Save();
}
ImGui.EndDisabled();
ImGui.Unindent();
ImGui.EndTabItem();
}
}
private void Save() => _pluginInterface.SavePluginConfig(_configuration);
public void SaveWindowConfig() => Save();
}

View File

@ -3,12 +3,14 @@ using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Game.ClientState.Objects.Enums;
using Dalamud.Game.ClientState.Keys;
using Dalamud.Game.Text;
using Dalamud.Interface;
using Dalamud.Interface.Colors;
using Dalamud.Interface.Components;
using Dalamud.Interface.Internal;
using Dalamud.Interface.Textures.TextureWraps;
using Dalamud.Interface.Utility;
using Dalamud.Interface.Utility.Raii;
using Dalamud.Plugin;
using Dalamud.Plugin.Services;
using Deliveroo.GameData;
@ -20,7 +22,7 @@ using LLib.ImGui;
namespace Deliveroo.Windows;
internal sealed class TurnInWindow : LWindow
internal sealed class TurnInWindow : LWindow, IPersistableWindowConfig<Configuration.MinimizableWindowConfig>
{
private static readonly IReadOnlyList<InventoryType> InventoryTypes = new[]
{
@ -45,20 +47,23 @@ internal sealed class TurnInWindow : LWindow
private static readonly string[] StockingTypeLabels = { "Purchase Once", "Keep in Stock" };
private readonly DeliverooPlugin _plugin;
private readonly DalamudPluginInterface _pluginInterface;
private readonly IDalamudPluginInterface _pluginInterface;
private readonly Configuration _configuration;
private readonly ICondition _condition;
private readonly IClientState _clientState;
private readonly GcRewardsCache _gcRewardsCache;
private readonly ConfigWindow _configWindow;
private readonly IconCache _iconCache;
private readonly IKeyState _keyState;
private readonly GameFunctions _gameFunctions;
private readonly TitleBarButton _minimizeButton;
private bool _state;
private Guid? _draggedItem;
public TurnInWindow(DeliverooPlugin plugin, DalamudPluginInterface pluginInterface, Configuration configuration,
public TurnInWindow(DeliverooPlugin plugin, IDalamudPluginInterface pluginInterface, Configuration configuration,
ICondition condition, IClientState clientState, GcRewardsCache gcRewardsCache, ConfigWindow configWindow,
IconCache iconCache, GameFunctions gameFunctions)
IconCache iconCache, IKeyState keyState, GameFunctions gameFunctions)
: base("GC Delivery###DeliverooTurnIn")
{
_plugin = plugin;
@ -69,6 +74,7 @@ internal sealed class TurnInWindow : LWindow
_gcRewardsCache = gcRewardsCache;
_configWindow = configWindow;
_iconCache = iconCache;
_keyState = keyState;
_gameFunctions = gameFunctions;
Position = new Vector2(100, 100);
@ -84,6 +90,20 @@ internal sealed class TurnInWindow : LWindow
ShowCloseButton = false;
AllowClickthrough = false;
_minimizeButton = new TitleBarButton
{
Icon = FontAwesomeIcon.Minus,
Priority = int.MinValue,
IconOffset = new Vector2(1.5f, 1),
Click = _ =>
{
IsMinimized = !IsMinimized;
_minimizeButton!.Icon = IsMinimized ? FontAwesomeIcon.WindowMaximize : FontAwesomeIcon.Minus;
},
AvailableClickthrough = true,
};
TitleBarButtons.Insert(0, _minimizeButton);
TitleBarButtons.Add(new TitleBarButton
{
Icon = FontAwesomeIcon.Cog,
@ -99,6 +119,18 @@ internal sealed class TurnInWindow : LWindow
});
}
public Configuration.MinimizableWindowConfig WindowConfig => _configuration.TurnInWindowConfig;
private bool IsMinimized
{
get => WindowConfig.IsMinimized;
set
{
WindowConfig.IsMinimized = value;
SaveWindowConfig();
}
}
public bool State
{
get => _state;
@ -120,7 +152,7 @@ internal sealed class TurnInWindow : LWindow
private bool IsOnHomeWorld =>
_clientState.LocalPlayer == null ||
_clientState.LocalPlayer.HomeWorld.Id == _clientState.LocalPlayer.CurrentWorld.Id;
_clientState.LocalPlayer.HomeWorld.RowId == _clientState.LocalPlayer.CurrentWorld.RowId;
private IItemsToPurchase ItemsWrapper => UseCharacterSpecificItemsToPurchase
? new CharacterSpecificItemsToPurchase(_plugin.CharacterConfiguration!, _pluginInterface)
@ -144,13 +176,16 @@ internal sealed class TurnInWindow : LWindow
.Where(x => x.Reward.RequiredRank <= rank)
.Select(x =>
{
var purchaseOption = _configuration.ItemsAvailableToPurchase.Where(y => y.SameQuantityForAllLists)
.FirstOrDefault(y => y.ItemId == x.Item.ItemId);
int limit = purchaseOption?.GlobalLimit ?? x.Item.Limit;
var request = 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,
limit <= 0 ? uint.MaxValue : (uint)limit,
x.Reward.StackSize,
x.Reward.InventoryLimit),
SealCost = x.Reward.SealCost,
@ -209,57 +244,85 @@ internal sealed class TurnInWindow : LWindow
}
float indentSize = ImGui.GetFrameHeight() + ImGui.GetStyle().ItemInnerSpacing.X;
ImGui.Indent(indentSize);
if (!string.IsNullOrEmpty(Error))
{
ImGui.TextColored(ImGuiColors.DalamudRed, Error);
using (ImRaii.PushIndent(indentSize))
ImGui.TextColored(ImGuiColors.DalamudRed, Error);
}
else
{
if (_configuration.BehaviorOnOtherWorld == Configuration.EBehaviorOnOtherWorld.Warning && !IsOnHomeWorld)
using (ImRaii.PushIndent(indentSize))
{
ImGui.TextColored(ImGuiColors.DalamudRed,
"You are not on your home world and will not earn FC points.");
}
if (Multiplier == 1m)
{
ImGui.TextColored(ImGuiColors.DalamudYellow, "You do not have an active seal buff.");
}
else
{
ImGui.TextColored(ImGuiColors.HealerGreen, $"Current Buff: {(Multiplier - 1m) * 100:N0}%%");
}
if (Multiplier <= 1.10m)
{
InventoryManager* inventoryManager = InventoryManager.Instance();
AgentInventoryContext* agentInventoryContext = AgentInventoryContext.Instance();
if (inventoryManager->GetInventoryItemCount(ItemIds.PrioritySealAllowance) > 0)
if (_configuration.BehaviorOnOtherWorld == Configuration.EBehaviorOnOtherWorld.Warning &&
!IsOnHomeWorld)
{
ImGui.BeginDisabled(_condition[ConditionFlag.OccupiedInQuestEvent] ||
_condition[ConditionFlag.Casting] ||
agentInventoryContext == null);
if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Bolt, "Use Priority Seal Allowance (15%)"))
{
agentInventoryContext->UseItem(ItemIds.PrioritySealAllowance);
}
ImGui.TextColored(ImGuiColors.DalamudRed,
"You are not on your home world and will not earn FC points.");
}
ImGui.EndDisabled();
if (Multiplier == 1m)
{
ImGui.TextColored(ImGuiColors.DalamudYellow, "You do not have an active seal buff.");
}
else
{
ImGui.TextColored(ImGuiColors.HealerGreen, $"Current Buff: {(Multiplier - 1m) * 100:N0}%%");
}
if (Multiplier <= 1.10m)
{
InventoryManager* inventoryManager = InventoryManager.Instance();
AgentInventoryContext* agentInventoryContext = AgentInventoryContext.Instance();
if (inventoryManager->GetInventoryItemCount(ItemIds.PrioritySealAllowance) > 0)
{
ImGui.BeginDisabled(_condition[ConditionFlag.OccupiedInQuestEvent] ||
_condition[ConditionFlag.Casting] ||
agentInventoryContext == null);
if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Bolt,
"Use Priority Seal Allowance (15%)"))
{
agentInventoryContext->UseItem(ItemIds.PrioritySealAllowance);
}
ImGui.EndDisabled();
}
}
}
ImGui.Unindent(indentSize);
ImGui.Separator();
ImGui.BeginDisabled(state);
DrawItemsToBuy(grandCompany);
ImGui.EndDisabled();
if (!IsMinimized)
{
ImGui.Separator();
using (ImRaii.Disabled(state))
DrawItemsToBuy(grandCompany);
}
}
ImGui.Separator();
ImGui.Text($"Debug (State): {_plugin.CurrentStage}");
if (_configuration.QuickTurnInKey != VirtualKey.NO_KEY)
{
var key = _configuration.QuickTurnInKey switch
{
VirtualKey.MENU => "ALT",
VirtualKey.SHIFT => "SHIFT",
VirtualKey.CONTROL => "CTRL",
_ => _configuration.QuickTurnInKey.ToString()
};
if (!State && _keyState[_configuration.QuickTurnInKey])
{
ImGui.Separator();
ImGui.TextColored(ImGuiColors.HealerGreen, "Click an item to turn it in without confirmation");
}
else if (!IsMinimized)
{
ImGui.Separator();
ImGui.Text($"Hold '{key}' when clicking an item to turn it in without confirmation.");
}
}
if (!IsMinimized)
{
ImGui.Separator();
ImGui.Text($"Debug (State): {_plugin.CurrentStage}");
}
}
private unsafe void DrawNextRankPrequesites()
@ -271,7 +334,7 @@ internal sealed class TurnInWindow : LWindow
uint requiredSeals = _gameFunctions.GetSealsRequiredForNextRank();
int currentHuntingLog =
MonsterNoteManager.Instance()->RankDataArraySpan[(int)_gameFunctions.GetGrandCompany() + 7]
MonsterNoteManager.Instance()->RankData[(int)_gameFunctions.GetGrandCompany() + 7]
.Rank;
byte requiredHuntingLog = _gameFunctions.GetRequiredHuntingLogForNextRank();
@ -318,13 +381,20 @@ internal sealed class TurnInWindow : LWindow
{
(GcRewardItem.None, GcRewardItem.None.Name, GcRewardItem.None.Name),
};
foreach (uint itemId in _configuration.ItemsAvailableForPurchase)
foreach (Configuration.PurchaseOption purchaseOption in _configuration.ItemsAvailableToPurchase)
{
var gcReward = _gcRewardsCache.GetReward(itemId);
int itemCountWithoutRetainers = _gameFunctions.GetItemCount(itemId, false);
int itemCountWithRetainers = _gameFunctions.GetItemCount(itemId, true);
var gcReward = _gcRewardsCache.GetReward(purchaseOption.ItemId);
int itemCountWithoutRetainers = _gameFunctions.GetItemCount(purchaseOption.ItemId, false);
int itemCountWithRetainers = _gameFunctions.GetItemCount(purchaseOption.ItemId, true);
string itemNameWithoutRetainers = gcReward.Name;
string itemNameWithRetainers = gcReward.Name;
if (purchaseOption.SameQuantityForAllLists)
{
itemNameWithoutRetainers += $" {((SeIconChar)57412).ToIconString()}";
itemNameWithRetainers += $" {((SeIconChar)57412).ToIconString()}";
}
if (itemCountWithoutRetainers > 0)
itemNameWithoutRetainers += $" ({itemCountWithoutRetainers:N0})";
if (itemCountWithRetainers > 0)
@ -338,161 +408,42 @@ internal sealed class TurnInWindow : LWindow
itemsWrapper.Save();
}
int? itemToRemove = null;
Configuration.PurchasePriority? itemToRemove = null;
Configuration.PurchasePriority? itemToAdd = null;
int indexToAdd = 0;
float width = ImGui.GetContentRegionAvail().X;
List<(Vector2 TopLeft, Vector2 BottomRight)> itemPositions = [];
for (int i = 0; i < itemsWrapper.GetItemsToPurchase().Count; ++i)
{
ImGui.PushID($"ItemToBuy{i}");
Configuration.PurchasePriority item = itemsWrapper.GetItemsToPurchase()[i];
DrawItemToBuy(grandCompany, i, itemsWrapper, comboValues, width, ref itemToRemove, itemPositions);
}
float indentX = ImGui.GetCursorPosX();
bool enabled = item.Enabled;
int popColors = 0;
if (!enabled)
if (!ImGui.IsMouseDragging(ImGuiMouseButton.Left))
_draggedItem = null;
else if (_draggedItem != null)
{
var items = itemsWrapper.GetItemsToPurchase().ToList();
var draggedItem = items.Single(x => x.InternalId == _draggedItem);
int oldIndex = items.IndexOf(draggedItem);
var (topLeft, bottomRight) = itemPositions[oldIndex];
ImGui.GetWindowDrawList().AddRect(topLeft, bottomRight, ImGui.GetColorU32(ImGuiColors.DalamudGrey), 3f,
ImDrawFlags.RoundCornersAll);
int newIndex = itemPositions.FindIndex(x => ImGui.IsMouseHoveringRect(x.TopLeft, x.BottomRight, true));
if (newIndex >= 0 && oldIndex != newIndex)
{
ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(1f, 0.5f, 0.35f, 1f));
popColors++;
itemToAdd = items.Single(x => x.InternalId == _draggedItem);
indexToAdd = newIndex;
}
}
if (ImGui.Button($"{item.GetIcon()}"))
ImGui.OpenPopup($"Configure{i}");
ImGui.PopStyleColor(popColors);
if (ImGui.BeginPopup($"Configure{i}"))
{
if (ImGui.Checkbox($"Enabled##Enabled{i}", ref enabled))
{
item.Enabled = enabled;
itemsWrapper.Save();
}
ImGui.SetNextItemWidth(375 * ImGuiHelpers.GlobalScale);
int type = (int)item.Type;
if (ImGui.Combo($"##Type{i}", ref type, StockingTypeLabels, StockingTypeLabels.Length))
{
item.Type = (Configuration.PurchaseType)type;
if (item.Type != Configuration.PurchaseType.KeepStocked)
item.CheckRetainerInventory = false;
itemsWrapper.Save();
}
if (item.Type == Configuration.PurchaseType.KeepStocked && item.ItemId != ItemIds.Venture)
{
bool checkRetainerInventory = item.CheckRetainerInventory;
if (ImGui.Checkbox("Check Retainer Inventory for items (requires AllaganTools)",
ref checkRetainerInventory))
{
item.CheckRetainerInventory = checkRetainerInventory;
itemsWrapper.Save();
}
}
ImGui.EndPopup();
}
ImGui.SameLine(0, 3);
ImGui.BeginDisabled(!enabled);
int comboValueIndex = comboValues.FindIndex(x => x.Item.ItemId == item.ItemId);
if (comboValueIndex < 0)
{
item.ItemId = 0;
item.Limit = 0;
itemsWrapper.Save();
comboValueIndex = 0;
}
var comboItem = comboValues[comboValueIndex];
IDalamudTextureWrap? icon = _iconCache.GetIcon(comboItem.Item.IconId);
if (icon != null)
{
ImGui.Image(icon.ImGuiHandle, new Vector2(ImGui.GetFrameHeight()));
ImGui.SameLine(0, 3);
}
indentX = ImGui.GetCursorPosX() - indentX;
if (ImGui.Combo("", ref comboValueIndex,
comboValues.Select(x => item.CheckRetainerInventory ? x.NameWithRetainers : x.NameWithoutRetainers)
.ToArray(), comboValues.Count))
{
comboItem = comboValues[comboValueIndex];
item.ItemId = comboItem.Item.ItemId;
itemsWrapper.Save();
}
ImGui.EndDisabled();
if (itemsWrapper.GetItemsToPurchase().Count >= 2)
{
ImGui.SameLine();
if (ImGuiComponents.IconButton($"##Up{i}", FontAwesomeIcon.ArrowUp))
{
itemToAdd = item;
if (i > 0)
indexToAdd = i - 1;
else
indexToAdd = itemsWrapper.GetItemsToPurchase().Count - 1;
}
ImGui.SameLine(0, 0);
if (ImGuiComponents.IconButton($"##Down{i}", FontAwesomeIcon.ArrowDown))
{
itemToAdd = item;
if (i < itemsWrapper.GetItemsToPurchase().Count - 1)
indexToAdd = i + 1;
else
indexToAdd = 0;
}
ImGui.SameLine();
if (ImGuiComponents.IconButton($"###Remove{i}", FontAwesomeIcon.Times))
itemToRemove = i;
}
if (enabled)
{
ImGui.Indent(indentX);
if (comboValueIndex > 0)
{
ImGui.SetNextItemWidth(ImGuiHelpers.GlobalScale * 130);
int limit = Math.Min(item.Limit, (int)comboItem.Item.InventoryLimit);
int stepSize = comboItem.Item.StackSize < 99 ? 1 : 50;
string label = item.Type == Configuration.PurchaseType.KeepStocked
? "Maximum items to buy"
: "Remaining items to buy";
if (ImGui.InputInt(label, ref limit, stepSize, stepSize * 10))
{
item.Limit = Math.Min(Math.Max(0, limit), (int)comboItem.Item.InventoryLimit);
itemsWrapper.Save();
}
}
else if (item.Limit != 0)
{
item.Limit = 0;
itemsWrapper.Save();
}
if (comboValueIndex > 0)
{
if (!comboItem.Item.GrandCompanies.Contains(grandCompany))
{
ImGui.TextColored(ImGuiColors.DalamudRed,
"This item will be skipped, as you are in the wrong Grand Company.");
}
else if (comboItem.Item.RequiredRank > _gameFunctions.GetGrandCompanyRank())
{
ImGui.TextColored(ImGuiColors.DalamudRed,
"This item will be skipped, your rank isn't high enough to buy it.");
}
}
ImGui.Unindent(indentX);
}
ImGui.PopID();
if (itemToRemove != null)
{
itemsWrapper.Remove(itemToRemove);
itemsWrapper.Save();
}
if (itemToAdd != null)
@ -502,27 +453,22 @@ internal sealed class TurnInWindow : LWindow
itemsWrapper.Save();
}
if (itemToRemove != null)
{
itemsWrapper.RemoveAt(itemToRemove.Value);
itemsWrapper.Save();
}
if (_configuration.ItemsAvailableForPurchase.Any(x =>
itemsWrapper.GetItemsToPurchase().All(y => x != y.ItemId)))
if (_configuration.ItemsAvailableToPurchase.Any(x =>
itemsWrapper.GetItemsToPurchase().All(y => x.ItemId != y.ItemId)))
{
if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Plus, "Add Item"))
ImGui.OpenPopup("##AddItem");
if (ImGui.BeginPopupContextItem("##AddItem", ImGuiPopupFlags.NoOpenOverItems))
{
foreach (var itemId in _configuration.ItemsAvailableForPurchase.Distinct())
foreach (var purchaseOption in _configuration.ItemsAvailableToPurchase.Distinct())
{
if (_gcRewardsCache.RewardLookup.TryGetValue(itemId, out var reward))
if (_gcRewardsCache.RewardLookup.TryGetValue(purchaseOption.ItemId, out var reward))
{
if (ImGui.MenuItem($"{reward.Name}##{itemId}"))
if (ImGui.MenuItem($"{reward.Name}##{purchaseOption.ItemId}"))
{
itemsWrapper.Add(new Configuration.PurchasePriority { ItemId = itemId, Limit = 0 });
itemsWrapper.Add(new Configuration.PurchasePriority
{ ItemId = purchaseOption.ItemId, Limit = 0 });
itemsWrapper.Save();
ImGui.CloseCurrentPopup();
}
@ -533,9 +479,208 @@ internal sealed class TurnInWindow : LWindow
}
ImGui.SameLine();
if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Cog, "Configure available Items"))
_configWindow.IsOpen = true;
}
if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Cog, "Configure available Items"))
_configWindow.IsOpen = true;
}
private void DrawItemToBuy(GrandCompany grandCompany, int i, IItemsToPurchase itemsWrapper,
List<(GcRewardItem Item, string NameWithoutRetainers, string NameWithRetainers)> comboValues, float width,
ref Configuration.PurchasePriority? itemToRemove, List<(Vector2 TopLeft, Vector2 BottomRight)> itemPositions)
{
Vector2 topLeft = ImGui.GetCursorScreenPos() + new Vector2(0, -ImGui.GetStyle().ItemSpacing.Y / 2);
ImGui.PushID($"ItemToBuy{i}");
Configuration.PurchasePriority item = itemsWrapper.GetItemsToPurchase()[i];
float indentX = ImGui.GetCursorPosX();
bool enabled = item.Enabled;
int popColors = 0;
if (!enabled)
{
ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(1f, 0.5f, 0.35f, 1f));
popColors++;
}
if (ImGui.Button($"{item.GetIcon()}"))
ImGui.OpenPopup($"Configure{i}");
ImGui.PopStyleColor(popColors);
if (ImGui.BeginPopup($"Configure{i}"))
{
if (ImGui.Checkbox($"Enabled##Enabled{i}", ref enabled))
{
item.Enabled = enabled;
itemsWrapper.Save();
}
ImGui.SetNextItemWidth(375 * ImGuiHelpers.GlobalScale);
int type = (int)item.Type;
if (ImGui.Combo($"##Type{i}", ref type, StockingTypeLabels, StockingTypeLabels.Length))
{
item.Type = (Configuration.PurchaseType)type;
if (item.Type != Configuration.PurchaseType.KeepStocked)
item.CheckRetainerInventory = false;
itemsWrapper.Save();
}
if (item.Type == Configuration.PurchaseType.KeepStocked && item.ItemId != ItemIds.Venture)
{
bool checkRetainerInventory = item.CheckRetainerInventory;
if (ImGui.Checkbox("Check Retainer Inventory for items (requires AllaganTools)",
ref checkRetainerInventory))
{
item.CheckRetainerInventory = checkRetainerInventory;
itemsWrapper.Save();
}
}
ImGui.EndPopup();
}
ImGui.SameLine(0, 3);
ImGui.BeginDisabled(!enabled);
int comboValueIndex = comboValues.FindIndex(x => x.Item.ItemId == item.ItemId);
if (comboValueIndex < 0)
{
item.ItemId = 0;
item.Limit = 0;
itemsWrapper.Save();
comboValueIndex = 0;
}
var comboItem = comboValues[comboValueIndex];
using (var icon = _iconCache.GetIcon(comboItem.Item.IconId))
{
if (icon != null)
{
ImGui.Image(icon.ImGuiHandle, new Vector2(ImGui.GetFrameHeight()));
ImGui.SameLine(0, 3);
}
}
indentX = ImGui.GetCursorPosX() - indentX;
ImGui.PushFont(UiBuilder.IconFont);
ImGui.SetNextItemWidth(width -
ImGui.GetStyle().WindowPadding.X -
ImGui.CalcTextSize(FontAwesomeIcon.Circle.ToIconString()).X -
ImGui.CalcTextSize(FontAwesomeIcon.ArrowsUpDown.ToIconString()).X -
ImGui.CalcTextSize(FontAwesomeIcon.Times.ToIconString()).X -
ImGui.GetStyle().FramePadding.X * 8 -
ImGui.GetStyle().ItemSpacing.X * 2 - 3 * 3);
ImGui.PopFont();
if (ImGui.Combo("", ref comboValueIndex,
comboValues.Select(x => item.CheckRetainerInventory ? x.NameWithRetainers : x.NameWithoutRetainers)
.ToArray(), comboValues.Count))
{
comboItem = comboValues[comboValueIndex];
item.ItemId = comboItem.Item.ItemId;
itemsWrapper.Save();
}
ImGui.EndDisabled();
if (itemsWrapper.GetItemsToPurchase().Count >= 2)
{
ImGui.PushFont(UiBuilder.IconFont);
ImGui.SameLine(ImGui.GetContentRegionAvail().X +
ImGui.GetStyle().WindowPadding.X -
ImGui.CalcTextSize(FontAwesomeIcon.ArrowsUpDown.ToIconString()).X -
ImGui.CalcTextSize(FontAwesomeIcon.Times.ToIconString()).X -
ImGui.GetStyle().FramePadding.X * 4 -
ImGui.GetStyle().ItemSpacing.X);
ImGui.PopFont();
if (_draggedItem == item.InternalId)
{
ImGuiComponents.IconButton("##Move", FontAwesomeIcon.ArrowsUpDown,
ImGui.ColorConvertU32ToFloat4(ImGui.GetColorU32(ImGuiCol.ButtonActive)));
}
else
ImGuiComponents.IconButton("##Move", FontAwesomeIcon.ArrowsUpDown);
if (_draggedItem == null && ImGui.IsItemActive() && ImGui.IsMouseDragging(ImGuiMouseButton.Left))
_draggedItem = item.InternalId;
ImGui.SameLine();
}
else
{
ImGui.PushFont(UiBuilder.IconFont);
ImGui.SameLine(ImGui.GetContentRegionAvail().X +
ImGui.GetStyle().WindowPadding.X -
ImGui.CalcTextSize(FontAwesomeIcon.Times.ToIconString()).X -
ImGui.GetStyle().FramePadding.X * 2);
ImGui.PopFont();
}
if (ImGuiComponents.IconButton($"###Remove{i}", FontAwesomeIcon.Times))
itemToRemove = item;
if (enabled)
{
ImGui.Indent(indentX);
if (comboValueIndex > 0)
{
var purchaseOption =
_configuration.ItemsAvailableToPurchase
.Where(x => x.SameQuantityForAllLists)
.FirstOrDefault(x => x.ItemId == item.ItemId);
ImGui.SetNextItemWidth(ImGuiHelpers.GlobalScale * 130);
int limit = Math.Min(purchaseOption?.GlobalLimit ?? item.Limit, (int)comboItem.Item.InventoryLimit);
int stepSize = comboItem.Item.StackSize < 99 ? 1 : 50;
string label = item.Type == Configuration.PurchaseType.KeepStocked
? "Maximum items to buy"
: "Remaining items to buy";
if (ImGui.InputInt(label, ref limit, stepSize, stepSize * 10))
{
int newLimit = Math.Min(Math.Max(0, limit), (int)comboItem.Item.InventoryLimit);
if (purchaseOption != null)
{
purchaseOption.GlobalLimit = newLimit;
_pluginInterface.SavePluginConfig(_configuration);
}
else
{
item.Limit = newLimit;
itemsWrapper.Save();
}
}
}
else if (item.Limit != 0)
{
item.Limit = 0;
itemsWrapper.Save();
}
if (comboValueIndex > 0)
{
if (!comboItem.Item.GrandCompanies.Contains(grandCompany))
{
ImGui.TextColored(ImGuiColors.DalamudRed,
"This item will be skipped, as you are in the wrong Grand Company.");
}
else if (comboItem.Item.RequiredRank > _gameFunctions.GetGrandCompanyRank())
{
ImGui.TextColored(ImGuiColors.DalamudRed,
"This item will be skipped, your rank isn't high enough to buy it.");
}
}
ImGui.Unindent(indentX);
}
ImGui.PopID();
Vector2 bottomRight = new Vector2(topLeft.X + width,
ImGui.GetCursorScreenPos().Y - ImGui.GetStyle().ItemSpacing.Y + 2);
itemPositions.Add((topLeft, bottomRight));
}
private unsafe uint CalculateEffectiveLimit(uint itemId, uint limit, uint stackSize, uint inventoryLimit)
@ -552,7 +697,7 @@ internal sealed class TurnInWindow : LWindow
for (int i = 0; i < container->Size; ++i)
{
var item = container->GetInventorySlot(i);
if (item == null || item->ItemID == 0 || item->ItemID == itemId)
if (item == null || item->ItemId == 0 || item->ItemId == itemId)
{
slotsThatCanBeUsed++;
}
@ -563,6 +708,8 @@ internal sealed class TurnInWindow : LWindow
}
}
public void SaveWindowConfig() => _pluginInterface.SavePluginConfig(_configuration);
private interface IItemsToPurchase
{
string Name { get; }
@ -578,10 +725,10 @@ internal sealed class TurnInWindow : LWindow
private sealed class CharacterSpecificItemsToPurchase : IItemsToPurchase
{
private readonly CharacterConfiguration _characterConfiguration;
private readonly DalamudPluginInterface _pluginInterface;
private readonly IDalamudPluginInterface _pluginInterface;
public CharacterSpecificItemsToPurchase(CharacterConfiguration characterConfiguration,
DalamudPluginInterface pluginInterface)
IDalamudPluginInterface pluginInterface)
{
_characterConfiguration = characterConfiguration;
_pluginInterface = pluginInterface;
@ -611,9 +758,9 @@ internal sealed class TurnInWindow : LWindow
private sealed class GlobalItemsToPurchase : IItemsToPurchase
{
private readonly Configuration _configuration;
private readonly DalamudPluginInterface _pluginInterface;
private readonly IDalamudPluginInterface _pluginInterface;
public GlobalItemsToPurchase(Configuration configuration, DalamudPluginInterface pluginInterface)
public GlobalItemsToPurchase(Configuration configuration, IDalamudPluginInterface pluginInterface)
{
_configuration = configuration;
_pluginInterface = pluginInterface;

View File

@ -4,12 +4,83 @@
"net8.0-windows7.0": {
"DalamudPackager": {
"type": "Direct",
"requested": "[2.1.12, )",
"resolved": "2.1.12",
"contentHash": "Sc0PVxvgg4NQjcI8n10/VfUQBAS4O+Fw2pZrAqBdRMbthYGeogzu5+xmIGCGmsEZ/ukMOBuAqiNiB5qA3MRalg=="
"requested": "[11.0.0, )",
"resolved": "11.0.0",
"contentHash": "bjT7XUlhIJSmsE/O76b7weUX+evvGQctbQB8aKXt94o+oPWxHpCepxAGMs7Thow3AzCyqWs7cOpp9/2wcgRRQA=="
},
"DotNet.ReproducibleBuilds": {
"type": "Direct",
"requested": "[1.1.1, )",
"resolved": "1.1.1",
"contentHash": "+H2t/t34h6mhEoUvHi8yGXyuZ2GjSovcGYehJrS2MDm2XgmPfZL2Sdxg+uL2lKgZ4M6tTwKHIlxOob2bgh0NRQ==",
"dependencies": {
"Microsoft.SourceLink.AzureRepos.Git": "1.1.1",
"Microsoft.SourceLink.Bitbucket.Git": "1.1.1",
"Microsoft.SourceLink.GitHub": "1.1.1",
"Microsoft.SourceLink.GitLab": "1.1.1"
}
},
"Microsoft.SourceLink.Gitea": {
"type": "Direct",
"requested": "[8.0.0, )",
"resolved": "8.0.0",
"contentHash": "KOBodmDnlWGIqZt2hT47Q69TIoGhIApDVLCyyj9TT5ct8ju16AbHYcB4XeknoHX562wO1pMS/1DfBIZK+V+sxg==",
"dependencies": {
"Microsoft.Build.Tasks.Git": "8.0.0",
"Microsoft.SourceLink.Common": "8.0.0"
}
},
"Microsoft.Build.Tasks.Git": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ=="
},
"Microsoft.SourceLink.AzureRepos.Git": {
"type": "Transitive",
"resolved": "1.1.1",
"contentHash": "qB5urvw9LO2bG3eVAkuL+2ughxz2rR7aYgm2iyrB8Rlk9cp2ndvGRCvehk3rNIhRuNtQaeKwctOl1KvWiklv5w==",
"dependencies": {
"Microsoft.Build.Tasks.Git": "1.1.1",
"Microsoft.SourceLink.Common": "1.1.1"
}
},
"Microsoft.SourceLink.Bitbucket.Git": {
"type": "Transitive",
"resolved": "1.1.1",
"contentHash": "cDzxXwlyWpLWaH0em4Idj0H3AmVo3L/6xRXKssYemx+7W52iNskj/SQ4FOmfCb8YQt39otTDNMveCZzYtMoucQ==",
"dependencies": {
"Microsoft.Build.Tasks.Git": "1.1.1",
"Microsoft.SourceLink.Common": "1.1.1"
}
},
"Microsoft.SourceLink.Common": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw=="
},
"Microsoft.SourceLink.GitHub": {
"type": "Transitive",
"resolved": "1.1.1",
"contentHash": "IaJGnOv/M7UQjRJks7B6p7pbPnOwisYGOIzqCz5ilGFTApZ3ktOR+6zJ12ZRPInulBmdAf1SrGdDG2MU8g6XTw==",
"dependencies": {
"Microsoft.Build.Tasks.Git": "1.1.1",
"Microsoft.SourceLink.Common": "1.1.1"
}
},
"Microsoft.SourceLink.GitLab": {
"type": "Transitive",
"resolved": "1.1.1",
"contentHash": "tvsg47DDLqqedlPeYVE2lmiTpND8F0hkrealQ5hYltSmvruy/Gr5nHAKSsjyw5L3NeM/HLMI5ORv7on/M4qyZw==",
"dependencies": {
"Microsoft.Build.Tasks.Git": "1.1.1",
"Microsoft.SourceLink.Common": "1.1.1"
}
},
"llib": {
"type": "Project"
"type": "Project",
"dependencies": {
"DalamudPackager": "[11.0.0, )"
}
}
}
}

2
LLib

@ -1 +1 @@
Subproject commit 3792244261a9f5426a7916f5a6dd1966238ba84a
Subproject commit b581e2ea2a61f44ed3f0cb4f6ea8cc1595525544